wlbt: Implement log collection Framework
authorAlbert Cano <a.canocamps@samsung.com>
Thu, 12 Jul 2018 12:26:32 +0000 (13:26 +0100)
committerhskang <hs1218.kang@samsung.com>
Fri, 17 Aug 2018 00:32:53 +0000 (20:32 -0400)
Implemented log collection Framework.

- Interested clients/writers register with the framework
- On event trigger, framework will execute clients callback
- Framework will generate sbl file as described in
http://confluence/display/PLAT/Sable+-+File+Format
- Add entry in proc interface to trigger log collection from
Userland (User manual trigger and dumpsys)

Change-Id: I8a37af7c8aac983df2bb9963e1832d769893d530
SCSC-Bug-Id: SSB-41841
Signed-off-by: Cristian Marussi <c.marussi@samsung.com>
Signed-off-by: Albert Cano <a.canocamps@samsung.com>
drivers/misc/samsung/scsc/Kconfig
drivers/misc/samsung/scsc/Makefile
drivers/misc/samsung/scsc/scsc_log_collector.c [new file with mode: 0644]
drivers/misc/samsung/scsc/scsc_log_collector_proc.c [new file with mode: 0644]
drivers/misc/samsung/scsc/scsc_log_collector_proc.h [new file with mode: 0644]
include/scsc/scsc_log_collector.h [new file with mode: 0644]

index 9ea60e232ad8a3f30969abfbec02d6220718f4f1..82317080844e5eda74ea83eb30a7d11996c4b5b0 100644 (file)
@@ -135,3 +135,6 @@ config SCSC_WIFILOGGER_TEST
        depends on SCSC_WIFILOGGER && SCSC_WIFILOGGER_DEBUGFS
        default n
 
+config SCSC_LOG_COLLECTION
+       bool "Samsung SCSC Log Collection"
+       depends on SCSC_CORE
index b67491f50cdb704dceaf2b7b9ad97c03afa2fa3f..d6e9b94f14213aabae6959676a7ce35b4416933d 100644 (file)
@@ -15,6 +15,9 @@ ccflags-y += $(CONFIG_SAMSUNG_MAXWELL_EXTRA)
 
 obj-$(CONFIG_SCSC_DEBUG) += scsc_logring.o
 scsc_logring-y += scsc_logring_main.o scsc_logring_ring.o scsc_logring_debugfs.o
+
+obj-$(CONFIG_SCSC_LOG_COLLECTION) += scsc_log_collection.o
+scsc_log_collection-y += scsc_log_collector.o scsc_log_collector_proc.o
 ##
 ## The following cumbersome ifeq/else is the only way to make
 ## this CONFIG_SCSC_PRINTK propagate correctly in any scenario (y,m,n)
diff --git a/drivers/misc/samsung/scsc/scsc_log_collector.c b/drivers/misc/samsung/scsc/scsc_log_collector.c
new file mode 100644 (file)
index 0000000..49913c7
--- /dev/null
@@ -0,0 +1,390 @@
+/********************************************************************************
+ *
+ *   Copyright (c) 2016 - 2018 Samsung Electronics Co., Ltd. All rights reserved.
+ *
+ ********************************************************************************/
+#include <linux/uaccess.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/list_sort.h>
+#include <linux/limits.h>
+#include <linux/workqueue.h>
+
+#include <scsc/scsc_log_collector.h>
+#include "scsc_log_collector_proc.h"
+#include <scsc/scsc_mx.h>
+
+#define SCSC_NUM_CHUNKS_SUPPORTED      12
+
+/* Add-remove supported chunks on this kernel */
+static u8 chunk_supported_sbl[SCSC_NUM_CHUNKS_SUPPORTED] = {
+       SCSC_LOG_CHUNK_SYNC,
+       SCSC_LOG_CHUNK_IMP,
+       SCSC_LOG_CHUNK_MXL,
+       SCSC_LOG_CHUNK_UDI,
+       SCSC_LOG_CHUNK_BT_HCF,
+       SCSC_LOG_CHUNK_WLAN_HCF,
+       SCSC_LOG_CHUNK_HIP4_SAMPLER,
+       SCSC_LOG_RESERVED_COMMON,
+       SCSC_LOG_RESERVED_BT,
+       SCSC_LOG_RESERVED_WLAN,
+       SCSC_LOG_RESERVED_RADIO,
+       SCSC_LOG_CHUNK_LOGRING,
+};
+
+/* Collect logs in an intermediate buffer to be collected at later time (mmap or wq) */
+static bool collect_to_ram;
+module_param(collect_to_ram, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(collect_to_ram, "Collect buffer in ram");
+
+static char collection_dir_buf[256] = "/data/exynos/log/wifi/";
+module_param_string(collection_target_directory, collection_dir_buf, sizeof(collection_dir_buf), S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(collection_target_directory, "Specify collection target directory");
+
+struct scsc_log_client {
+       struct list_head list;
+       struct scsc_log_collector_client *collect_client;
+};
+static struct scsc_log_collector_list { struct list_head list; } scsc_log_collector_list = {
+       .list = LIST_HEAD_INIT(scsc_log_collector_list.list)
+};
+
+struct scsc_log_status {
+       struct file *fp;
+       loff_t pos;
+       bool in_collection;
+       char fapi_ver[SCSC_LOG_FAPI_VERSION_SIZE];
+       struct mutex log_mutex;
+       struct mutex collection_mutex;
+
+       struct workqueue_struct *collection_workq;
+       struct work_struct      collect_work;
+       enum scsc_log_reason    collect_reason;
+} log_status;
+
+static void collection_worker(struct work_struct *work)
+{
+       struct scsc_log_status *ls;
+
+       ls = container_of(work, struct scsc_log_status, collect_work);
+       if (!ls)
+               return;
+       pr_info("SCSC running scheduled Log Collection - reason:%d\n", ls->collect_reason);
+       scsc_log_collector_collect(ls->collect_reason);
+}
+
+/* Module init */
+int __init scsc_log_collector(void)
+{
+       pr_info("Log Collector Init\n");
+
+       mutex_init(&log_status.log_mutex);
+       mutex_init(&log_status.collection_mutex);
+       log_status.in_collection = false;
+       log_status.collection_workq = create_workqueue("log_collector");
+       if (log_status.collection_workq)
+               INIT_WORK(&log_status.collect_work, collection_worker);
+       scsc_log_collect_proc_create();
+       return 0;
+}
+
+void __exit scsc_log_collector_exit(void)
+{
+       scsc_log_collect_proc_remove();
+       if (log_status.collection_workq) {
+               cancel_work_sync(&log_status.collect_work);
+               destroy_workqueue(log_status.collection_workq);
+               log_status.collection_workq = NULL;
+       }
+
+       pr_info("Log Collect Unloaded\n");
+}
+
+module_init(scsc_log_collector);
+module_exit(scsc_log_collector_exit);
+
+static bool scsc_is_chunk_supported(u8 type)
+{
+       u8 i;
+
+       for (i = 0; i < SCSC_NUM_CHUNKS_SUPPORTED; i++) {
+               if (type == chunk_supported_sbl[i])
+                       return true;
+       }
+
+       return false;
+}
+
+static int scsc_log_collector_compare(void *priv, struct list_head *A, struct list_head *B)
+{
+       struct scsc_log_client *a = list_entry(A, typeof(*a), list);
+       struct scsc_log_client *b = list_entry(B, typeof(*b), list);
+
+       if (a->collect_client->type < b->collect_client->type)
+               return -1;
+       else
+               return 1;
+}
+
+int scsc_log_collector_register_client(struct scsc_log_collector_client *collect_client)
+{
+       struct scsc_log_client *lc;
+
+       if (!scsc_is_chunk_supported(collect_client->type)) {
+               pr_info("Type not supported: %d\n", collect_client->type);
+               return -EIO;
+       }
+
+       lc = kzalloc(sizeof(*lc), GFP_KERNEL);
+       if (!lc)
+               return -ENOMEM;
+
+       lc->collect_client = collect_client;
+       list_add_tail(&lc->list, &scsc_log_collector_list.list);
+
+       /* Sort the list */
+       list_sort(NULL, &scsc_log_collector_list.list, scsc_log_collector_compare);
+
+       pr_info("Registered client: %s\n", collect_client->name);
+       return 0;
+}
+EXPORT_SYMBOL(scsc_log_collector_register_client);
+
+int scsc_log_collector_unregister_client(struct scsc_log_collector_client *collect_client)
+{
+       struct scsc_log_client *lc, *next;
+       bool match = false;
+
+       /* block any attempt of unregistering while a collection is in progres */
+       mutex_lock(&log_status.log_mutex);
+       list_for_each_entry_safe(lc, next, &scsc_log_collector_list.list, list) {
+               if (lc->collect_client == collect_client) {
+                       match = true;
+                       list_del(&lc->list);
+                       kfree(lc);
+               }
+       }
+
+       if (match == false)
+               pr_err("FATAL, no match for given scsc_log_collector_client\n");
+
+       pr_info("Unregistered client: %s\n", collect_client->name);
+       mutex_unlock(&log_status.log_mutex);
+
+       return 0;
+}
+EXPORT_SYMBOL(scsc_log_collector_unregister_client);
+
+
+static inline int __scsc_log_collector_write_to_ram(char __user *buf, size_t count, u8 align)
+{
+       return 0;
+}
+
+static inline int __scsc_log_collector_write_to_file(char __user *buf, size_t count, u8 align)
+{
+       int ret = 0;
+
+       if (!log_status.in_collection)
+               return -EIO;
+
+       log_status.pos = (log_status.pos + align - 1) & ~(align - 1);
+       /* Write buf to file */
+       ret = vfs_write(log_status.fp, buf, count, &log_status.pos);
+       if (ret < 0) {
+               pr_err("write file error, err = %d\n", ret);
+               return ret;
+       }
+       return 0;
+}
+
+int scsc_log_collector_write(char __user *buf, size_t count, u8 align)
+{
+       if (collect_to_ram)
+               return __scsc_log_collector_write_to_ram(buf, count, align);
+       else
+               return __scsc_log_collector_write_to_file(buf, count, align);
+}
+EXPORT_SYMBOL(scsc_log_collector_write);
+
+static inline int __scsc_log_collector_collect_to_ram(enum scsc_log_reason reason)
+{
+       return 0;
+}
+
+#define align_chunk(ppos) (((ppos) + (SCSC_LOG_CHUNK_ALIGN - 1)) & \
+                         ~(SCSC_LOG_CHUNK_ALIGN - 1))
+
+const char *scsc_loc_reason_str[] = { "unknown", "fw_panic", "host_trig",
+                                     "fw_trig", "dumpstate", "wlan_disc", "bt_trig" /* Add others */};
+
+static inline int __scsc_log_collector_collect_to_file(enum scsc_log_reason reason)
+{
+       struct scsc_log_client *lc, *next;
+       struct timeval t;
+       struct tm tm_n;
+       mm_segment_t old_fs;
+       char memdump_path[128];
+       int ret = 0;
+       char version_fw[SCSC_LOG_FW_VERSION_SIZE] = {0};
+       char version_host[SCSC_LOG_HOST_VERSION_SIZE] = {0};
+       u32 mem_pos, temp_pos, chunk_size;
+       ktime_t start;
+       u8 num_chunks = 0;
+       u16 first_chunk_pos = SCSC_LOG_OFFSET_FIRST_CHUNK;
+       struct scsc_log_sbl_header sbl_header;
+       struct scsc_log_chunk_header chk_header;
+       u8 j;
+
+       mutex_lock(&log_status.log_mutex);
+
+       pr_info("Log collection to file triggered\n");
+
+       start = ktime_get();
+       do_gettimeofday(&t);
+       time_to_tm(t.tv_sec, 0, &tm_n);
+
+       snprintf(memdump_path, sizeof(memdump_path), "%s%s_%s.sbl",
+                collection_dir_buf, "scsc_log", scsc_loc_reason_str[reason]);
+
+       /* change to KERNEL_DS address limit */
+       old_fs = get_fs();
+       set_fs(KERNEL_DS);
+
+       log_status.fp = filp_open(memdump_path, O_CREAT | O_WRONLY | O_SYNC | O_TRUNC, 0664);
+       if (IS_ERR(log_status.fp)) {
+               pr_err("open file error, err = %ld\n", PTR_ERR(log_status.fp));
+               goto exit;
+       }
+
+       log_status.in_collection = true;
+       /* Position index to start of the first chunk */
+       log_status.pos = SCSC_LOG_OFFSET_FIRST_CHUNK;
+
+       /* Call client init callbacks if any */
+       list_for_each_entry_safe(lc, next, &scsc_log_collector_list.list, list) {
+               if (lc->collect_client && lc->collect_client->collect_init)
+                       lc->collect_client->collect_init(lc->collect_client);
+       }
+       /* Traverse all the clients from the list.. Those would start calling scsc_log_collector_write!!*/
+       /* Create chunk */
+       list_for_each_entry_safe(lc, next, &scsc_log_collector_list.list, list) {
+               if (lc->collect_client) {
+                       num_chunks++;
+                       /* Create Chunk */
+                       /* Store current post */
+                       temp_pos = log_status.pos;
+                       /* Make room for chunck header */
+                       log_status.pos += SCSC_CHUNK_HEADER_SIZE;
+                       /* Execute clients callbacks */
+                       if (lc->collect_client->collect(lc->collect_client, 0))
+                               goto exit;
+                       /* Write chunk headers */
+                       /* Align log_status.pos */
+                       mem_pos = log_status.pos = align_chunk(log_status.pos);
+                       chunk_size = log_status.pos - temp_pos - SCSC_CHUNK_HEADER_SIZE;
+                       /* rewind pos */
+                       log_status.pos = temp_pos;
+                       /* Write chunk header */
+                       memcpy(chk_header.magic, "CHK", 3);
+                       chk_header.type = (char)lc->collect_client->type;
+                       chk_header.chunk_size = chunk_size;
+                       scsc_log_collector_write((char *)&chk_header, sizeof(struct scsc_log_chunk_header), 1);
+                       /* restore position for next chunk */
+                       log_status.pos = mem_pos;
+               }
+       }
+       /* Callbacks to clients have finished at this point. */
+       /* Write file header */
+       /* Move position to start of file */
+       log_status.pos = 0;
+       /* Write header */
+       memset(&sbl_header, 0, sizeof(sbl_header));
+       memcpy(sbl_header.magic, "SCSC", 4);
+       sbl_header.version_major = SCSC_LOG_HEADER_VERSION_MAJOR;
+       sbl_header.version_minor = SCSC_LOG_HEADER_VERSION_MINOR;
+       sbl_header.num_chunks = num_chunks;
+       sbl_header.reason = reason;
+       sbl_header.offset_data = first_chunk_pos;
+       mxman_get_fw_version(version_fw, SCSC_LOG_FW_VERSION_SIZE);
+       memcpy(sbl_header.fw_version, version_fw, SCSC_LOG_FW_VERSION_SIZE);
+       mxman_get_driver_version(version_host, SCSC_LOG_HOST_VERSION_SIZE);
+       memcpy(sbl_header.host_version, version_host, SCSC_LOG_HOST_VERSION_SIZE);
+       memcpy(sbl_header.fapi_version, log_status.fapi_ver, SCSC_LOG_FAPI_VERSION_SIZE);
+
+       memset(sbl_header.supported_chunks, SCSC_LOG_CHUNK_INVALID, SCSC_SUPPORTED_CHUNKS_HEADER);
+       for (j = 0; j < SCSC_NUM_CHUNKS_SUPPORTED; j++)
+               sbl_header.supported_chunks[j] = chunk_supported_sbl[j];
+
+       scsc_log_collector_write((char *)&sbl_header, sizeof(struct scsc_log_sbl_header), 1);
+
+       /* Sync file from filesystem to physical media */
+       ret = vfs_fsync(log_status.fp, 0);
+       if (ret < 0) {
+               pr_err("sync file error, error = %d\n", ret);
+               goto exit;
+       }
+
+exit:
+       /* close file before return */
+       if (!IS_ERR(log_status.fp))
+               filp_close(log_status.fp, current->files);
+
+       /* restore previous address limit */
+       set_fs(old_fs);
+
+       log_status.in_collection = false;
+
+       list_for_each_entry_safe(lc, next, &scsc_log_collector_list.list, list) {
+               if (lc->collect_client && lc->collect_client->collect_end)
+                       lc->collect_client->collect_end(lc->collect_client);
+       }
+
+       pr_info("File %s collection end. Took: %lld\n", memdump_path, ktime_to_ns(ktime_sub(ktime_get(), start)));
+
+       mutex_unlock(&log_status.log_mutex);
+       return ret;
+}
+
+int scsc_log_collector_collect(enum scsc_log_reason reason)
+{
+       int ret;
+
+       mutex_lock(&log_status.collection_mutex);
+       if (collect_to_ram)
+               ret = __scsc_log_collector_collect_to_ram(reason);
+       else
+               ret =  __scsc_log_collector_collect_to_file(reason);
+       mutex_unlock(&log_status.collection_mutex);
+
+       return ret;
+}
+EXPORT_SYMBOL(scsc_log_collector_collect);
+
+void  scsc_log_collector_schedule_collection(enum scsc_log_reason reason)
+{
+       if (log_status.collection_workq) {
+               log_status.collect_reason = reason;
+               queue_work(log_status.collection_workq, &log_status.collect_work);
+       } else {
+               pr_err("Log Collection Workqueue NOT available...aborting scheduled collection.\n");
+       }
+}
+EXPORT_SYMBOL(scsc_log_collector_schedule_collection);
+
+void scsc_log_collector_write_fapi(char __user *buf, size_t len)
+{
+       if (len > SCSC_LOG_FAPI_VERSION_SIZE)
+               len = SCSC_LOG_FAPI_VERSION_SIZE;
+       memcpy(log_status.fapi_ver, buf, len);
+}
+EXPORT_SYMBOL(scsc_log_collector_write_fapi);
+
+MODULE_DESCRIPTION("SCSC Log collector");
+MODULE_AUTHOR("SLSI");
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/misc/samsung/scsc/scsc_log_collector_proc.c b/drivers/misc/samsung/scsc/scsc_log_collector_proc.c
new file mode 100644 (file)
index 0000000..30cb1df
--- /dev/null
@@ -0,0 +1,96 @@
+/****************************************************************************
+ *
+ * Copyright (c) 2014 - 2018 Samsung Electronics Co., Ltd. All rights reserved
+ *
+ ****************************************************************************/
+
+#include <linux/uaccess.h>
+#include <linux/ctype.h>
+#include <scsc/scsc_logring.h>
+#include <scsc/scsc_log_collector.h>
+#include "scsc_log_collector_proc.h"
+
+static struct proc_dir_entry *procfs_dir;
+
+static int log_collect_procfs_open_file_generic(struct inode *inode, struct file *file)
+{
+       file->private_data = LOG_COLLECT_PDE_DATA(inode);
+       return 0;
+}
+
+LOG_COLLECT_PROCFS_RW_FILE_OPS(trigger_collection);
+
+static ssize_t log_collect_procfs_trigger_collection_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos)
+{
+       char         buf[128];
+       int          pos = 0;
+       const size_t bufsz = sizeof(buf);
+
+       /* Avoid unused parameter error */
+       (void)file;
+
+       pos += scnprintf(buf + pos, bufsz - pos, "%s\n", "OK");
+
+       return simple_read_from_buffer(user_buf, count, ppos, buf, pos);
+}
+
+static ssize_t log_collect_procfs_trigger_collection_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
+{
+       char val;
+
+       /* check that only one digit is passed */
+       if (count != 2)
+               pr_err("%s: Incorrect argument length\n", __func__);
+
+       if (copy_from_user(&val, user_buf, 1))
+               return -EFAULT;
+
+       if (val == '1') {
+               pr_info("%s: Userland has triggered log collection\n", __func__);
+               scsc_log_collector_collect(SCSC_LOG_REASON_HOST_PROC_TRIGGERED);
+       } else if (val == '2') {
+               pr_info("%s: Dumpstate/dumpsys has triggered log collection\n", __func__);
+               scsc_log_collector_collect(SCSC_LOG_REASON_DUMPSTATE);
+       } else {
+               pr_err("%s: Incorrect argument\n", __func__);
+       }
+       return count;
+}
+
+static const char *procdir = "driver/scsc_log_collect";
+
+#define LOG_COLLECT_DIRLEN 128
+
+int scsc_log_collect_proc_create(void)
+{
+       char dir[LOG_COLLECT_DIRLEN];
+       struct proc_dir_entry *parent;
+
+       (void)snprintf(dir, sizeof(dir), "%s", procdir);
+       parent = proc_mkdir(dir, NULL);
+       if (parent) {
+#if (LINUX_VERSION_CODE <= KERNEL_VERSION(3, 4, 0))
+               parent->data = NULL;
+#endif
+               procfs_dir = parent;
+
+               LOG_COLLECT_PROCFS_ADD_FILE(NULL, trigger_collection, parent, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+       } else {
+               pr_err("failed to create /proc dir\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+void scsc_log_collect_proc_remove(void)
+{
+       if (procfs_dir) {
+               char dir[LOG_COLLECT_DIRLEN];
+
+               LOG_COLLECT_PROCFS_REMOVE_FILE(trigger_collection, procfs_dir);
+               (void)snprintf(dir, sizeof(dir), "%s", procdir);
+               remove_proc_entry(dir, NULL);
+               procfs_dir = NULL;
+       }
+}
diff --git a/drivers/misc/samsung/scsc/scsc_log_collector_proc.h b/drivers/misc/samsung/scsc/scsc_log_collector_proc.h
new file mode 100644 (file)
index 0000000..22321ed
--- /dev/null
@@ -0,0 +1,77 @@
+/****************************************************************************
+ *
+ * Copyright (c) 2014 - 2018 Samsung Electronics Co., Ltd. All rights reserved
+ *
+ ****************************************************************************/
+
+/*
+ * Chip Manager /proc interface
+ */
+#include <linux/proc_fs.h>
+#include <linux/version.h>
+#include <linux/seq_file.h>
+
+#ifndef SCSC_LOG_COLLECT_PROC_H
+#define SCSC_LOG_COLLECT_PROC_H
+
+#ifndef AID_MX
+#define AID_MX  0444
+#endif
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9))
+#define LOG_COLLECT_PDE_DATA(inode) PDE_DATA(inode)
+#else
+#define LOG_COLLECT_PDE_DATA(inode) (PDE(inode)->data)
+#endif
+
+#define LOG_COLLECT_PROCFS_RW_FILE_OPS(name)                                           \
+       static ssize_t log_collect_procfs_ ## name ## _write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos); \
+       static ssize_t                      log_collect_procfs_ ## name ## _read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos); \
+       static const struct file_operations log_collect_procfs_ ## name ## _fops = { \
+               .read = log_collect_procfs_ ## name ## _read,                        \
+               .write = log_collect_procfs_ ## name ## _write,                      \
+               .open = log_collect_procfs_open_file_generic,                     \
+               .llseek = generic_file_llseek                                 \
+       }
+
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9))
+#define LOG_COLLECT_PROCFS_SET_UID_GID(_entry) \
+       do { \
+               kuid_t proc_kuid = KUIDT_INIT(AID_MX); \
+               kgid_t proc_kgid = KGIDT_INIT(AID_MX); \
+               proc_set_user(_entry, proc_kuid, proc_kgid); \
+       } while (0)
+#else
+#define LOG_COLLECT_PROCFS_SET_UID_GID(entry) \
+       do { \
+               (entry)->uid = AID_MX; \
+               (entry)->gid = AID_MX; \
+       } while (0)
+#endif
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 9))
+#define LOG_COLLECT_PROCFS_ADD_FILE(_sdev, name, parent, mode)                      \
+       do {                                                               \
+               struct proc_dir_entry *entry = proc_create_data(# name, mode, parent, &log_collect_procfs_ ## name ## _fops, _sdev); \
+               LOG_COLLECT_PROCFS_SET_UID_GID(entry);                            \
+       } while (0)
+#else
+#define LOG_COLLECT_PROCFS_ADD_FILE(_sdev, name, parent, mode)                      \
+       do {                                                               \
+               struct proc_dir_entry *entry;                              \
+               entry = create_proc_entry(# name, mode, parent);           \
+               if (entry) {                                               \
+                       entry->proc_fops = &log_collect_procfs_ ## name ## _fops; \
+                       entry->data = _sdev;                               \
+                       LOG_COLLECT_PROCFS_SET_UID_GID(entry);                      \
+               }                                                          \
+       } while (0)
+#endif
+
+#define LOG_COLLECT_PROCFS_REMOVE_FILE(name, parent) remove_proc_entry(# name, parent)
+
+int scsc_log_collect_proc_create(void);
+void scsc_log_collect_proc_remove(void);
+
+#endif /* SCSC_log_collect__PROC_H */
diff --git a/include/scsc/scsc_log_collector.h b/include/scsc/scsc_log_collector.h
new file mode 100644 (file)
index 0000000..0b1e686
--- /dev/null
@@ -0,0 +1,111 @@
+/****************************************************************************
+ *
+ * Copyright (c) 2014 - 2018 Samsung Electronics Co., Ltd. All rights reserved
+ *
+ ****************************************************************************/
+
+#ifndef __SCSC_LOG_COLLECTOR_H__
+#define __SCSC_LOG_COLLECTOR_H__
+
+/* High nibble is Major, Low nibble is Minor */
+#define SCSC_LOG_HEADER_VERSION_MAJOR  0x00
+#define SCSC_LOG_HEADER_VERSION_MINOR  0x00
+/* Magic string. 4 bytes "SCSC"*/
+/* Header version. 1 byte */
+/* Num chunks. 1 byte */
+/* Offset first Chunk. 2 bytes  */
+/* Collection reason. 1 byte */
+/* Reserved. 1 byte */
+#define SCSC_LOG_HEADER_SIZE           (10)
+#define SCSC_LOG_FW_VERSION_SIZE       (64)
+#define SCSC_LOG_HOST_VERSION_SIZE     (64)
+#define SCSC_LOG_FAPI_VERSION_SIZE     (64)
+/* Reserved 2 . 6 byte */
+#define SCSC_LOG_RESERVED_2             6
+/* Ideally header + versions should be 16 bytes aligne*/
+#define SCSC_SUPPORTED_CHUNKS_HEADER    48
+
+#define SCSC_LOG_CHUNK_ALIGN           1
+/* First chunk should be aligned */
+#define SCSC_LOG_OFFSET_FIRST_CHUNK    (((SCSC_LOG_HEADER_SIZE + SCSC_LOG_FW_VERSION_SIZE + \
+                                       SCSC_LOG_HOST_VERSION_SIZE + SCSC_LOG_FAPI_VERSION_SIZE + \
+                                       SCSC_LOG_RESERVED_2 + SCSC_SUPPORTED_CHUNKS_HEADER) + \
+                                       (SCSC_LOG_CHUNK_ALIGN - 1)) & ~(SCSC_LOG_CHUNK_ALIGN - 1))
+enum scsc_log_reason {
+       SCSC_LOG_REASON_UNKNOWN = 0,
+       SCSC_LOG_REASON_FW_PANIC,
+       SCSC_LOG_REASON_HOST_PROC_TRIGGERED,
+       SCSC_LOG_REASON_FW_TRIGGERED,
+       SCSC_LOG_REASON_DUMPSTATE,
+       SCSC_LOG_REASON_WLAN_DISCONNECT,
+       SCSC_LOG_REASON_BT_TRIGGERED,
+       /* Add others */
+};
+
+extern const char *scsc_loc_reason_str[];
+
+#define SCSC_CHUNK_DAT_LEN_SIZE                4
+#define SCSC_CHUNK_TYP_LEN_SIZE                4
+#define SCSC_CHUNK_HEADER_SIZE         (SCSC_CHUNK_DAT_LEN_SIZE + SCSC_CHUNK_TYP_LEN_SIZE)
+
+/* CHUNKS WILL COLLECTED ON THIS ORDER -
+ * SYNC SHOULD BE THE FIRST CHUNK
+ * LOGRING SHOULD BE THE LAST ONE SO IT COULD CAPTURE COLLECTION ERRORS
+ */
+enum scsc_log_chunk_type {
+       SCSC_LOG_CHUNK_SYNC, /* SYNC should be the first chunk to collect */
+       SCSC_LOG_CHUNK_IMP,
+       SCSC_LOG_CHUNK_MXL,
+       SCSC_LOG_CHUNK_UDI,
+       SCSC_LOG_CHUNK_BT_HCF,
+       SCSC_LOG_CHUNK_WLAN_HCF,
+       SCSC_LOG_CHUNK_HIP4_SAMPLER,
+       SCSC_LOG_RESERVED_COMMON,
+       SCSC_LOG_RESERVED_BT,
+       SCSC_LOG_RESERVED_WLAN,
+       SCSC_LOG_RESERVED_RADIO,
+       SCSC_LOG_CHUNK_LOGRING,
+       /* Add other chunks */
+       SCSC_LOG_CHUNK_INVALID = 255,
+};
+
+/* SBL HEADER v 0.0*/
+struct scsc_log_sbl_header {
+       char magic[4];
+       u8   version_major;
+       u8   version_minor;
+       u8   num_chunks;
+       u8   reason;
+       u16  offset_data;
+       char fw_version[SCSC_LOG_FW_VERSION_SIZE];
+       char host_version[SCSC_LOG_HOST_VERSION_SIZE];
+       char fapi_version[SCSC_LOG_FAPI_VERSION_SIZE];
+       u8   reserved2[SCSC_LOG_RESERVED_2];
+       char supported_chunks[SCSC_SUPPORTED_CHUNKS_HEADER];
+} __packed;
+
+struct scsc_log_chunk_header {
+       char magic[3];
+       u8   type;
+       u32  chunk_size;
+} __packed;
+
+struct scsc_log_collector_client {
+       char *name;
+       enum scsc_log_chunk_type type;
+       int (*collect_init)(struct scsc_log_collector_client *collect_client);
+       int (*collect)(struct scsc_log_collector_client *collect_client, size_t size);
+       int (*collect_end)(struct scsc_log_collector_client *collect_client);
+       void *prv;
+};
+
+int scsc_log_collector_register_client(struct scsc_log_collector_client *collect_client);
+int scsc_log_collector_unregister_client(struct scsc_log_collector_client *collect_client);
+
+/* Public method to register FAPI version. */
+void scsc_log_collector_write_fapi(char __user *buf, size_t len);
+
+int scsc_log_collector_collect(enum scsc_log_reason reason);
+void scsc_log_collector_schedule_collection(enum scsc_log_reason reason);
+int scsc_log_collector_write(char __user *buf, size_t count, u8 align);
+#endif /* __SCSC_LOG_COLLECTOR_H__ */