--- /dev/null
+/********************************************************************************
+ *
+ * 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");
--- /dev/null
+/****************************************************************************
+ *
+ * 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;
+ }
+}
--- /dev/null
+/****************************************************************************
+ *
+ * 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 */
--- /dev/null
+/****************************************************************************
+ *
+ * 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__ */