Include zero-copy logging kernel module 56/279256/16
authorArkadiusz Nowak <a.nowak3@samsung.com>
Tue, 21 Jun 2022 11:14:50 +0000 (13:14 +0200)
committerArkadiusz Nowak <a.nowak3@samsung.com>
Fri, 26 Aug 2022 12:32:26 +0000 (14:32 +0200)
This is basically borrowed from the Tizen TV, where the kernel module
was called vlog. Hence the attribution in the MODULE_AUTHOR field. In
our version, the module is called zlogger instead. There is also a
number of changes from the original:

- we support direct writing to the module (similar to the stdout
  redirection support in the logger module), which includes the write
  operation, various ioctl commands and the test vlog_stdout program,
- the thread block table (g_threads) works slightly differently. It is
  no longer a table, but a hashtable. This also removes a limitation on
  the max TID (previously such a limitation has been needed, as the
  table was indexed using the TID),
- the thread block table is no longer mmapable. Instead, the userspace
  is supposed to cache the received block number using thread-local
  storage, and check if it still has the block by verifying the TID
  field in the block header, which is reset when the block is lost.

Change-Id: Ia04f1add0a4904bfd92b156022c704bc62495931

include/uapi/linux/zlogger.h [new file with mode: 0644]
kernel/Makefile
kernel/zlogger/Makefile [new file with mode: 0644]
kernel/zlogger/zlogger.c [new file with mode: 0644]
packaging/linux-tizen-modules-source.spec
tests/zlog_stdout/Makefile [new file with mode: 0644]
tests/zlog_stdout/zlog_stdout.c [new file with mode: 0644]

diff --git a/include/uapi/linux/zlogger.h b/include/uapi/linux/zlogger.h
new file mode 100644 (file)
index 0000000..9cd11c5
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef _ZLOGGER_H
+#define _ZLOGGER_H
+
+#define IOCTL_COMMAND_ALLOC (20745321)
+#define SET_DEFAULT_PRIORITY _IOW('a','a', char*)
+#define SET_DEFAULT_TAG _IOW('a','b', char*)
+
+#endif
index a4a8d16..d3d3a64 100644 (file)
@@ -20,10 +20,12 @@ CFLAGS_proc-tsm.o += -Wno-error=missing-attributes
 BUILD_logger ?= n
 BUILD_proc_tsm ?= n
 BUILD_kdbus ?= n
+BUILD_zlogger ?= n
 
 obj-$(BUILD_logger) += logger.o
 obj-$(BUILD_proc_tsm) += proc-tsm.o
 obj-$(BUILD_kdbus) += kdbus/
+obj-$(BUILD_zlogger) += zlogger/
 
 all:
        make -C $(KERNELDIR) M=$(PWD) CFLAGS_MODULE=-I$(PWD)/include modules
diff --git a/kernel/zlogger/Makefile b/kernel/zlogger/Makefile
new file mode 100644 (file)
index 0000000..3261066
--- /dev/null
@@ -0,0 +1,2 @@
+ccflags-y += -std=c11 -std=gnu11 -Wno-error=missing-attributes  -Wno-error=declaration-after-statement
+obj-m := zlogger.o
diff --git a/kernel/zlogger/zlogger.c b/kernel/zlogger/zlogger.c
new file mode 100644 (file)
index 0000000..2ccc100
--- /dev/null
@@ -0,0 +1,875 @@
+/*
+ * 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");
index b7e4cb2..b5aa1e4 100644 (file)
@@ -35,21 +35,26 @@ cp %{SOURCE1} .
 %build
 make -C tests/kdbus
 make -C tests/logger
+make -C tests/zlog_stdout
 
 %install
 mkdir -p %{buildroot}/usr/src/%{name}/kdbus
+mkdir -p %{buildroot}/usr/src/%{name}/zlogger
 mkdir -p %{buildroot}/%{_includedir}/linux
 mkdir -p %{buildroot}/%{_libexecdir}/%{name}/tests
 
 cp -a include/ %{buildroot}/usr/src/%{name}
 cp kernel/*.[ch] kernel/Makefile COPYING %{buildroot}/usr/src/%{name}
 cp kernel/kdbus/*.[ch] kernel/kdbus/Makefile %{buildroot}/usr/src/%{name}/kdbus
+cp kernel/zlogger/*.c kernel/zlogger/Makefile %{buildroot}/usr/src/%{name}/zlogger
 
 cp include/uapi/linux/kdbus.h %{buildroot}/%{_includedir}/linux
 cp include/uapi/linux/logger.h %{buildroot}/%{_includedir}/linux
+cp include/uapi/linux/zlogger.h %{buildroot}/%{_includedir}/linux
 
 cp tests/logger/logger-test %{buildroot}/%{_libexecdir}/%{name}/tests/logger-test
 cp tests/kdbus/kdbus-test %{buildroot}/%{_libexecdir}/%{name}/tests/kdbus-test
+cp tests/zlog_stdout/zlog_stdout %{buildroot}/%{_libexecdir}/%{name}/tests/zlog_stdout
 
 %files
 %manifest %{name}.manifest
@@ -62,14 +67,19 @@ cp tests/kdbus/kdbus-test %{buildroot}/%{_libexecdir}/%{name}/tests/kdbus-test
 /usr/src/%{name}/kdbus/Makefile
 /usr/src/%{name}/include/uapi/linux/*.h
 
+/usr/src/%{name}/zlogger/*.c
+/usr/src/%{name}/zlogger/Makefile
+
 %files -n linux-tizen-modules-headers
 %manifest %{name}.manifest
 %license COPYING
 %{_includedir}/linux/kdbus.h
 %{_includedir}/linux/logger.h
+%{_includedir}/linux/zlogger.h
 
 %files -n linux-tizen-modules-tests
 %manifest %{name}.manifest
 %license COPYING
 %{_libexecdir}/%{name}/tests/logger-test
 %{_libexecdir}/%{name}/tests/kdbus-test
+%{_libexecdir}/%{name}/tests/zlog_stdout
diff --git a/tests/zlog_stdout/Makefile b/tests/zlog_stdout/Makefile
new file mode 100644 (file)
index 0000000..fc8419a
--- /dev/null
@@ -0,0 +1,25 @@
+CC := $(CROSS_COMPILE)gcc
+CFLAGS += -I../../include/uapi
+CFLAGS += -std=gnu11
+CFLAGS += -D_GNU_SOURCE
+CFLAGS += -Wno-error=unused-result
+LDFLAGS = -pthread
+
+.PHONY: all clean
+
+TEST_CUSTOM_PROGS := zlog_stdout
+all: $(TEST_CUSTOM_PROGS)
+
+OBJS = \
+       zlog_stdout.o
+
+$(TEST_CUSTOM_PROGS): $(OBJS)
+       $(CC) -o $(TEST_CUSTOM_PROGS) $(OBJS) $(LDFLAGS)
+
+$(OBJS): %.o: %.c
+       $(CC) -c $^ -o $@ $(CFLAGS)
+
+EXTRA_CLEAN := $(TEST_CUSTOM_PROGS) $(OBJS)
+
+clean:
+       $(RM) -r $(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED) $(TEST_GEN_FILES) $(EXTRA_CLEAN)
diff --git a/tests/zlog_stdout/zlog_stdout.c b/tests/zlog_stdout/zlog_stdout.c
new file mode 100644 (file)
index 0000000..95dfed4
--- /dev/null
@@ -0,0 +1,100 @@
+#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;
+}