--- /dev/null
+/*
+ * drivers/misc/zlogger.c
+ *
+ * A Logging Subsystem for Tizen TV
+ *
+ * Copyright (C) 2021 Samsung Electronics Co., Ltd
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "zlogger: " fmt
+
+#include <linux/module.h>
+#include <linux/threads.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+#include <linux/mm_types.h>
+#include <linux/mm.h>
+#include <linux/ptrace.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/fs.h>
+#include <linux/moduleparam.h>
+#include <linux/trace_clock.h>
+#include <linux/time.h>
+#include <linux/completion.h>
+#include <linux/version.h>
+#include <linux/hashtable.h>
+#include <linux/compat.h>
+#include <uapi/linux/zlogger.h>
+
+#define MB (1 << 20)
+#define KB (1 << 10)
+
+#define DEVICE_COUNT (8)
+#define MAP_SIZE (4 * MB)
+#define BUFFER_SIZE (DEVICE_COUNT * MAP_SIZE)
+#define V_BLOCK_SIZE (2 * KB)
+#define BLOCK_COUNT (BUFFER_SIZE / V_BLOCK_SIZE)
+#define DATA_MAX (V_BLOCK_SIZE - sizeof(struct head_t))
+#define MS_PER_SEC (1000)
+#define NS_PER_SEC (1000000000UL)
+
+#define ZLOGGER_DEVICE_NAME "zlogger"
+#define ZLOGGER_SMACK_LABEL "*"
+
+#define MAX_TAG_SIZE (32)
+#define MAX_MSG_SIZE (140)
+
+#define BLOCK_RATIO(count) (count*100/BLOCK_COUNT)
+
+#define ZLOG_FD_BUFER (2 * MB)
+
+struct thread_t {
+ uint16_t block;
+};
+
+struct entry_t {
+ uint64_t time;
+ uint16_t CRC;
+ uint16_t len;
+ char data[0];
+};
+
+struct head_t {
+ uint64_t ts;
+ pid_t pid;
+ pid_t tid;
+ uint16_t offset;
+};
+
+struct block_t {
+ struct head_t head;
+ char data[DATA_MAX];
+};
+
+struct queue_t {
+ char name[5];
+ uint16_t front;
+ uint16_t rear;
+ uint16_t count;
+ uint16_t capacity;
+ uint16_t *values;
+};
+
+struct thread_table_field {
+ pid_t tid;
+ uint16_t blk;
+ bool is_stdout;
+ struct hlist_node next;
+};
+
+struct thread_table {
+ DECLARE_HASHTABLE(data, 16);
+};
+
+// taken from dlog.h
+typedef enum {
+ DLOG_UNKNOWN = 0, /**< Keep this always at the start */
+ DLOG_DEFAULT, /**< Default */
+ DLOG_VERBOSE, /**< Verbose */
+ DLOG_DEBUG, /**< Debug */
+ DLOG_INFO, /**< Info */
+ DLOG_WARN, /**< Warning */
+ DLOG_ERROR, /**< Error */
+ DLOG_FATAL, /**< Fatal */
+ DLOG_SILENT, /**< Silent */
+ DLOG_PRIO_MAX /**< Keep this always at the end. */
+} log_priority;
+
+/*zlogger file channel */
+
+#define LOG_BUFFER_SIZE 512
+const char default_tag[] = "STDOUT";
+typedef struct {
+ char prio;
+ char tag[MAX_TAG_SIZE];
+ char msg[MAX_MSG_SIZE];
+ char *buffer;
+ size_t buffer_len;
+} zlog_file_t;
+
+/*--zlogger file channel*/
+
+static struct miscdevice zlogger_device;
+
+static int g_init;
+static char *g_shm_ptr[DEVICE_COUNT];
+static struct thread_table *g_thread_table;
+
+static struct mutex g_block_mutex;
+static struct mutex g_task_mutex;
+
+static struct queue_t g_free_q;
+
+static int g_max_thread_id;
+
+static uint64_t g_start_time;
+static uint64_t g_hwc_offset;
+
+static int g_task_on;
+static uint32_t g_free_count;
+
+static uint32_t g_err_count;
+
+static struct completion g_completion;
+
+static int g_zlog_enable = 1;
+module_param_named(zlog_enable, g_zlog_enable, int, 0644);
+
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,17,0))
+static inline u64 ktime_get_ns(void)
+{
+ return ktime_to_ns(ktime_get());
+}
+#endif
+
+static uint16_t get_thread_table(pid_t tid, bool is_stdout)
+{
+ struct thread_table_field *ptr = NULL;
+
+ // TODO: Is this needed?
+ if (g_thread_table == NULL)
+ return 0;
+
+ hash_for_each_possible(g_thread_table->data, ptr, next, tid) {
+ if (ptr->tid == tid && ptr->is_stdout == is_stdout) {
+ return ptr->blk;
+ }
+ }
+
+ return 0;
+}
+
+static void set_thread_table(pid_t tid, bool is_stdout, uint16_t blk)
+{
+ struct thread_table_field *ptr = NULL;
+
+ // TODO: Is this needed?
+ if (g_thread_table == NULL)
+ return;
+
+ hash_for_each_possible(g_thread_table->data, ptr, next, tid) {
+ if (ptr->tid == tid && ptr->is_stdout == is_stdout) {
+ ptr->blk = blk;
+ return;
+ }
+ }
+
+ ptr = kzalloc(sizeof(*ptr), GFP_KERNEL);
+ ptr->tid = tid;
+ ptr->is_stdout = is_stdout;
+ ptr->blk = blk;
+ hash_add(g_thread_table->data, &ptr->next, tid);
+}
+
+static inline char *get_shared_memory(int dev_index)
+{
+ if (dev_index < 0 || dev_index >= DEVICE_COUNT) {
+ pr_debug("Invalid index: %d\n", dev_index);
+ return NULL;
+ }
+
+ return g_shm_ptr[dev_index];
+}
+
+static inline struct block_t *get_block(uint16_t block_index)
+{
+ uint16_t index = block_index - 1;
+ int offset = index & (V_BLOCK_SIZE - 1);
+ char *p = get_shared_memory(index / V_BLOCK_SIZE);
+
+ if (!p)
+ return NULL;
+
+ return (struct block_t *)(p + (offset * V_BLOCK_SIZE));
+}
+
+static inline void queue_init(struct queue_t *q, const char *name, uint16_t capacity)
+{
+ snprintf(q->name, sizeof(q->name), "%s", name);
+ q->front = 0;
+ q->rear = capacity - 1;
+ q->count = 0;
+ q->capacity = capacity;
+ q->values = kzalloc(capacity * sizeof(uint16_t), GFP_KERNEL);
+}
+
+static inline void queue_deinit(struct queue_t *q)
+{
+ if (q->values)
+ kfree(q->values);
+}
+
+static inline uint16_t queue_pop(struct queue_t *q)
+{
+ uint16_t r;
+
+ if (q->count == 0)
+ return 0;
+
+ r = q->values[q->front++];
+ if (q->front == q->capacity)
+ q->front = 0;
+ q->count--;
+
+ return r;
+}
+
+static inline void queue_push(struct queue_t *q, uint16_t value)
+{
+ if (q->count >= q->capacity) {
+ pr_info("[%s] Queue is full", q->name);
+ return;
+ }
+
+ if (value == 0) {
+ pr_info("[%s] NULL is invalid", q->name);
+ return;
+ }
+
+ q->rear++;
+ if (q->rear == q->capacity)
+ q->rear = 0;
+ q->values[q->rear] = value;
+ q->count++;
+}
+
+static int zlog_task(void *user_data)
+{
+ int i;
+ int is_stdout;
+ uint16_t blk;
+
+ do {
+ // TODO: Consider hash_for_each_safe
+ for (i = 1; i <= g_max_thread_id; i++) {
+ for (is_stdout = 0; is_stdout <= 1; ++is_stdout) {
+ blk = get_thread_table(i, is_stdout);
+ // TODO: g_start_time should be under some kind of mutex.
+ if (blk && get_block(blk)->head.ts < g_start_time) {
+ mutex_lock(&g_block_mutex);
+ get_block(blk)->head.tid = 0;
+ queue_push(&g_free_q, blk);
+ set_thread_table(i, is_stdout, 0);
+ // TODO: The userspace might very well be using this block right now.
+ mutex_unlock(&g_block_mutex);
+ g_free_count++;
+ }
+ }
+ }
+
+ // TODO: This makes no sense.
+ wait_for_completion_interruptible_timeout(&g_completion, msecs_to_jiffies(MS_PER_SEC * 5));
+ } while (1);
+
+ g_task_on = 0;
+ do_exit(0);
+
+ return 0;
+}
+
+static void run_task(void)
+{
+ struct task_struct *task;
+
+ if (!mutex_trylock(&g_task_mutex))
+ return;
+
+ if (g_task_on) {
+ // TODO: this mutex is either incorrect or nonsensical,
+ // since the other task will not end (it is an infinite loop after all).
+ mutex_unlock(&g_task_mutex);
+ complete(&g_completion);
+ return;
+ }
+ g_task_on = 1;
+ mutex_unlock(&g_task_mutex);
+
+ task = kthread_run(zlog_task, NULL, "zlog_task");
+ if (IS_ERR(task)) {
+ pr_err("Failed to run zlog_task\n");
+ g_task_on = 0;
+ }
+}
+
+static long alloc_block_for_thread(bool is_stdout)
+{
+ pid_t pid = current->tgid;
+ pid_t tid = current->pid;
+ uint16_t blk;
+ struct block_t *block;
+
+ if (g_max_thread_id < tid)
+ g_max_thread_id = tid;
+
+ mutex_lock(&g_block_mutex);
+ blk = get_thread_table(tid, is_stdout);
+ if (blk)
+ queue_push(&g_free_q, blk);
+ blk = queue_pop(&g_free_q);
+ set_thread_table(tid, is_stdout, blk);
+
+ if (!blk) {
+ if ((g_err_count++ % 10000) < 3)
+ pr_info("[NO MEMORY] tid:%d free:%d err:%d", tid, g_free_q.count, g_err_count);
+ mutex_unlock(&g_block_mutex);
+ return -ENOMEM;
+ }
+
+ block = get_block(blk);
+
+ // TODO: Needs documentation on how the g_start_time value behaves.
+ if (g_start_time < block->head.ts)
+ g_start_time = block->head.ts;
+
+ block->head.pid = pid;
+ block->head.tid = tid;
+ block->head.offset = 0;
+ block->head.ts = g_start_time;
+ mutex_unlock(&g_block_mutex);
+
+ return (long)blk;
+}
+
+static inline struct block_t *get_valid_block(int tid, size_t len, bool is_stdout)
+{
+ uint16_t blk = 0;
+ long r;
+
+ blk = get_thread_table(tid, is_stdout);
+
+ if (blk != 0) {
+ struct block_t *block = get_block(blk);
+
+ if (!block)
+ return NULL;
+
+ if (block->head.offset + len < DATA_MAX)
+ return block;
+ }
+
+ r = alloc_block_for_thread(is_stdout);
+ if (r <= 0)
+ return NULL;
+
+ return get_block((uint16_t)r);
+}
+
+static int zlogger_open(struct inode *inode, struct file *file)
+{
+ int ret = nonseekable_open(inode, file);
+ if (ret)
+ return ret;
+ file->private_data = NULL;
+ return 0;
+}
+
+static int zlogger_release(struct inode *ignored, struct file *file)
+{
+ if (file->private_data != NULL) {
+ zlog_file_t *zlog_file_data = (zlog_file_t *)file->private_data;
+
+ if (zlog_file_data->buffer != NULL) {
+ kfree(zlog_file_data->buffer);
+ zlog_file_data->buffer = NULL;
+ }
+ kfree(file->private_data);
+ file->private_data = NULL;
+ }
+ return 0;
+}
+
+static int zlogger_mmap(struct file *filep, struct vm_area_struct *vma)
+{
+ const int PAGES_PER_MAP = MAP_SIZE / PAGE_SIZE;
+ int dev_index = (int)vma->vm_pgoff / PAGES_PER_MAP;
+ unsigned long offset = vma->vm_pgoff % PAGES_PER_MAP;
+ unsigned long size = vma->vm_end - vma->vm_start;
+ char *p;
+ struct page *page;
+
+ if (dev_index > DEVICE_COUNT || offset != 0 || size > MAP_SIZE) {
+ pr_err("mmap failed: dev(%d) offset(%lu), size(%lu), pgoff(%lu)\n", dev_index, offset, size, vma->vm_pgoff);
+ return -EINVAL;
+ }
+
+ p = get_shared_memory(dev_index);
+ if (p)
+ page = virt_to_page((unsigned long)p);
+ else
+ return -EINVAL;
+
+ return remap_pfn_range(vma, vma->vm_start, page_to_pfn(page), size, vma->vm_page_prot);
+}
+
+static ssize_t zlogger_read(struct file *filep, char *buffer, size_t len, loff_t *offset)
+{
+ pr_err("read failed!\n");
+ return -EPERM;
+}
+
+static ssize_t init_file_zlog_tag_data(struct file *filep)
+{
+ zlog_file_t *zlog_file_data = kmalloc(sizeof(zlog_file_t), GFP_KERNEL);
+ if (!zlog_file_data) {
+ pr_err("init_zlog_tag_data: no memory \n");
+ return -ENOMEM;
+ }
+ zlog_file_data->buffer = NULL;
+ memcpy(zlog_file_data->tag, default_tag, sizeof(default_tag) - 1);
+ zlog_file_data->tag[sizeof(default_tag) - 1] = '\0';
+ zlog_file_data->prio = (char)DLOG_INFO;
+ zlog_file_data->buffer = kmalloc(LOG_BUFFER_SIZE, GFP_KERNEL);
+
+ if (!zlog_file_data->buffer) {
+ kfree(zlog_file_data);
+ return -ENOMEM;
+ }
+ zlog_file_data->buffer_len = LOG_BUFFER_SIZE;
+ filep->private_data = (void *)zlog_file_data;
+ return 0;
+}
+
+int update_zlog_data_buffer_size(zlog_file_t *zlog_file_data, size_t len)
+{
+ char *new_buffer = kmalloc(len, GFP_KERNEL);
+ if (!new_buffer)
+ return -ENOMEM;
+ kfree(zlog_file_data->buffer);
+ zlog_file_data->buffer = new_buffer;
+ zlog_file_data->buffer_len = len;
+ return 0;
+}
+
+static int _zlog_write(const unsigned char prio, const char *tag, const char *msg)
+{
+ uint64_t ts = ktime_get_ns();
+ uint16_t *slt = (uint16_t *)&ts;
+ const pid_t tid = current->pid;
+ size_t hd_size = sizeof(struct entry_t);
+ size_t prio_size = 1;
+ size_t tag_size = strnlen(tag, MAX_TAG_SIZE) + 1;
+ size_t msg_size = strnlen(msg, MAX_MSG_SIZE) + 1;
+ size_t entry_size = hd_size + prio_size + tag_size + msg_size;
+ struct block_t *block = get_valid_block(tid, entry_size, true);
+ struct entry_t *entry;
+ struct entry_t tmp;
+
+ if (block == NULL) {
+ pr_err("_zlog_write: no memory\n");
+ return -ENOMEM;
+ }
+
+ entry = (struct entry_t *)(block->data + block->head.offset);
+
+ if ((char *)entry + entry_size > (char *)block + V_BLOCK_SIZE) {
+ // TODO: This is not unexpected: the offset can change at any time,
+ // as it is userspace writable.
+ pr_err("[%d] block:%p(tid:%d offset:%x) entry:%p(%zu)\n",
+ tid, block, block->head.tid, block->head.offset, entry, entry_size);
+ return -EFAULT;
+ }
+
+ tmp.time = ts;
+ tmp.CRC = slt[0] + slt[1] + slt[2] + slt[3];
+ tmp.len = (uint16_t)entry_size - hd_size;
+
+ memcpy(entry, &tmp, hd_size);
+ entry->data[0] = prio;
+ memcpy(&entry->data[prio_size], tag, tag_size-1);
+ entry->data[prio_size+tag_size-1] = 0;
+ memcpy(&entry->data[prio_size+tag_size], msg, msg_size-1);
+ entry->data[prio_size+tag_size+msg_size-1] = 0;
+ block->head.offset += (uint16_t)entry_size;
+ block->head.ts = ts;
+
+ return (int)entry_size;
+}
+
+static void endl_to_zero(zlog_file_t *zlog_file_data, size_t len)
+{
+ char *cb = (char*)zlog_file_data->buffer;
+ while(cb < (char*)zlog_file_data->buffer + len) {
+ if(*cb == '\n') {
+ *cb = '\0';
+ }
+ cb++;
+ }
+}
+
+static ssize_t partition_write_buffer(zlog_file_t *zlog_file_data, size_t len)
+{
+ char *cb = (char*)zlog_file_data->buffer;
+ char *buffer = (char*)zlog_file_data->buffer;
+
+ endl_to_zero(zlog_file_data, len);
+
+ while(cb < buffer + len ) {
+
+ if(*cb == '\0' && cb < buffer + len ) {
+ cb++;
+ continue;
+ }
+
+ int res = _zlog_write((const unsigned char)zlog_file_data->prio, (const char *)&zlog_file_data->tag, (const char *)cb);
+ if(res < 0) {
+ pr_err("_zlog_write failed\n");
+ return -EFAULT;
+ }
+
+ if(cb < buffer + len) {
+ cb += strnlen(cb, len - (cb - buffer));
+ }
+ }
+ return 0;
+}
+
+static ssize_t zlogger_write(struct file *filep, const char *buffer, size_t len, loff_t *offset)
+{
+ if( (!filep->private_data ) && init_file_zlog_tag_data(filep)) {
+ pr_err("zlogger_write init zlog tag data failed\n");
+ return -ENOMEM;
+ }
+
+ zlog_file_t *zlog_file_data = (zlog_file_t *)filep->private_data;
+
+ if (len > zlog_file_data->buffer_len && !update_zlog_data_buffer_size(zlog_file_data, len)) {
+ pr_err("update_zlog_data_buffer_size failed\n");
+ return -ENOMEM;
+ }
+ unsigned long copied = copy_from_user(zlog_file_data->buffer, buffer, len);
+
+ if(copied != 0) {
+ pr_err("copy_from_user failed\n");
+ return -EFAULT;
+ }
+
+ ssize_t res = partition_write_buffer(zlog_file_data, len);
+ if(res < 0) {
+ pr_err("partition_write_buffer failed\n");
+ return -EFAULT;
+ }
+ return len;
+}
+
+static long update_prio(zlog_file_t *zlog_file_data, void __user *argp)
+{
+ char prio;
+
+ prio = (char)(uintptr_t)argp;
+
+ if (prio < (char)DLOG_DEFAULT || prio >=DLOG_PRIO_MAX) {
+ return -EINVAL;
+ }
+ zlog_file_data->prio = prio;
+
+ return 0;
+}
+
+static long zlogger_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ if (cmd == IOCTL_COMMAND_ALLOC)
+ return alloc_block_for_thread(false);
+
+ #ifdef CONFIG_COMPAT
+ void __user *argp = compat_ptr(arg);
+ #else
+ void __user *argp = (void __user *)arg;
+ #endif
+
+ if (cmd == SET_DEFAULT_PRIORITY) {
+ if( (!file->private_data ) && init_file_zlog_tag_data(file)) {
+ pr_err("zlogger_write init zlog tag data failed\n");
+ return -ENOMEM;
+ }
+
+ zlog_file_t *zlog_file_data = (zlog_file_t *)file->private_data;
+
+ return update_prio(zlog_file_data, argp);
+ }
+
+ if(cmd == SET_DEFAULT_TAG) {
+ if( (!file->private_data ) && init_file_zlog_tag_data(file)) {
+ pr_err("zlogger_write init zlog tag data failed\n");
+ return -ENOMEM;
+ }
+
+ zlog_file_t *zlog_file_data = (zlog_file_t *)file->private_data;
+
+ return copy_from_user(&zlog_file_data->tag, (char *)(uintptr_t)argp, strnlen((char *)(uintptr_t)argp, MAX_TAG_SIZE));
+ }
+
+ return -EINVAL;
+}
+
+static const struct file_operations zlogger_fops = {
+ .open = zlogger_open,
+ .read = zlogger_read,
+ .write = zlogger_write,
+ .release = zlogger_release,
+ .mmap = zlogger_mmap,
+ .unlocked_ioctl = zlogger_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = zlogger_ioctl,
+#endif
+ .owner = THIS_MODULE,
+};
+
+static ssize_t status_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int thread_count = 0;
+ int i;
+ int is_stdout;
+
+ for (i = 0; i < g_max_thread_id; i++) {
+ for (is_stdout = 0; is_stdout <= 1; ++is_stdout) {
+ if (get_thread_table(i, is_stdout) > 0)
+ thread_count ++;
+ }
+ }
+
+ return snprintf(buf, PAGE_SIZE,
+ "free(%d%%):%d/%d task_on:%d gc_free:%u error:%u tid_max:%d\n",
+ BLOCK_RATIO(g_free_q.count), g_free_q.count, (g_free_q.count + thread_count),
+ g_task_on, g_free_count, g_err_count, g_max_thread_id);
+}
+
+static DEVICE_ATTR_RO(status);
+
+static ssize_t time_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE,
+ "%llu %llu %llu\n", g_start_time, ktime_get_ns(), g_hwc_offset);
+}
+
+static DEVICE_ATTR_RO(time);
+
+static uint16_t g_block = 1;
+
+static ssize_t block_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ unsigned long value;
+
+ if (kstrtoul(buf, 10, &value) < 0) {
+ pr_err("Failed to get value");
+ return -EINVAL;
+ }
+
+ if (value < 1 || value > BLOCK_COUNT)
+ return -EINVAL;
+
+ g_block = value;
+
+ return count;
+}
+
+static ssize_t block_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct block_t *block = get_block((uint16_t)g_block);
+
+ if (!block)
+ return snprintf(buf, PAGE_SIZE, "[%d] Invalid block\n", g_block);
+
+ return snprintf(buf, PAGE_SIZE, "[%d] pid:%u tid:%u offset:%u %llu %llu\n",
+ g_block, block->head.pid, block->head.tid, block->head.offset,
+ ((struct entry_t *)(block->data))->time, block->head.ts);
+}
+
+static DEVICE_ATTR_RW(block);
+
+static uint16_t g_thread = 1;
+static ssize_t thread_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ long value;
+
+ if (kstrtol(buf, 10, &value) < 0) {
+ pr_err("Failed to get value");
+ return -EINVAL;
+ }
+
+ if (value < 1 || value > g_max_thread_id)
+ return -EINVAL;
+
+ g_thread = value;
+
+ return count;
+}
+
+static ssize_t thread_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "[%d] block: %u, stdout block: %u\n",
+ g_thread, get_thread_table(g_thread, false), get_thread_table(g_thread, true));
+}
+
+static DEVICE_ATTR_RW(thread);
+
+static struct attribute *zlogger_attributes[] = {
+ &dev_attr_status.attr,
+ &dev_attr_time.attr,
+ &dev_attr_block.attr,
+ &dev_attr_thread.attr,
+ NULL,
+};
+
+static const struct attribute_group zlogger_attr_group = {
+ .attrs = zlogger_attributes,
+};
+
+int __init zlogger_init(void)
+{
+ int i = 0;
+ int g_shm_ptr_i = 0;
+ int r = 0;
+
+ if (!g_zlog_enable) {
+ pr_info("zlog is disable\n");
+ return 0;
+ }
+
+ zlogger_device.minor = MISC_DYNAMIC_MINOR;
+ zlogger_device.name = ZLOGGER_DEVICE_NAME;
+ zlogger_device.fops = &zlogger_fops;
+ zlogger_device.mode = 0666;
+#ifdef CONFIG_SECURITY_SMACK_SET_DEV_SMK_LABEL
+ zlogger_device.lab_smk64 = ZLOGGER_SMACK_LABEL;
+#endif
+ r = misc_register(&zlogger_device);
+ if (unlikely(r)) {
+ pr_err("Failed to register misc device for '%s' (%d)\n", ZLOGGER_DEVICE_NAME, r);
+ return r;
+ }
+
+ r = sysfs_create_group(&zlogger_device.this_device->kobj, &zlogger_attr_group);
+ if (unlikely(r)) {
+ dev_err(zlogger_device.this_device, "failed to create sysfs nodes with (%d)\n", r);
+ return r;
+ }
+
+ g_thread_table = kzalloc(sizeof(struct thread_table), GFP_KERNEL);
+ if (g_thread_table == NULL)
+ return -ENOMEM;
+ hash_init(g_thread_table->data);
+
+ for (g_shm_ptr_i = 0; g_shm_ptr_i < DEVICE_COUNT; g_shm_ptr_i++) {
+ g_shm_ptr[g_shm_ptr_i] = kzalloc(MAP_SIZE, GFP_KERNEL);
+ if (g_shm_ptr[g_shm_ptr_i] == NULL) {
+ r = -ENOMEM;
+ goto out_free_g_thread_table_g_shm_ptr;
+ }
+ }
+
+ mutex_init(&g_block_mutex);
+ mutex_init(&g_task_mutex);
+
+ init_completion(&g_completion);
+
+ queue_init(&g_free_q, "free", BLOCK_COUNT);
+ for (i = 1; i <= BLOCK_COUNT; i++)
+ queue_push(&g_free_q, i);
+
+#ifdef CONFIG_TRACE_CLOCK
+ g_hwc_offset = trace_clock_local() - ktime_get_ns();
+#endif
+ run_task();
+
+ g_init = 1;
+ pr_info("Init success\n");
+
+ return 0;
+
+out_free_g_thread_table_g_shm_ptr:
+ for (i = 0; i < g_shm_ptr_i; ++i){
+ kfree(g_shm_ptr[i]);
+ g_shm_ptr[i] = NULL;
+ }
+
+ kfree(g_thread_table);
+ g_thread_table = NULL;
+
+ return r;
+}
+
+static void __exit zlogger_exit(void)
+{
+ int i;
+ struct thread_table_field *ptr = NULL;
+ struct hlist_node *tmp_iter = NULL;
+ int tmp_bkt;
+
+ // TODO: What about the task that is running in the background?
+
+ queue_deinit(&g_free_q);
+ hash_for_each_safe(g_thread_table->data, tmp_bkt, tmp_iter, ptr, next) {
+ kfree(ptr);
+ }
+
+ kfree(g_thread_table);
+ g_thread_table = NULL;
+
+ for (i = 0; i < DEVICE_COUNT; i++) {
+ kfree(g_shm_ptr[i]);
+ g_shm_ptr[i] = NULL;
+ }
+
+
+
+ g_init = 0;
+
+ sysfs_remove_group(&zlogger_device.this_device->kobj, &zlogger_attr_group);
+ misc_deregister(&zlogger_device);
+}
+
+module_init(zlogger_init);
+module_exit(zlogger_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("jh1009.sung <jh1009.sung@samsung.com>, Arkadiusz Nowak <a.nowak3@samsung.com>, Mateusz Majewski <m.majewski2@samsung.com");
+MODULE_DESCRIPTION("Tizen zero copy logger");
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <linux/zlogger.h>
+
+// taken from dlog.h
+typedef enum {
+ DLOG_UNKNOWN = 0, /**< Keep this always at the start */
+ DLOG_DEFAULT, /**< Default */
+ DLOG_VERBOSE, /**< Verbose */
+ DLOG_DEBUG, /**< Debug */
+ DLOG_INFO, /**< Info */
+ DLOG_WARN, /**< Warning */
+ DLOG_ERROR, /**< Error */
+ DLOG_FATAL, /**< Fatal */
+ DLOG_SILENT, /**< Silent */
+ DLOG_PRIO_MAX /**< Keep this always at the end. */
+} log_priority;
+
+static const char* prio_default = "Prio: DEFAULT\n";
+static const char* prio_verbose = "Prio: VERBOSE\n";
+static const char* prio_debug = "Prio: DEBUG\n";
+static const char* prio_info = "Prio: INFO\n";
+static const char* prio_warn = "Prio: WARN\n";
+static const char* prio_error = "Prio: ERROR\n";
+static const char* prio_fatal = "Prio: FATAL\n";
+static const char* prio_silent = "Prio: SILENT\n";
+
+static const char* test_message = "A test message\n";
+
+int main(int argc, char *argv[])
+{
+ printf("Simple zlog test!\n");
+ printf("see at zlogutil messages\n");
+ int fd = open("/dev/zlogger", O_RDWR);
+ if (fd < 0) {
+ printf("Error opening zlog device\n");
+ return -1;
+ }
+
+ ioctl(fd, SET_DEFAULT_PRIORITY, (char)DLOG_DEFAULT);
+ write(fd, prio_default, strlen(prio_default));
+
+ ioctl(fd, SET_DEFAULT_PRIORITY, (char)DLOG_VERBOSE);
+ write(fd, prio_verbose, strlen(prio_verbose));
+
+ ioctl(fd, SET_DEFAULT_PRIORITY, (char)DLOG_DEBUG);
+ write(fd, prio_debug, strlen(prio_debug));
+
+ ioctl(fd, SET_DEFAULT_PRIORITY, (char)DLOG_INFO);
+ write(fd, prio_info, strlen(prio_info));
+
+ ioctl(fd, SET_DEFAULT_PRIORITY, (char)DLOG_WARN);
+ write(fd, prio_warn, strlen(prio_warn));
+
+ ioctl(fd, SET_DEFAULT_PRIORITY, (char)DLOG_ERROR);
+ write(fd, prio_error, strlen(prio_error));
+
+ ioctl(fd, SET_DEFAULT_PRIORITY, (char)DLOG_FATAL);
+ write(fd, prio_fatal, strlen(prio_fatal));
+
+ ioctl(fd, SET_DEFAULT_PRIORITY, (char)DLOG_SILENT);
+ write(fd, prio_silent, strlen(prio_silent));
+
+ ioctl(fd, SET_DEFAULT_TAG, "TESTPROGRAM_1");
+ write(fd, test_message, strlen(test_message));
+
+ ioctl(fd, SET_DEFAULT_PRIORITY, (char)DLOG_DEFAULT);
+ write(fd, prio_default, strlen(prio_default));
+
+ ioctl(fd, SET_DEFAULT_PRIORITY, (char)DLOG_VERBOSE);
+ write(fd, prio_verbose, strlen(prio_verbose));
+
+ ioctl(fd, SET_DEFAULT_PRIORITY, (char)DLOG_DEBUG);
+ write(fd, prio_debug, strlen(prio_debug));
+
+ ioctl(fd, SET_DEFAULT_PRIORITY, (char)DLOG_INFO);
+ write(fd, prio_info, strlen(prio_info));
+
+ ioctl(fd, SET_DEFAULT_PRIORITY, (char)DLOG_WARN);
+ write(fd, prio_warn, strlen(prio_warn));
+
+ ioctl(fd, SET_DEFAULT_PRIORITY, (char)DLOG_ERROR);
+ write(fd, prio_error, strlen(prio_error));
+
+ ioctl(fd, SET_DEFAULT_PRIORITY, (char)DLOG_FATAL);
+ write(fd, prio_fatal, strlen(prio_fatal));
+
+ ioctl(fd, SET_DEFAULT_PRIORITY, (char)DLOG_SILENT);
+ write(fd, prio_silent, strlen(prio_silent));
+
+ ioctl(fd, SET_DEFAULT_TAG, "TESTPROGRAM_2");
+ write(fd, test_message, strlen(test_message));
+ return 0;
+}