From: Arkadiusz Nowak Date: Tue, 21 Jun 2022 11:14:50 +0000 (+0200) Subject: Added vlog module X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=refs%2Fheads%2Fsandbox%2Fanowak03%2Ftizen-vlogger;p=platform%2Fkernel%2Flinux-tizen-modules-source.git Added vlog module Change-Id: I8035b25456aa642e584a671774775c8ca480d0ae --- diff --git a/kernel/Makefile b/kernel/Makefile index a4a8d16..14ea71a 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -14,19 +14,22 @@ CFLAGS_logger.o += -Wno-error=missing-attributes \ -DLOGGER_SYSTEM_BUFFER_SIZE=$(LOGGER_SYSTEM_BUFFER_SIZE) CFLAGS_proc-tsm.o += -Wno-error=missing-attributes + # Build is disabled by default so that when new module is added to this repository (and -source package), # it won't get automatically build in packages using it (that would break these automatically as file list # would no longer match). BUILD_logger ?= n BUILD_proc_tsm ?= n BUILD_kdbus ?= n +BUILD_vlog ?= n obj-$(BUILD_logger) += logger.o obj-$(BUILD_proc_tsm) += proc-tsm.o obj-$(BUILD_kdbus) += kdbus/ +obj-$(BUILD_vlog) += vlogger/ all: - make -C $(KERNELDIR) M=$(PWD) CFLAGS_MODULE=-I$(PWD)/include modules + make -C $(KERNELDIR) M=$(PWD) CFLAGS_MODULE=-I$(PWD)/include modules modules_install: make -C $(KERNELDIR) M=$(PWD) INSTALL_MOD_STRIP=$(INSTALL_MOD_STRIP) INSTALL_MOD_PATH=$(PWD)/../$(INSTALL_MOD_PATH) modules_install diff --git a/kernel/vlogger/Makefile b/kernel/vlogger/Makefile new file mode 100644 index 0000000..18172e1 --- /dev/null +++ b/kernel/vlogger/Makefile @@ -0,0 +1,4 @@ +ccflags-y += -Wno-error=missing-attributes -I$(src)/include_internal + +vlog-y := logger_main.o logger_policy.o logger_filter.o logger_stats.o logger_ext.o vlogger.o +obj-m += vlog.o diff --git a/kernel/vlogger/include_internal/logger.h b/kernel/vlogger/include_internal/logger.h new file mode 100644 index 0000000..f01c4ae --- /dev/null +++ b/kernel/vlogger/include_internal/logger.h @@ -0,0 +1,115 @@ +/* include/linux/logger.h + * + * Copyright (C) 2007-2008 Google, Inc. + * Author: Robert Love + * + * 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. + * + */ + +#ifndef _LINUX_LOGGER_H +#define _LINUX_LOGGER_H + +#include +#include + +/** + * struct user_logger_entry_compat - defines a single entry that is given to a logger + * @len: The length of the payload + * @__pad: Two bytes of padding that appear to be required + * @pid: The generating process' process ID + * @tid: The generating process' thread ID + * @sec: The number of seconds that have elapsed since the Epoch + * @nsec: The number of nanoseconds that have elapsed since @sec + * @msg: The message that is to be logged + * + * The userspace structure for version 1 of the logger_entry ABI. + * This structure is returned to userspace unless the caller requests + * an upgrade to a newer ABI version. + */ +struct user_logger_entry_compat { + __u16 len; + __u16 __pad; + __s32 pid; + __s32 tid; + __s32 sec; + __s32 nsec; + char msg[0]; +}; + +/** + * struct logger_entry - defines a single entry that is given to a logger + * @len: The length of the payload + * @pid: The generating process' process ID + * @tid: The generating process' thread ID + * @sec: The number of seconds that have elapsed since the Epoch + * @nsec: The number of nanoseconds that have elapsed since @sec + * @euid: Effective UID of logger + * @hash: The hashkey of tagname + * @logger_id: The target log buffer id for reading + * @msg: The message that is to be logged + * + * The structure for version 2 of the logger_entry ABI. + * This structure is returned to userspace if ioctl(LOGGER_SET_VERSION) + * is called with version >= 2 + * WARNING: In case of version >= 2, ext_buf can break compatability with 32 bit user on 64 bit kernel + */ +struct logger_entry { + __u16 len; + __s32 pid; + __s32 tid; + __s32 sec; + __s32 nsec; + kuid_t euid; + __u32 hash; + __s32 logger_id; + char msg[0]; +}; + +/** + * struct logger_tag + * @len: Length of a NULL-terminated tag including '\0' + * @ptr: Pointer to a user buffer containing the tag + */ +struct logger_tag { + uint64_t len; + uint64_t ptr; +}; + +#define LOGGER_LOG_RADIO "log_radio" /* radio-related messages */ +#define LOGGER_LOG_EVENTS "log_events" /* system/hardware events */ +#define LOGGER_LOG_SYSTEM "log_system" /* system/framework messages */ +#define LOGGER_LOG_MAIN "log_main" /* everything else */ +#define LOGGER_SMACK_LABEL "*" /* default smack label in log nodes */ + +#define LOGGER_BUFFER_SIZE (1 << 21); // 2 MB + +#define LOGGER_ENTRY_MAX_LEN (1 * 1024) +#define LOGGER_ENTRY_MAX_PAYLOAD (LOGGER_ENTRY_MAX_LEN - \ + sizeof(struct user_logger_entry_compat)) + +#define __LOGGERIO 0xAE + +#define LOGGER_GET_LOG_BUF_SIZE _IO(__LOGGERIO, 1) /* size of log */ +#define LOGGER_GET_LOG_LEN _IO(__LOGGERIO, 2) /* used log len */ +#define LOGGER_GET_NEXT_ENTRY_LEN _IO(__LOGGERIO, 3) /* next entry len */ +#define LOGGER_FLUSH_LOG _IO(__LOGGERIO, 4) /* flush log */ +#define LOGGER_GET_VERSION _IO(__LOGGERIO, 5) /* abi version */ +#define LOGGER_SET_VERSION _IO(__LOGGERIO, 6) /* abi version */ +#define LOGGER_SET_GLOBAL_PRIORITY _IO(__LOGGERIO, 8) /* Set global priority */ +#define LOGGER_SET_TAG_FILTER_INFO _IO(__LOGGERIO, 9) /* Set tag filter information */ +#define LOGGER_GET_BACKEND_FILTER_STATE _IO(__LOGGERIO, 10) /* Get state of backend filter */ +#define LOGGER_SET_PID _IO(__LOGGERIO, 11) /* Set target PID for log to read */ +#define LOGGER_SET_DEV_ID _IO(__LOGGERIO, 12) /* Set devices id */ +#define LOGGER_SET_EXTENDED_BUFFER _IO(__LOGGERIO, 14) /* Set reader to use extended buffer */ +#define LOGGER_SET_TAG _IO(__LOGGERIO, 15) /* set flow mode tag */ +#define LOGGER_SET_PRIO _IO(__LOGGERIO, 16) /* set flow mode prio */ + +#endif /* _LINUX_LOGGER_H */ diff --git a/kernel/vlogger/include_internal/logger_ext.h b/kernel/vlogger/include_internal/logger_ext.h new file mode 100644 index 0000000..31a7ab0 --- /dev/null +++ b/kernel/vlogger/include_internal/logger_ext.h @@ -0,0 +1,21 @@ +/* + * logger_ext: Extended buffer for Logger + * + * Copyright (C) 2019 Samsung Electronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __LOGGER_EXT_H__ +#define __LOGGER_EXT_H__ + +#include + +int logger_ext_get_buffer(size_t size); +char *logger_ext_get_address(int offset); +bool logger_ext_enable(void); +bool logger_ext_disable(void); +bool logger_ext_invalidate(size_t invalid, size_t invalid_max); +#endif /* __LOGGER_EXT_H__ */ diff --git a/kernel/vlogger/include_internal/logger_filter.h b/kernel/vlogger/include_internal/logger_filter.h new file mode 100644 index 0000000..802a327 --- /dev/null +++ b/kernel/vlogger/include_internal/logger_filter.h @@ -0,0 +1,79 @@ +/* + * logger_filter: Filter data structures for logger + * + * Copyright (C) 2018 Samsung Electronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __LOGGER_FILTER_H__ +#define __LOGGER_FILTER_H__ + +#include +#include +#include + +#define DLOG_MAX_TAGS 32 +#define DLOG_MAX_TAGNAME 32 + +/** + * @brief log priority values, in ascending priority order. + */ +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; + +/** + * struct filter_info_t - Filter information for dlog tags and priorities. + * @tagname: Identifiable dlog tag name. + * @priority: indicates the estimated severity of the event that caused the log. + * @hentry: Linked entry list. + */ +typedef struct filter_info_t { + char *tagname; + log_priority priority; + struct hlist_node hentry; +} filter_info; + +/** + * struct logger_tagname_registry - names registered for a filter. + * @entries_hash: Map of entries. + * @name_seq_last: Last used sequence number to assign to a name entry. + */ +struct logger_tagname_registry { + DECLARE_HASHTABLE(entries_hash, 7); + u32 name_seq_last; +}; + +/** + * struct filter_args_t - the representation of ioctl argument used by user and kernel + * @tagname: Identifiable dlog tag name. + * @priority: indicates the estimated severity of the event that caused the log. + */ +#ifdef CONFIG_COMPAT +struct filter_args_compat_t { + compat_uptr_t tagname; + log_priority priority; +}; +#endif +struct filter_args_t { + char *tagname; + log_priority priority; +}; + +unsigned int logger_strhash(const char *str); +struct logger_tagname_registry *logger_tagname_registry_new(void); +void logger_tagname_registry_free(struct logger_tagname_registry *r); + +#endif /* __LOGGER_FILTER_H__ */ diff --git a/kernel/vlogger/include_internal/logger_policy.h b/kernel/vlogger/include_internal/logger_policy.h new file mode 100644 index 0000000..3368947 --- /dev/null +++ b/kernel/vlogger/include_internal/logger_policy.h @@ -0,0 +1,120 @@ +/* + * logger_policy: A Logging Policy for Tizen TV. + * + * Copyright (C) 2016 Samsung Electronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __LOGGER_POLICY_H__ +#define __LOGGER_POLICY_H__ + +#define LOGGER_POLICY_MISCDEV_NAME "logger_policy" +#define LOGGER_POLICY_MAX_TAG_NAME 32 +#define LOGGER_POLICY_MAX_TAG_COUNT 10 +#define LOGGER_POLICY_SMACK_LABEL "*" +#define LOGGER_POLICY_MAX_TASK_NAME 16 + +enum logger_id { + LOGGER_ID_MAIN = 0, + LOGGER_ID_RADIO, + LOGGER_ID_SYSTEM, + LOGGER_ID_EVENTS, + NR_LOGGER_IDS, +}; + +/** + * struct policy_qos - structure to represent 'qos' + * @count : the number of logs used by a process associated @tgid + * @reset_timer : Next time when QoS data will be reset. + * @num_tags : the number of tags + * @tag_list : the list for tags + * @lock : the mutex to protect the @tag_list + * @activated : Mark whether logger QoS is activated. + * @qos_bypass : Mark whether logger QoS is bypass or not. + */ +struct policy_qos { + int count; + int reset_timer; + int num_tags; + struct list_head tag_list; + struct mutex lock; + bool activated; + bool qos_bypass; +}; + +/** + * struct policy_tag - structure to represent TAG + * @name: the name of tag + * @node: the node for @tag_list in policy_qos structure + * @count: the number of logs used by this tag. + * @limited: the flag indicating whether this tag is limited or not + */ +struct policy_tag { + char name[LOGGER_POLICY_MAX_TAG_NAME + 1]; + struct list_head node; + int count; + int limited; +}; + +/** + * struct policy_filter - structure to represent filter + * @name: the name of filter + * @node: the node for filter list + */ +struct policy_filter { + char name[LOGGER_POLICY_MAX_TAG_NAME + 1]; + struct list_head node; +}; + +/** + * struct policy_qos_bypass - structure to represent filter + * @name: the name of VIP Process + * @node: the node for VIP Process list + */ +struct policy_qos_bypass { + char name[LOGGER_POLICY_MAX_TASK_NAME]; + struct list_head node; +}; + +/** + * struct policy_qos_controller - structure to represent qos controller + * @pid: the pid of qos controller + * @node: the node for qos controller list + */ +struct policy_qos_controller { + int pid; + struct list_head node; +}; + +/** + * struct logger_policy - structure to represent logger policy + * @qos: the qos structure + * @pid: pid of current task + * @ref: reference count + * @node: the node for logger policy + */ +struct logger_policy { + struct policy_qos qos; + int pid; + int ref; + struct hlist_node node; +}; + + +#define __LOGGERPOLICYIOCTL 0x97 +#define LOGGER_POLICY_SET_FILTER _IOW(__LOGGERPOLICYIOCTL, 0x1, char[LOGGER_POLICY_MAX_TAG_NAME]) +#define LOGGER_POLICY_SET_STATUS _IOW(__LOGGERPOLICYIOCTL, 0x2, int) +#define LOGGER_POLICY_SET_LOGGER_ENABLE _IOW(__LOGGERPOLICYIOCTL, 0x3, int) +#define LOGGER_POLICY_SET_QOS _IOW(__LOGGERPOLICYIOCTL, 0x4, int) + +int logger_policy_check(struct logger_policy *policy, uint8_t priority, const char *tag); +int get_backend_logfilter_enable(void); +struct logger_policy *logger_policy_get(void); +void logger_policy_put(struct logger_policy *policy); +int logger_policy_init(void); +void logger_policy_exit(void); +#endif /* __LOGGER_POLICY_H__ */ + diff --git a/kernel/vlogger/include_internal/logger_stats.h b/kernel/vlogger/include_internal/logger_stats.h new file mode 100644 index 0000000..900ac4b --- /dev/null +++ b/kernel/vlogger/include_internal/logger_stats.h @@ -0,0 +1,33 @@ +/* + * logger_stats: Logger Stats for Tizen TV. + * + * Copyright (C) 2019 Samsung Electronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __LOGGER_STATS_H__ +#define __LOGGER_STATS_H__ + +#define LOGGER_STATS_MISCDEV_NAME "logger_stats" +#define LOGGER_STATS_SMACK_LABEL "*" +#define LOGGER_STATS_SIZE 256 + +#define __LOGGERSTATSIOCTL 0x98 +#define LOGGER_STATS_SEND _IOW(__LOGGERSTATSIOCTL, 0x2, char[LOGGER_STATS_SIZE]) + +enum logger_stats_id { + LOGGER_STATS_WRITE = 0, + LOGGER_STATS_READ, + LOGGER_STATS_OVERFLOW, + LOGGER_STATS_DROP, + LOGGER_STATS_MAX, +}; + +void logger_stats_update(int stats_id, int log_id, int bytes, int pid); +int logger_stats_init(void); +void logger_stats_exit(void); +#endif /* __LOGGER_STATS_H__ */ + diff --git a/kernel/vlogger/include_internal/vlogger.h b/kernel/vlogger/include_internal/vlogger.h new file mode 100644 index 0000000..5369aac --- /dev/null +++ b/kernel/vlogger/include_internal/vlogger.h @@ -0,0 +1,21 @@ +/* + * A Logging Subsystem for VD + * + * Copyright (c) 2015-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. + */ + +#ifndef __VLOGGER_H__ +#define __VLOGGER_H__ + +int vlogger_init(void); +int vlogger_exit(void); +#endif /* __VLOGGER_H__ */ diff --git a/kernel/vlogger/logger_ext.c b/kernel/vlogger/logger_ext.c new file mode 100644 index 0000000..11f6ea0 --- /dev/null +++ b/kernel/vlogger/logger_ext.c @@ -0,0 +1,221 @@ +/* + * logger_ext: Extended buffer for Logger + * + * Copyright (C) 2019 Samsung Electronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#define pr_fmt(fmt) "logger_ext: " fmt + +#include +#include "logger_ext.h" + +#ifndef NUMA_EMEM_VNODE +#define NUMA_EMEM_VNODE 0 +#endif + +#define LOGGER_EXT_BUFFER_SIZE (1 << 24) // 16 MB +#define LOGGER_EXT_BUFFER_COUNT (2) + +typedef enum { + EXT_STATE_DISABLE, + EXT_STATE_ENABLE, + EXT_STATE_ENABLE_EXHAUSTED +} ext_state_t; + +struct ext_buffer { + unsigned int id; + ext_state_t status; + size_t offset; + size_t invalid; + char *buffer; +}; + +static struct ext_buffer g_ext[LOGGER_EXT_BUFFER_COUNT] = {0, }; +static unsigned int g_idx = 0; +static unsigned int g_sequencer = 0; + +static int ext_get_buffer(struct ext_buffer *ext, size_t size) +{ + int r; + + if (!ext) + return -1; + + if (ext->status != EXT_STATE_ENABLE) + return -1; + + if (!ext->buffer) { + char *buf = vmalloc_node(LOGGER_EXT_BUFFER_SIZE, NUMA_EMEM_VNODE); + if (!buf) { + pr_err("[%u] Failed to allocate memory!\n", ext->id); + return -1; + } + ext->buffer = buf; + pr_info("[%u] Allocate memory (%p)\n", ext->id, ext->buffer); + } + + r = (int)ext->offset; + ext->offset += size; + + if (ext->offset >= LOGGER_EXT_BUFFER_SIZE) { + ext->status = EXT_STATE_ENABLE_EXHAUSTED; + pr_info("[%u] Use all extended buffer (%p)\n", ext->id, ext->buffer); + return -1; + } + + return r; +} + +static bool ext_enable(struct ext_buffer *ext) +{ + if (!ext) + return false; + + if (ext->status != EXT_STATE_DISABLE) + return false; + + ext->buffer = NULL; + ext->id = g_sequencer++; + ext->offset = 0; + ext->invalid = 0; + ext->status = EXT_STATE_ENABLE; + + pr_info("[%u] Enable extended buffer\n", ext->id); + return true; +} + +static bool ext_disable(struct ext_buffer *ext) +{ + if (!ext) + return false; + + if (ext->status == EXT_STATE_DISABLE) { + pr_err("[%u] Already disabled!\n", ext->id); + return true; + } + + if (ext->buffer) + vfree(ext->buffer); + ext->buffer = NULL; + ext->offset = 0; + ext->invalid = 0; + ext->status = EXT_STATE_DISABLE; + + pr_info("[%u] Disable extended buffer\n", ext->id); + return true; +} + +static bool ext_invalidate(struct ext_buffer *ext, size_t invalid, size_t invalid_max) +{ + if (!ext) + return false; + + if (ext->status != EXT_STATE_ENABLE_EXHAUSTED) + return false; + + ext->invalid += invalid; + if (ext->invalid < invalid_max) + return false; + + return ext_disable(ext); +} + +/* + * logger_ext_get_buffer - Get the offset of extended buffer + * + * The caller needs to hold log.mutex. + */ +int logger_ext_get_buffer(size_t size) +{ + unsigned int i; + int r; + + for (i = 0; i < LOGGER_EXT_BUFFER_COUNT; i++) { + r = ext_get_buffer(&g_ext[g_idx], size); + if (r >= 0) + return (r + (g_idx * LOGGER_EXT_BUFFER_SIZE)); + + g_idx = (g_idx + 1) % LOGGER_EXT_BUFFER_COUNT; + } + + return -1; +} + +/* + * logger_ext_get_address - Get the address of extended buffer from offset + * + * The caller needs to hold log.mutex. + */ +char *logger_ext_get_address(int offset) +{ + int idx = offset / LOGGER_EXT_BUFFER_SIZE; + int off = offset % LOGGER_EXT_BUFFER_SIZE; + + if (idx >= 0 && idx < LOGGER_EXT_BUFFER_COUNT) + return (char *)g_ext[idx].buffer + off; + + return NULL; +} + +/* + * logger_ext_enable - Enable extended buffer + * + * The caller needs to hold log.mutex. + */ +bool logger_ext_enable(void) +{ + unsigned int i; + unsigned int idx = g_idx; + + for (i = 0; i < LOGGER_EXT_BUFFER_COUNT; i++) { + if (ext_enable(&g_ext[idx])) + return true; + idx = (idx + 1) % LOGGER_EXT_BUFFER_COUNT; + } + + return false; +} + +/* + * logger_ext_disable - Disable extended buffer + * + * The caller needs to hold log.mutex. + */ +bool logger_ext_disable(void) +{ + unsigned int i; + unsigned int idx = g_idx; + bool ret = false; + + for (i = 0; i < LOGGER_EXT_BUFFER_COUNT; i++) { + ret |= ext_disable(&g_ext[idx]); + idx = (idx + 1) % LOGGER_EXT_BUFFER_COUNT; + } + + return ret; +} + +/* + * logger_ext_invalidate + * + * For all extended buffers, update a invalidation offset and check whether it can be disabled. + * The caller needs to hold log.mutex. + */ +bool logger_ext_invalidate(size_t invalid, size_t invalid_max) +{ + unsigned int i; + unsigned int idx = g_idx; + bool ret = false; + + for (i = 0; i < LOGGER_EXT_BUFFER_COUNT; i++) { + ret |= ext_invalidate(&g_ext[idx], invalid, invalid_max); + idx = (idx + 1) % LOGGER_EXT_BUFFER_COUNT; + } + + return ret; +} + diff --git a/kernel/vlogger/logger_filter.c b/kernel/vlogger/logger_filter.c new file mode 100644 index 0000000..b653e8b --- /dev/null +++ b/kernel/vlogger/logger_filter.c @@ -0,0 +1,57 @@ +#include "logger_filter.h" +#include +#include + +/** + * logger_tagname_registry_new() - create a new name registry + * + * Return: a new logger_tagname_registry on success, ERR_PTR on failure. + */ +struct logger_tagname_registry *logger_tagname_registry_new(void) +{ + struct logger_tagname_registry *r; + + r = kmalloc(sizeof(*r), GFP_KERNEL); + if (!r) + return ERR_PTR(-ENOMEM); + + hash_init(r->entries_hash); + r->name_seq_last = 0; + + return r; +} + +/** + * logger_tagname_registry_free() - free name registry + * @r: name registry to free, or NULL + * + * Free a name registry and cleanup all internal objects. This is a no-op if + * you pass NULL as registry. + */ +void logger_tagname_registry_free(struct logger_tagname_registry *r) +{ + if (!r) + return; + + WARN_ON(!hash_empty(r->entries_hash)); + kfree(r); +} + +/** + * logger_strhash - calculate a hash + * @str: String + * + * Return: hash value + */ +unsigned int logger_strhash(const char *str) +{ + unsigned long hash = 0; + + while (*str) { + hash += (*str << 4) + (*str >> 4); + hash *= 11; + str++; + } + + return (unsigned int)hash; +} diff --git a/kernel/vlogger/logger_main.c b/kernel/vlogger/logger_main.c new file mode 100755 index 0000000..5a208e9 --- /dev/null +++ b/kernel/vlogger/logger_main.c @@ -0,0 +1,1315 @@ +/* + * drivers/misc/logger.c + * + * A Logging Subsystem + * + * Copyright (C) 2007-2008 Google, Inc. + * + * Robert Love + * + * 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) "logger: " fmt +#define MAX_LOG 129 +#define N_FD 3 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* The KERNEL_VERSION macro check is added to maintain build + * compatibility with old versions of VD kernel. + * This macro is defined in + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) +/* The below headers are included to fix build errors of type + * "implicit-function-declaration" when build with kernel 5.4 + */ +#include +#include +#endif +#include "logger.h" +#include "logger_policy.h" +#include "logger_filter.h" +#include "logger_stats.h" +#include "logger_ext.h" +#include "vlogger.h" + +#ifndef NUMA_EMEM_VNODE +#define NUMA_EMEM_VNODE 0 +#endif + +#define LOGGER_NR_SEGS (3) +#define ENABLE_ONE_BUFF (7) +#define MAGIC_EXT (0xFEFE) + +#define LOGGER_GET_IOV_DATA(p, l, v, n) { \ + if (copy_from_user(p, (void __user *)v->iov[n].iov_base, min_t(size_t, l, v->iov[n].iov_len))) \ + return -EFAULT; \ +} + +#define get_user_hdr_len(v) \ + (v < 2 ? sizeof(struct user_logger_entry_compat) : sizeof(struct logger_entry)) + +/** + * struct logger_log - provide data for managing log buffer + * @buffer: The actual ring buffer + * @readers: This log's readers + * @mutex: The mutex that protects the @buffer + * @w_off: The current write head offset + * @head: The head, or location that readers start reading at. + * @size: The size of the log + * @eot: End-of-Tape, The offset of beginning of margin. + * @miscs: The "misc" devices representing the log + * + * This structure lives from module insertion until module removal, so it does + * not need additional reference counting. The structure is protected by the + * mutex 'mutex'. + */ +struct logger_log { + unsigned char *buffer; + struct list_head readers; + struct mutex mutex; + size_t w_off; + size_t head; + size_t size; + size_t eot; + struct miscdevice miscs[NR_LOGGER_IDS]; +}; + +/* + * struct logger_filter_pid_list - Data set to filter pid and its nos + * @no_of_pids: Total no of pids for filtering + * @pid_list: The list of all pids for filtering +*/ +struct logger_pid_list{ + int no_of_pids; + pid_t *pid_list; +}; + +#ifdef CONFIG_COMPAT +struct logger_pid_list_compat { + int no_of_pids; + compat_uptr_t pid_list; +}; +#endif + +/* + * struct logger_backend_filter - Data set to filter log on backend side + * @global_pri: The priority defined globally + * @tag_names: The list of all tag names for each reader + * @pid: Target pid for reading +*/ +struct logger_backend_filter { + log_priority global_pri; + struct logger_tagname_registry *tag_names; + struct logger_pid_list pid_filter_list; +}; + +/** + * struct log_writer - a logging device open for writing + * @owner: The owner task of writer + * @prio: Default message priority value + * @tag: The tag to be attached to messages + * @tag_len: The length of the tag + */ +struct logger_writer { + struct task_struct *owner; + int prio; + size_t tag_len; + char tag[DLOG_MAX_TAGNAME + 1]; +}; + +/** + * struct logger_reader - a logging device open for reading + * @log: The associated log + * @list: The associated entry in @logger_log's list + * @r_off: The current read head offset. + * @r_all: Reader can read all entries + * @r_ver: Reader ABI version + * @wq: The wait queue for @reader + * @logger_ids: Mask of interested buffer ids + * @filter_data: Data to filter on backend + * @pid: The pid of reader + * + * This object lives from open to release, so we don't need additional + * reference counting. The structure is protected by log.mutex. + */ +struct logger_reader { + struct list_head list; + size_t r_off; + bool r_all; + int r_ver; + wait_queue_head_t wq; + unsigned long logger_ids; + struct logger_backend_filter filter_data; + pid_t pid; +}; + +struct logger_pdata { + int logger_id; + void *reader; + void *writer; + void *policy; +}; + +struct logger_ext_entry { + __u16 magic; + __s32 offset; +}; + +static struct logger_log log; + +static int g_plog_enable = 0; +module_param_named(plog_enable, g_plog_enable, int, 0644); + +/* + * get_ext_entry - return the entry located at the offset if it is extended. + * + * The caller needs to hold log.mutex. + */ +static inline struct logger_ext_entry *get_ext_entry(size_t off) +{ + struct logger_ext_entry *ext = (struct logger_ext_entry *)(log.buffer + off); + + if (ext == NULL || ext->magic != MAGIC_EXT) + return NULL; + + return ext; +} + +/* + * get_entry - return the entry located at the offset. + * + * The caller needs to hold log.mutex. + */ +static inline struct logger_entry *get_entry(size_t off) +{ + struct logger_ext_entry *ext = get_ext_entry(off); + + if (ext) + return (struct logger_entry *)logger_ext_get_address(ext->offset); + + return (struct logger_entry *)(log.buffer + off); +} + +static ssize_t copy_header_to_user(int ver, struct logger_entry *entry, + char __user *buf, size_t *hdr_len) +{ + void *hdr; + struct user_logger_entry_compat v1; + + if (ver < 2) { + v1.len = entry->len; + v1.__pad = 0; + v1.pid = entry->pid; + v1.tid = entry->tid; + v1.sec = entry->sec; + v1.nsec = entry->nsec; + hdr = &v1; + *hdr_len = sizeof(struct user_logger_entry_compat); + } else { + hdr = entry; + *hdr_len = sizeof(struct logger_entry); + } + + return copy_to_user(buf, hdr, *hdr_len); +} + +/* + * get_next_entry - return the offset of the next entry. + * + * The caller needs to hold log.mutex. + */ +static size_t get_next_entry(size_t off) +{ + if (get_ext_entry(off)) + off += sizeof(struct logger_ext_entry); + else + off += (sizeof(struct logger_entry) + get_entry(off)->len); + + if (off == log.eot) + off = 0; + + return off; +} + +/* + * fix_up_offset - update the offset to the first valid entry at least 'len' + * bytes after 'off' and return the size of skipped entries. + * + * The caller needs to hold log.mutex. + */ +static size_t fix_up_offset(size_t *off, size_t len) +{ + size_t count = 0; + size_t ret = 0; + + do { + if (*off >= log.eot) { + struct logger_reader *reader; + list_for_each_entry(reader, &log.readers, list) { + pr_err("r:%zu pid:%d", reader->r_off, (int)current->tgid); + } + pr_err("LOGGER_ERROR!! cnt:%zu len:%zu off:%zu h:%zu w:%zu e:%zu %s(%d)", + count, len, *off, log.head, log.w_off, + log.eot, current->comm, (int)current->tgid); + *off = 0; + } + + if (get_ext_entry(*off)) + count += sizeof(struct logger_ext_entry); + else + count += sizeof(struct logger_entry) + get_entry(*off)->len; + + ret += sizeof(struct logger_entry) + get_entry(*off)->len; + + *off = get_next_entry(*off); + } while (count < len); + + return ret; +} + +static bool check_pid_filter(struct logger_pid_list *ptr, pid_t pid) +{ + int i = 0; + + for(i = 0; i < ptr->no_of_pids; i++){ + if(ptr->pid_list[i] == pid) + return true; + } + + return false; +} + +/* + * do_match_log_filters - Filtering message by tag. + * + * If the tag and tag priority matches, it returns true. + * and it returns false if not. + * + * The caller needs to hold log.mutex. + */ +static bool do_match_log_filters(struct logger_reader *reader) +{ + struct logger_tagname_registry *r = reader->filter_data.tag_names; + struct logger_entry *entry = get_entry(reader->r_off); + log_priority pri = entry->msg[0]; + char *tag = &entry->msg[1]; + __u32 hashkey = entry->hash; + + /* Check Tag priority */ + if (pri < DLOG_UNKNOWN || pri > DLOG_SILENT) { + pr_info("Wrong message priority\n"); + goto skip_entry; + } + + if ((reader->filter_data.pid_filter_list.no_of_pids > 0) + && !check_pid_filter(&reader->filter_data.pid_filter_list, entry->pid)) + goto skip_entry; + + if (reader->filter_data.global_pri == DLOG_PRIO_MAX) + return true; + + if (r->name_seq_last) { + filter_info *filter; + hash_for_each_possible(r->entries_hash, filter, hentry, hashkey) { + if (strcmp(filter->tagname, tag)) + continue; + + if (pri >= filter->priority) + return true; + else + goto skip_entry; + } + } + + if (pri >= reader->filter_data.global_pri) + return true; + +skip_entry: + /* Calculate offset for next entry */ + reader->r_off = get_next_entry(reader->r_off); + return false; +} + +/* + * do_read_log_to_user - reads exactly 'count' bytes from 'log' into the + * user-space buffer 'buf'. Returns 'count' on success. + * + * The caller needs to hold log.mutex. + */ +static ssize_t do_read_log_to_user(struct logger_reader *reader, char __user *buf) +{ + struct logger_entry *entry; + size_t hdr_len; + + /* + * First, copy the header to userspace, using the version of + * the header requested + */ + entry = get_entry(reader->r_off); + if (copy_header_to_user(reader->r_ver, entry, buf, &hdr_len)) + return -EFAULT; + + /* + * Now, copy message to userspace. + */ + if (copy_to_user(buf + hdr_len, entry->msg, entry->len)) + return -EFAULT; + + logger_stats_update(LOGGER_STATS_READ, entry->logger_id, entry->len + sizeof(struct logger_entry), reader->pid); + + reader->r_off = get_next_entry(reader->r_off); + + return entry->len + hdr_len; +} + +/* + * get_next_entry_by_uid - Starting at 'off', returns an offset into + * 'log buffer' which contains the first entry readable by 'euid' + * + * The caller needs to hold log.mutex. + */ +static size_t get_next_entry_by_uid(size_t off, kuid_t euid) +{ + while (off != log.w_off) { + struct logger_entry *entry = get_entry(off); + + if (uid_eq(entry->euid, euid)) + return off; + + off = get_next_entry(off); + } + return off; +} + +/* + * logger_read - our log's read() method + * + * Behavior: + * + * - O_NONBLOCK works + * - If there are no log entries to read, blocks until log is written to + * - Atomically reads exactly one log entry + * + * If filter enabled, finds until there are matching log in the buffer. + * set errno to EINTR if reader can't find a tag that matches up to the end + * (i.e w_off == r_off) + * + * Will set errno to EINVAL if read + * buffer is insufficient to hold next entry. + */ +static ssize_t logger_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct logger_pdata *pdata = file->private_data; + struct logger_reader *reader = pdata->reader; + ssize_t ret = -EINTR; + DEFINE_WAIT(wait); + + while (1) { + mutex_lock(&log.mutex); + if (!reader->r_all) + reader->r_off = get_next_entry_by_uid( + reader->r_off, current_euid()); + + if (log.w_off != reader->r_off) { + struct logger_entry *entry = get_entry(reader->r_off); + + /* get the size of the next entry */ + ret = get_user_hdr_len(reader->r_ver) + entry->len; + if (count < ret) { + ret = -EINVAL; + break; + } + + if (test_bit(entry->logger_id, &reader->logger_ids) == 0) { + reader->r_off = get_next_entry(reader->r_off); + if (log.w_off == reader->r_off) { + ret = -EINTR; + break; + } + mutex_unlock(&log.mutex); + continue; + } + + /* Filter matching if backend filter is enabled */ + if (get_backend_logfilter_enable()) { + if (!do_match_log_filters(reader)) { + /* When there are no more messages to read */ + if (log.w_off == reader->r_off) { + ret = -EINTR; + break; + } + mutex_unlock(&log.mutex); + continue; + } + } + + /* get exactly one entry from the log */ + ret = do_read_log_to_user(reader, buf); + break; + } + mutex_unlock(&log.mutex); + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (signal_pending(current)) + return -EINTR; + + prepare_to_wait(&reader->wq, &wait, TASK_INTERRUPTIBLE); + schedule(); + finish_wait(&reader->wq, &wait); + } + + mutex_unlock(&log.mutex); + return ret; +} + +/* + * is_between - is a < c < b, accounting for wrapping of a, b, and c + * positions in the buffer + * + * That is, if ab, check for c outside (not between) a and b + * + * |------- a xxxxxxxx b --------| + * c^ + * + * |xxxxx b --------- a xxxxxxxxx| + * c^ + * or c^ + */ +static inline int is_between(size_t a, size_t b, size_t c) +{ + if (a < b) { + /* is c between a and b? */ + if (a < c && c <= b) + return 1; + } else { + /* is c outside of b through a? */ + if (c <= b || a < c) + return 1; + } + + return 0; +} + +/* + * fix_up_readers - walk the list of all readers and "fix up" any who were + * lapped by the writer; also do the same for the default "start head". + * We do this by "pulling forward" the readers and start head to the first + * entry after the new write head. + * + * The caller needs to hold log.mutex. + */ +static void fix_up_readers(size_t len, int logger_id) +{ + size_t old = log.w_off; + size_t new = (old + len) & (log.size - 1); /* 2^n modulo */ + struct logger_reader *reader; + + if (is_between(old, new, log.head)) + fix_up_offset(&log.head, len); + + /* If offset is equal to new EoT, reset it to 0 */ + if (new < old && old == log.head) + log.head = 0; + + list_for_each_entry(reader, &log.readers, list) { + if (is_between(old, new, reader->r_off)) { + size_t dropped = fix_up_offset(&reader->r_off, len); + logger_stats_update(LOGGER_STATS_OVERFLOW, logger_id, dropped, reader->pid); + } + + /* If offset is equal to new EoT, reset it to 0 */ + if (new < old && old == reader->r_off) + reader->r_off = 0; + } +} + +static void print_warn_info(struct iov_iter *from) +{ + int i; + char unformatstring[MAX_LOG] = {0,}; + struct fdtable *fdt=NULL; + struct files_struct *files_p = NULL; + struct file *file = NULL; + char *path = NULL; + int log_length = min_t(size_t, MAX_LOG, from->iov[0].iov_len); + + if (copy_from_user(unformatstring, from->iov[0].iov_base, log_length)) + pr_err("Illegal write by process : %s\n", current->comm); + else + pr_err("Illegal write by process : %s, String : %s\n", current->comm, unformatstring); + + + files_p = current->files; + if (files_p) { + int min_fd; + spin_lock(&files_p->file_lock); + fdt = files_fdtable(files_p); + min_fd = N_FD < fdt->max_fds ? N_FD : fdt->max_fds; + + for (i = 0; i < min_fd; i++) { + char tmp[256] = {0,}; + file = fdt->fd[i]; + if (file) { + path = d_path(&file->f_path, tmp, sizeof(tmp)); + if (IS_ERR(path)) { + pr_err("Error parsing path of FD : %d, error :%ld", i, PTR_ERR(path)); + continue; + } + } else + continue; + pr_err("FD : %d ----> %s\n", i, path); + } + spin_unlock(&files_p->file_lock); + } +} + +/* + * logger_write_iter - our write method, implementing support for write(), + * writev(), and aio_write(). Writes are our fast path, and we try to optimize + * them above all else. + */ +static ssize_t logger_write_iter(struct kiocb *iocb, struct iov_iter *from) +{ + struct logger_pdata *pdata = iocb->ki_filp->private_data; + struct logger_policy *policy = pdata->policy; + struct logger_writer *writer = pdata->writer; + unsigned long nr_segs = from->nr_segs; + struct logger_entry header; + size_t count, w_off; + u64 ts; + unsigned long nsec; + size_t entry_len, fix_up_len; + struct logger_reader *reader; + int ret; + uint8_t priority = DLOG_UNKNOWN; + char tag[DLOG_MAX_TAGNAME + 1] = {}; + size_t tag_size; + size_t msg_size; + unsigned char *buffer; + struct logger_ext_entry ext; + void __user * msg; + + if (!g_plog_enable) + return -EPERM; + + if (nr_segs == LOGGER_NR_SEGS && from->iov[0].iov_len == 1) { + LOGGER_GET_IOV_DATA(&priority, sizeof(priority), from, 0); + LOGGER_GET_IOV_DATA(tag, DLOG_MAX_TAGNAME, from, 1); + } else if (nr_segs == 1) { + priority = (uint8_t)writer->prio; + strncpy(tag, writer->tag, sizeof(tag)); + } else { + print_warn_info(from); + return -EINVAL; + } + + tag_size = strlen(tag) + 1; + msg = from->iov[nr_segs-1].iov_base; + msg_size = min_t(size_t, from->iov[nr_segs-1].iov_len, LOGGER_ENTRY_MAX_PAYLOAD - tag_size - 1); + count = msg_size + tag_size + 1; + + /* null writes succeed, return zero */ + if (unlikely(!msg_size)) + return 0; + + /* check logger policy */ + ret = logger_policy_check(policy, priority, tag); + if (ret < 0) { + if (ret == -EDQUOT) + logger_stats_update(LOGGER_STATS_DROP, pdata->logger_id, count + sizeof(struct logger_entry), current->tgid); + return 0; + } + + header.pid = current->tgid; + header.tid = current->pid; + header.euid = current_euid(); + header.len = count; + header.logger_id = pdata->logger_id; + + if (get_backend_logfilter_enable()) + header.hash = logger_strhash(tag); + + mutex_lock(&log.mutex); + + if (unlikely(log.buffer == NULL)) + { + log.buffer = vmalloc_node(log.size, NUMA_EMEM_VNODE); + if (log.buffer == NULL) + { + mutex_unlock(&log.mutex); + return -ENOMEM; + } + } + + ts = local_clock(); + nsec = do_div(ts, 1000000000); + header.sec = ts; + header.nsec = nsec; + entry_len = sizeof(struct logger_entry) + count; + ext.offset = logger_ext_get_buffer(entry_len); + if (ext.offset >= 0) { + ext.magic = MAGIC_EXT; + entry_len = sizeof(struct logger_ext_entry); + } + w_off = log.w_off; + + /* + * Check whether left room is enough for commit. + * If it's not, then skip it and start from offset 0. + */ + fix_up_len = entry_len; + if (unlikely(entry_len > log.size - w_off)) { + fix_up_len += log.size - w_off; + w_off = 0; + } + + /* + * Fix up any readers, pulling them forward to the first readable + * entry after (what will be) the new write offset. We do this now + * because if we partially fail, we can end up with clobbered log + * entries that encroach on readable buffer. + */ + fix_up_readers(fix_up_len, header.logger_id); + + buffer = log.buffer + w_off; + if (ext.offset >= 0) { + memcpy(buffer, &ext, sizeof(struct logger_ext_entry)); + buffer = logger_ext_get_address(ext.offset); + } + + memcpy(buffer, &header, sizeof(struct logger_entry)); + buffer += sizeof(struct logger_entry); + + *buffer++ = priority; + + memcpy(buffer, tag, tag_size); + buffer += tag_size; + + if (copy_from_user(buffer, msg, msg_size)) { + mutex_unlock(&log.mutex); + return -EFAULT; + } + w_off += entry_len; + + /* Update End-of-Tape*/ + if (w_off < log.w_off) + log.eot = log.w_off; + else if (w_off > log.eot) + log.eot = w_off; + + log.w_off = (w_off == log.eot ? 0 : w_off); + + /* disable extended buffer if it is not used anymore */ + logger_ext_invalidate(entry_len, log.size); + + /* wake up any blocked readers */ + list_for_each_entry(reader, &log.readers, list) { + if (test_bit(header.logger_id, &reader->logger_ids) != 0) + wake_up_interruptible(&reader->wq); + } + + mutex_unlock(&log.mutex); + + logger_stats_update(LOGGER_STATS_WRITE, header.logger_id, count + sizeof(struct logger_entry), current->tgid); + + return from->count; +} + +/* + * logger_open - the log's open() file operation + */ +static int logger_open(struct inode *inode, struct file *file) +{ + struct logger_pdata *pdata; + const char *fname; + + nonseekable_open(inode, file); + + pdata = kzalloc(sizeof(struct logger_pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + fname = file->f_path.dentry->d_name.name; + if (!strncmp(fname, LOGGER_LOG_MAIN, strlen(LOGGER_LOG_MAIN))) + pdata->logger_id = LOGGER_ID_MAIN; + else if (!strncmp(fname, LOGGER_LOG_SYSTEM, strlen(LOGGER_LOG_SYSTEM))) + pdata->logger_id = LOGGER_ID_SYSTEM; + else if (!strncmp(fname, LOGGER_LOG_EVENTS, strlen(LOGGER_LOG_EVENTS))) + pdata->logger_id = LOGGER_ID_EVENTS; + else if (!strncmp(fname, LOGGER_LOG_RADIO, strlen(LOGGER_LOG_RADIO))) + pdata->logger_id = LOGGER_ID_RADIO; + else { + kfree(pdata); + return -EINVAL; + } + + if (file->f_mode & FMODE_READ) { + struct logger_reader *reader; + + reader = kzalloc(sizeof(struct logger_reader), GFP_KERNEL); + if (!reader) { + kfree(pdata); + return -ENOMEM; + } + + reader->r_ver = 1; +#if defined(CHECK_READ_PRIVILEGE) + reader->r_all = in_egroup_p(inode->i_gid) || capable(CAP_SYSLOG); +#else + reader->r_all = 1; +#endif + reader->pid = current->tgid; + set_bit(pdata->logger_id, &reader->logger_ids); + + init_waitqueue_head(&reader->wq); + INIT_LIST_HEAD(&reader->list); + reader->filter_data.global_pri = DLOG_PRIO_MAX; + if (get_backend_logfilter_enable()) { + reader->filter_data.tag_names = logger_tagname_registry_new(); + if (IS_ERR(reader->filter_data.tag_names)) { + int ret = PTR_ERR(reader->filter_data.tag_names); + reader->filter_data.tag_names = NULL; + kfree(reader); + kfree(pdata); + return ret; + } + } + + reader->filter_data.pid_filter_list.no_of_pids = 0; + reader->filter_data.pid_filter_list.pid_list = NULL; + + mutex_lock(&log.mutex); + reader->r_off = log.head; + list_add_tail(&reader->list, &log.readers); + mutex_unlock(&log.mutex); + + pdata->reader = reader; + } + + if (file->f_mode & FMODE_WRITE) { + struct logger_policy *policy; + struct logger_writer *writer; + + writer = kzalloc(sizeof(struct logger_writer), GFP_KERNEL); + if (!writer) { + if (pdata->reader) + kfree(pdata->reader); + kfree(pdata); + return -ENOMEM; + } + writer->owner = current->group_leader; + writer->prio = DLOG_INFO; + writer->tag_len = 6; + strncpy(writer->tag, "STDOUT", DLOG_MAX_TAGNAME); + pdata->writer = writer; + + policy = logger_policy_get(); + if (IS_ERR(policy)) { + if (pdata->reader) + kfree(pdata->reader); + if (pdata->writer) + kfree(pdata->writer); + kfree(pdata); + return PTR_ERR(policy); + } + + pdata->policy = policy; + } + + file->private_data = pdata; + + return 0; +} + +/* + * logger_release - the log's release file operation + */ +static int logger_release(struct inode *ignored, struct file *file) +{ + struct logger_pdata *pdata = file->private_data; + + if (file->f_mode & FMODE_READ) { + struct logger_reader *reader = pdata->reader; + struct logger_tagname_registry *r = reader->filter_data.tag_names; + + mutex_lock(&log.mutex); + list_del(&reader->list); + + if (r && get_backend_logfilter_enable()) { + filter_info *filter; + struct hlist_node *tmp; + int bkt; + + hash_for_each_safe(r->entries_hash, bkt, tmp, filter, hentry) { + hash_del(&filter->hentry); + if (filter->tagname) + kfree(filter->tagname); + kfree(filter); + } + + logger_tagname_registry_free(r); + } + mutex_unlock(&log.mutex); + + kfree(reader); + } + + if (file->f_mode & FMODE_WRITE) { + logger_policy_put(pdata->policy); + kfree(pdata->writer); + } + + kfree(file->private_data); + + return 0; +} + +/* + * logger_poll - the log's poll file operation, for poll/select/epoll + * + * Note we always return POLLOUT, because you can always write() to the log. + * Note also that, strictly speaking, a return value of POLLIN does not + * guarantee that the log is readable without blocking, as there is a small + * chance that the writer can lap the reader in the interim between poll() + * returning and the read() request. + */ +static unsigned int logger_poll(struct file *file, poll_table *wait) +{ + struct logger_pdata *pdata = file->private_data; + struct logger_reader *reader; + unsigned int ret = POLLOUT | POLLWRNORM; + + if (!(file->f_mode & FMODE_READ)) + return ret; + + reader = pdata->reader; + + if (test_bit(pdata->logger_id, &reader->logger_ids) == 0) + return ret; + + poll_wait(file, &reader->wq, wait); + + mutex_lock(&log.mutex); + if (!reader->r_all) + reader->r_off = get_next_entry_by_uid( + reader->r_off, current_euid()); + + if (log.w_off != reader->r_off) + ret |= POLLIN | POLLRDNORM; + mutex_unlock(&log.mutex); + + return ret; +} + +static long logger_set_version(struct logger_reader *reader, void __user *arg) +{ + int version; + + if (copy_from_user(&version, arg, sizeof(int))) + return -EFAULT; + + if ((version < 1) || (version > 2)) + return -EINVAL; + + reader->r_ver = version; + return 0; +} + +static long logger_set_global_priority(struct logger_reader *reader, void __user *arg) +{ + if (!get_backend_logfilter_enable()) + return -EPERM; + + if (get_user(reader->filter_data.global_pri, (log_priority __user *)arg)) { + reader->filter_data.global_pri = DLOG_SILENT; + return -EFAULT; + } + + if ((reader->filter_data.global_pri < DLOG_UNKNOWN + || reader->filter_data.global_pri > DLOG_SILENT)) { + reader->filter_data.global_pri = DLOG_SILENT; + return -EINVAL; + } + return 0; +} + +static long logger_set_logger_ids(struct logger_reader *reader, int arg) +{ + unsigned long logbuf_id = (unsigned long)arg; + int id; + + if ((logbuf_id & 0xf) == 0) + return -EINVAL; + + reader->logger_ids = 0; + for (id = 0; id < NR_LOGGER_IDS; id++) { + if (test_bit(id, &logbuf_id) != 0) { + set_bit(id, &reader->logger_ids); + pr_debug("%d log buffer is enabled", id); + } + } + set_bit(ENABLE_ONE_BUFF, &reader->logger_ids); + pr_info("Masking status of log buffer : %x", (int)reader->logger_ids); + + return 0; +} + +static long logger_set_tag_filter_info(struct logger_reader *reader, void __user *arg) +{ + void *tagname_ptr = NULL; + filter_info *karg; + log_priority prio = DLOG_UNKNOWN; + struct logger_tagname_registry *r; + __u32 hash = 0; +#ifdef CONFIG_COMPAT + if (is_compat_task()) { + struct filter_args_compat_t cuarg; + if(copy_from_user(&cuarg, arg, sizeof(struct filter_args_compat_t))) + return -EFAULT; + + tagname_ptr = compat_ptr(cuarg.tagname); + prio = cuarg.priority; + } + else +#endif + { + struct filter_args_t uarg; + if(copy_from_user(&uarg, arg, sizeof(struct filter_args_t))) + return -EFAULT; + + tagname_ptr = uarg.tagname; + prio = uarg.priority; + } + if (!get_backend_logfilter_enable()) + return -EPERM; + + r = reader->filter_data.tag_names; + if (r->name_seq_last >= DLOG_MAX_TAGS) + return -EINVAL; + + if (prio < DLOG_UNKNOWN || prio > DLOG_SILENT) + return -EINVAL; + + karg = kzalloc(sizeof(filter_info), GFP_KERNEL); + if (!karg) + return -ENOMEM; + + karg->tagname = strndup_user(tagname_ptr, DLOG_MAX_TAGNAME + 1); + if (IS_ERR(karg->tagname)) { + long ret = PTR_ERR(karg->tagname); + kfree(karg); + return ret; + } + + /* blocking zero length-ed tag */ + if (!strlen(karg->tagname)) { + kfree(karg->tagname); + kfree(karg); + return -EINVAL; + } + + karg->priority = prio; + hash = logger_strhash(karg->tagname); + hash_add(r->entries_hash, &karg->hentry, hash); + r->name_seq_last++; + + return 0; +} + +static long logger_set_pid(struct logger_reader *reader, void __user *arg) +{ + void *plist_ptr = NULL; + int plist_len = 0; + int n_pids = 0; +#ifdef CONFIG_COMPAT + if (is_compat_task()) { + struct logger_pid_list_compat p_list; + plist_len = sizeof(struct logger_pid_list_compat); + if (copy_from_user(&p_list, arg, plist_len)) { + return -EFAULT; + } + plist_ptr = compat_ptr(p_list.pid_list); + n_pids = p_list.no_of_pids; + } + else +#endif + { + struct logger_pid_list p_list; + plist_len = sizeof(struct logger_pid_list); + if (copy_from_user(&p_list, arg, plist_len)) { + return -EFAULT; + } + plist_ptr = p_list.pid_list; + n_pids = p_list.no_of_pids; + } + + reader->filter_data.pid_filter_list.no_of_pids = 0; + + if(reader->filter_data.pid_filter_list.pid_list){ + kfree(reader->filter_data.pid_filter_list.pid_list); + reader->filter_data.pid_filter_list.pid_list = NULL; + } + + if(n_pids) { + reader->filter_data.pid_filter_list.no_of_pids = n_pids; + + /* Allocate kernel memory for pid_list */ + reader->filter_data.pid_filter_list.pid_list = (pid_t *) kmalloc((reader->filter_data.pid_filter_list.no_of_pids) * sizeof(pid_t), GFP_KERNEL); + if(reader->filter_data.pid_filter_list.pid_list == NULL) { + return -ENOMEM; + } + + /* Now copy pid_list from user space to kernel space */ + if (copy_from_user(reader->filter_data.pid_filter_list.pid_list, plist_ptr, (n_pids * sizeof(pid_t))) != 0) { + kfree(reader->filter_data.pid_filter_list.pid_list); + return -EFAULT; + } + } + + return 0; +} + +static long logger_get_log_len(struct logger_reader *reader) +{ + long ret = 0; + + if (log.w_off >= reader->r_off) + ret = log.w_off - reader->r_off; + else + ret = (log.eot - reader->r_off) + log.w_off; + + return ret; +} + +static long logger_get_next_entry_len(struct logger_reader *reader) +{ + long ret = 0; + + if (!reader->r_all) + reader->r_off = get_next_entry_by_uid(reader->r_off, current_euid()); + + if (log.w_off != reader->r_off) + ret = get_user_hdr_len(reader->r_ver) + get_entry(reader->r_off)->len; + + return ret; +} + +static long logger_flush_log(struct file *file) +{ + if (!(in_egroup_p(file_inode(file)->i_gid) || capable(CAP_SYSLOG))) + return (long)-EPERM; + + pr_info("Flush log by comm:%s", current->comm); + log.head = log.w_off; + + return 0; +} + +static long logger_set_prio(struct logger_writer *writer, void __user *arg) +{ + int prio; + + prio = (int)(uintptr_t)arg; + + if (prio < DLOG_UNKNOWN || prio > DLOG_SILENT) + return -EINVAL; + + writer->prio = prio; + return 0; +} + +static long logger_set_tag(struct logger_writer *writer, void __user *arg) +{ + struct logger_tag tag; + size_t len; + + if (copy_from_user(&tag, arg, sizeof(struct logger_tag))) + return -EFAULT; + + len = tag.len - 1; + if (len > DLOG_MAX_TAGNAME) + return -EINVAL; + + if (copy_from_user(writer->tag, (void*)(uintptr_t)tag.ptr, tag.len)) + return -EFAULT; + + writer->tag[len] = '\0'; + writer->tag_len = len; + + return 0; +} + +static long logger_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct logger_pdata *pdata = file->private_data; + long ret = -EINVAL; + void __user *argp = (void __user *)arg; + + mutex_lock(&log.mutex); + + if (file->f_mode & FMODE_READ) { + struct logger_reader *reader = pdata->reader; + + switch (cmd) { + case LOGGER_GET_LOG_BUF_SIZE: + ret = log.size; + break; + case LOGGER_GET_LOG_LEN: + ret = logger_get_log_len(reader); + break; + case LOGGER_GET_NEXT_ENTRY_LEN: + ret = logger_get_next_entry_len(reader); + break; + case LOGGER_GET_VERSION: + ret = reader->r_ver; + break; + case LOGGER_SET_VERSION: + ret = logger_set_version(reader, argp); + break; + case LOGGER_SET_DEV_ID: + ret = logger_set_logger_ids(reader, (int)arg); + break; + case LOGGER_GET_BACKEND_FILTER_STATE: + ret = get_backend_logfilter_enable(); + break; + case LOGGER_SET_GLOBAL_PRIORITY: + ret = logger_set_global_priority(reader, argp); + break; + case LOGGER_SET_TAG_FILTER_INFO: + ret = logger_set_tag_filter_info(reader, argp); + break; + case LOGGER_SET_PID: + ret = logger_set_pid(reader, argp); + break; + case LOGGER_SET_EXTENDED_BUFFER: + ret = logger_ext_enable(); + break; + } + } + + if (file->f_mode & FMODE_WRITE) { + struct logger_writer *writer = pdata->writer; + + switch (cmd) { + case LOGGER_FLUSH_LOG: + ret = logger_flush_log(file); + break; + case LOGGER_SET_PRIO: + ret = logger_set_prio(writer, argp); + break; + case LOGGER_SET_TAG: + ret = logger_set_tag(writer, argp); + break; + } + } + + mutex_unlock(&log.mutex); + + return ret; +} + +static const struct file_operations logger_fops = { + .owner = THIS_MODULE, + .read = logger_read, + .write_iter = logger_write_iter, + .poll = logger_poll, + .unlocked_ioctl = logger_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = logger_ioctl, +#endif + .open = logger_open, + .release = logger_release, +}; + +static int __init logger_init(void) +{ + char *device_name[] = { + [LOGGER_ID_MAIN] = LOGGER_LOG_MAIN, + [LOGGER_ID_EVENTS] = LOGGER_LOG_EVENTS, + [LOGGER_ID_RADIO] = LOGGER_LOG_RADIO, + [LOGGER_ID_SYSTEM] = LOGGER_LOG_SYSTEM, + }; + int ret, i; + + INIT_LIST_HEAD(&log.readers); + mutex_init(&log.mutex); + log.w_off = 0; + log.head = 0; + log.size = log.eot = LOGGER_BUFFER_SIZE; + + for (i = LOGGER_ID_MAIN; i < NR_LOGGER_IDS; i++) { + log.miscs[i].minor = MISC_DYNAMIC_MINOR; + log.miscs[i].name = device_name[i]; + log.miscs[i].fops = &logger_fops; + log.miscs[i].mode = (S_IRUGO | S_IWUGO); +#ifdef CONFIG_SECURITY_SMACK_SET_DEV_SMK_LABEL + log.miscs[i].lab_smk64 = LOGGER_SMACK_LABEL, +#endif + ret = misc_register(&log.miscs[i]); + if (unlikely(ret)) { + pr_err("failed to register misc device for log '%s'!\n", device_name[i]); + goto out; + } + } + + ret = logger_policy_init(); + if (unlikely(ret)) + goto out; + + ret = logger_stats_init(); + + logger_ext_enable(); + vlogger_init(); + +out: + return ret; +} + +static void __exit logger_exit(void) +{ + int i; + + logger_ext_disable(); + logger_stats_exit(); + logger_policy_exit(); + vlogger_exit(); + + for (i = LOGGER_ID_MAIN; i < NR_LOGGER_IDS; i++) + misc_deregister(&log.miscs[i]); + + if (log.buffer) + vfree(log.buffer); +} + +device_initcall(logger_init); +module_exit(logger_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Robert Love, "); +MODULE_DESCRIPTION("Android Logger"); diff --git a/kernel/vlogger/logger_policy.c b/kernel/vlogger/logger_policy.c new file mode 100644 index 0000000..a3c5f57 --- /dev/null +++ b/kernel/vlogger/logger_policy.c @@ -0,0 +1,839 @@ +/* + * logger_policy: A Logging Policy for Tizen TV. + * + * Copyright (C) 2016 Samsung Electronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#define pr_fmt(fmt) "logger: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "logger.h" +#include "logger_policy.h" +#include "logger_filter.h" + +static LIST_HEAD(policy_filter_list); +static DEFINE_MUTEX(policy_filter_lock); +static atomic_t policy_filter_count = ATOMIC_INIT(0); + +static LIST_HEAD(policy_qos_bypass_list); +static DEFINE_MUTEX(policy_qos_bypass_lock); +static int policy_qos_bypass_count = -1; + +static LIST_HEAD(policy_qos_controller_list); +static DEFINE_MUTEX(policy_qos_controller_lock); +static int policy_qos_controller_count = 0; + +static int policy_min_priority = 3; +static int policy_max_priority = 6; +static int backend_filter_enabled = 0; +static int verbose_mode_enabled = 0; + +module_param_named(min_priority, policy_min_priority, int, 0644); +module_param_named(max_priority, policy_max_priority, int, 0644); +module_param_named(backend_filter, backend_filter_enabled, int, 0644); +module_param_named(verbose_mode, verbose_mode_enabled, int, 0644); + +static atomic_t policy_status = ATOMIC_INIT(1); +static atomic_t policy_logger_enable = ATOMIC_INIT(1); + +/* hash table size(2^14) is bigger than max user processes(13233) */ +#define POLICY_HASH_BIT (14) + +static DECLARE_HASHTABLE(policy_hash, POLICY_HASH_BIT); +static DEFINE_MUTEX(policy_lock); + +static int policy_qos_limit = 0; +static int policy_qos_limit_default = 0; +static int policy_qos_interval = 10; +static int policy_qos_controller_main = 0; + +module_param_named(qos_limit, policy_qos_limit_default, int, 0644); +module_param_named(qos_interval, policy_qos_interval, int, 0644); + +/** + * get_backend_logfilter_enable() - get backend_filter_enabled value + */ +int get_backend_logfilter_enable() { + return !!backend_filter_enabled; +} + +/** + * qos_is_available() - return true if qos is available + */ +static inline bool qos_is_available(void) +{ + return (policy_qos_limit_default != 0); +} + +/** + * set_policy_status() - set policy status + * @status: the value for status + */ +static inline void set_policy_status(int status) +{ + atomic_set(&policy_status, !!status); + pr_info("policy_status is set to %d\n", atomic_read(&policy_status)); +} + +/** + * get_policy_status() - get policy status + */ +static inline int get_policy_status(void) +{ + return atomic_read(&policy_status); +} + +/** + * set_policy_logger_enable() - set logger status + * @enable: the value for logger status + */ +static inline void set_policy_logger_enable(int enable) +{ + atomic_set(&policy_logger_enable, !!enable); + pr_info("policy_logger_enable is set to %d\n", atomic_read(&policy_logger_enable)); +} + +/** + * get_policy_logger_enable() - get logger status + */ +static inline int get_policy_logger_enable(void) +{ + return atomic_read(&policy_logger_enable); +} + +/** + * get_policy_tag() - return a tag, if it does not exist, make a new one + * @qos: the qos structure + * @name: the name of tag + */ +static struct policy_tag *get_policy_tag(struct policy_qos *qos, + const char *name) +{ + struct policy_tag *tag; + + list_for_each_entry(tag, &qos->tag_list, node) { + if (!strcmp(tag->name, name)) + return tag; + } + + tag = kzalloc(sizeof(*tag), GFP_KERNEL); + if (tag) { + strncpy(tag->name, name, sizeof(tag->name) - 1); + list_add_tail(&tag->node, &qos->tag_list); + + qos->num_tags++; + return tag; + } + + return NULL; +} + +/** + * set_policy_qos_bypass() - toggle a qos_bypass process by the given name + * @name: the name of qos_bypass process + */ +static int set_policy_qos_bypass(const char *name) +{ + struct policy_qos_bypass *qos_bypass, *tmp_qos_bypass; + + mutex_lock(&policy_qos_bypass_lock); + list_for_each_entry_safe(qos_bypass, tmp_qos_bypass, &policy_qos_bypass_list, node) { + if (!strncmp(qos_bypass->name, name, strlen(qos_bypass->name))) { + list_del(&qos_bypass->node); + kfree(qos_bypass); + policy_qos_bypass_count--; + + mutex_unlock(&policy_qos_bypass_lock); + pr_info("remove %s to qos_bypass\n", name); + + return 0; + } + } + + qos_bypass = kzalloc(sizeof(*qos_bypass), GFP_KERNEL); + if (!qos_bypass) { + mutex_unlock(&policy_qos_bypass_lock); + return -ENOMEM; + } + + strncpy(qos_bypass->name, name, sizeof(qos_bypass->name)); + qos_bypass->name[LOGGER_POLICY_MAX_TASK_NAME - 1] = '\0'; + list_add_tail(&qos_bypass->node, &policy_qos_bypass_list); + + if (policy_qos_bypass_count == -1) + policy_qos_bypass_count = 0; + policy_qos_bypass_count++; + + mutex_unlock(&policy_qos_bypass_lock); + pr_info("add %s to qos_bypass\n", name); + + return 0; +} + +/** + * update_policy_qos_bypass() - check if the process could bypass qos filter or not, and set qos_bypass property. + * @qos: the qos property structure of the process + */ +static bool update_policy_qos_bypass(struct policy_qos *qos) +{ + struct policy_qos_bypass *qos_bypass; + char task_comm[LOGGER_POLICY_MAX_TASK_NAME]; + + mutex_lock(&policy_qos_bypass_lock); + + if (policy_qos_bypass_count == -1) { + mutex_unlock(&policy_qos_bypass_lock); + return false; + } + + get_task_comm(task_comm, current->group_leader); + list_for_each_entry(qos_bypass, &policy_qos_bypass_list, node) { + if (!strncmp(qos_bypass->name, task_comm, strlen(qos_bypass->name))) { + if (!qos->qos_bypass) { + qos->qos_bypass = 1; + pr_info("Set qos_bypass process : %s", task_comm); + } + mutex_unlock(&policy_qos_bypass_lock); + return true; + } + } + + if (qos->qos_bypass) { + qos->qos_bypass = 0; + pr_info("Unset qos_bypass process : %s", task_comm); + } + + mutex_unlock(&policy_qos_bypass_lock); + return false; +} + +/** + * set_policy_filter() - toggle a filter by the given name + * @name: the name of filter + */ +static int set_policy_filter(const char *name) +{ + struct policy_filter *filter, *tmp_filter; + int ret = -ENOMEM; + + mutex_lock(&policy_filter_lock); + list_for_each_entry_safe(filter, tmp_filter, &policy_filter_list, node) { + if (!strcmp(filter->name, name)) { + list_del(&filter->node); + kfree(filter); + atomic_dec(&policy_filter_count); + + mutex_unlock(&policy_filter_lock); + pr_info("remove %s to filter\n", name); + + return 0; + } + } + + filter = kzalloc(sizeof(*filter), GFP_KERNEL); + if (filter) { + strncpy(filter->name, name, sizeof(filter->name) - 1); + list_add_tail(&filter->node, &policy_filter_list); + atomic_inc(&policy_filter_count); + + ret = 0; + } + + mutex_unlock(&policy_filter_lock); + pr_info("add %s to filter\n", name); + + return ret; +} + +/** + * check_policy_filter() - check a filter whether it does exist or not + * @name: the name of filter + */ +static int check_policy_filter(const char *name) +{ + struct policy_filter *filter; + + if (!atomic_read(&policy_filter_count)) + return -EPERM; + + mutex_lock(&policy_filter_lock); + list_for_each_entry(filter, &policy_filter_list, node) { + if (!strcmp(filter->name, name) || !strcmp(filter->name, "*")) { + mutex_unlock(&policy_filter_lock); + return 0; + } + } + mutex_unlock(&policy_filter_lock); + return -EPERM; +} + +/** + * check_policy_qos() - check a qos with the given arguments + * @qos: the qos structure + * @name: the name of tag + */ +static int check_policy_qos(struct policy_qos *qos, const char *name) +{ + struct policy_tag *tag; + int upper_limit = policy_qos_limit * policy_qos_interval; + int ret = 0; + int ts = (int)jiffies / HZ; + + if (upper_limit == 0) + return 0; + + mutex_lock(&qos->lock); + + if (ts >= qos->reset_timer) { + int total_dropped = 0; + bool tag_found = false; + + qos->activated = false; + qos->count = 1; + qos->reset_timer = ts + policy_qos_interval; + + update_policy_qos_bypass(qos); + + list_for_each_entry(tag, &qos->tag_list, node) { + if (!tag_found && !strcmp(tag->name, name)) { + tag->count = 1; + tag_found = true; + } else { + tag->count = 0; + } + + if (tag->limited) { + pr_info("%s (p:%d)'s \"%s\" tagged %d logs were dropped\n", + current->group_leader->comm, current->tgid, + tag->name, tag->limited); + total_dropped += tag->limited; + tag->limited = 0; + } + } + + if (total_dropped) + pr_err("%s (p:%d)'s %d logs were dropped from %d tags\n", + current->group_leader->comm, current->tgid, + total_dropped, qos->num_tags); + goto out; + } + + if (qos->qos_bypass) + goto out; + + /* check process */ + if (qos->count < upper_limit) { + qos->count++; + + tag = get_policy_tag(qos, name); + if (tag) + tag->count++; + } else { + if (!qos->activated) { + pr_err("%s (p:%d) exceeds the log limit:%d\n", + current->group_leader->comm, current->tgid, upper_limit); + qos->activated = true; + qos->reset_timer = ts + policy_qos_interval; + } + + /* check tag */ + tag = get_policy_tag(qos, name); + if (tag) { + int num_tags = min_t(int, qos->num_tags, LOGGER_POLICY_MAX_TAG_COUNT); + + if (tag->count < (upper_limit / num_tags)) { + tag->count++; + } else { + tag->limited++; + ret = -EPERM; + } + } + } + +out: + mutex_unlock(&qos->lock); + return ret; +} + +/** + * logger_policy_check() - check a policy for the associated logger + * @policy: the policy structure + * @priority: the priority of log + * @tag: the tag of log + */ +int logger_policy_check(struct logger_policy *policy, uint8_t priority, const char *tag) +{ + /* if policy is disabled, return immediately to print the log. */ + if (!get_policy_status()) + return 0; + + /* if logger is disabled, don't do anything. */ + if (!get_policy_logger_enable()) + return -EPERM; + + /* check policies. */ + if (priority <= policy_min_priority) { + if (check_policy_filter(tag)) + return -EPERM; + } else if (priority > policy_min_priority && priority < policy_max_priority) { + if (check_policy_qos(&policy->qos, tag)) + return -EDQUOT; + } + + return 0; +} + +/* hash key is only based on PID so as to avoid duplicate policy structure creation in case of launchpad loaded apps */ +static unsigned int get_hash(int pid) +{ + /* fnv32 hash */ + unsigned int hash = 2166136261U + pid; + int p = pid; + + while(p) { + hash = (hash ^ ((p%10) + 0x30)) * 0x01000193; + p /= 10; + } + return (hash % (1<tgid); + + mutex_lock(&policy_lock); + hash_for_each_possible(policy_hash, policy, node, hash_key) { + if (policy->pid == current->tgid) { + policy->ref ++; + mutex_unlock(&policy_lock); + return policy; + } + } + mutex_unlock(&policy_lock); + if (verbose_mode_enabled) + pr_err("logger_open called by %d:%s", (int)current->tgid, current->group_leader->comm); + policy = kzalloc(sizeof(*policy), GFP_KERNEL); + if (!policy) + return ERR_PTR(-ENOMEM); + + update_policy_qos_bypass(&policy->qos); + + INIT_LIST_HEAD(&policy->qos.tag_list); + mutex_init(&policy->qos.lock); + + policy->pid = current->tgid; + policy->ref = 1; + + mutex_lock(&policy_lock); + hash_add(policy_hash, &policy->node, hash_key); + mutex_unlock(&policy_lock); + + return policy; +} + +/** + * logger_policy_put() - release a policy structure + * @policy: the policy structure to release + */ +void logger_policy_put(struct logger_policy *policy) +{ + struct policy_qos *qos = &policy->qos; + struct policy_tag *tag, *tmp_tag; + + if (-- policy->ref > 0) + return; + + mutex_lock(&qos->lock); + list_for_each_entry_safe(tag, tmp_tag, &qos->tag_list, node) { + list_del(&tag->node); + kfree(tag); + } + mutex_unlock(&qos->lock); + + mutex_lock(&policy_lock); + hash_del(&policy->node); + mutex_unlock(&policy_lock); + kfree(policy); +} + +/* file ops. */ +static long logger_policy_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + char name[LOGGER_POLICY_MAX_TAG_NAME + 1] = {0, }; + int var; + int err = 0; + + void __user *argp = (void __user *)arg; + + switch (cmd) { + case LOGGER_POLICY_SET_FILTER: + if (copy_from_user(name, argp, sizeof(name) - 1)) + return -EFAULT; + + err = set_policy_filter(name); + break; + case LOGGER_POLICY_SET_STATUS: + if (copy_from_user(&var, argp, sizeof(var))) + return -EFAULT; + + set_policy_status(var); + break; + case LOGGER_POLICY_SET_LOGGER_ENABLE: + if (copy_from_user(&var, argp, sizeof(var))) + return -EFAULT; + + set_policy_logger_enable(var); + break; + case LOGGER_POLICY_SET_QOS: + if (!qos_is_available()) + return -EPERM; + + if (policy_qos_controller_main != current->tgid) + return -EPERM; + + if (copy_from_user(&var, argp, sizeof(var))) + return -EFAULT; + + if (var < 0) + return -EINVAL; + + policy_qos_limit = var; + pr_info("policy qos limit : %d\n", policy_qos_limit); + break; + default: + err = -EINVAL; + break; + } + + return err; +} + +static int logger_policy_open(struct inode *inode, struct file *file) +{ + if (!qos_is_available()) + return -EPERM; + + if (file->f_mode & FMODE_WRITE) { + struct policy_qos_controller *qos_controller; + + qos_controller = kzalloc(sizeof(*qos_controller), GFP_KERNEL); + if (!qos_controller) + return -ENOMEM; + + policy_qos_limit = policy_qos_limit_default; + qos_controller->pid = (int)current->tgid; + + mutex_lock(&policy_qos_controller_lock); + list_add(&qos_controller->node, &policy_qos_controller_list); + policy_qos_controller_count++; + policy_qos_controller_main = qos_controller->pid; + mutex_unlock(&policy_qos_controller_lock); + pr_err("set qos controller PID:%d", policy_qos_controller_main); + } + return 0; +} + +static int logger_policy_release(struct inode *ignored, struct file *file) +{ + if (file->f_mode & FMODE_WRITE) { + struct policy_qos_controller *controller, *tmp_controller; + + mutex_lock(&policy_qos_controller_lock); + list_for_each_entry_safe(controller, tmp_controller, &policy_qos_controller_list, node) { + if (controller->pid == (int)current->tgid) { + list_del(&controller->node); + kfree(controller); + policy_qos_controller_count--; + pr_info("release qos controller : %d(remain:%d), main:%d", + current->tgid, policy_qos_controller_count, policy_qos_controller_main); + } + } + + if (policy_qos_controller_main == (int)current->tgid) { + struct policy_qos_controller *latest_controller; + int main_controller_next; + + if (policy_qos_controller_count == 0) { + main_controller_next = 0; + policy_qos_limit = policy_qos_limit_default; + } else { + latest_controller = list_first_entry(&policy_qos_controller_list, struct policy_qos_controller, node); + main_controller_next = latest_controller->pid; + } + + pr_err("reset qos controller : %d to %d", + policy_qos_controller_main, main_controller_next); + policy_qos_controller_main = main_controller_next; + } + + mutex_unlock(&policy_qos_controller_lock); + } + return 0; +} + +static const struct file_operations logger_policy_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = logger_policy_ioctl, +#if defined CONFIG_COMPAT + .compat_ioctl = logger_policy_ioctl, // modified ioctl to get same number +#endif + .open = logger_policy_open, + .release = logger_policy_release, +}; + +/* sysfs callbacks */ +static ssize_t filter_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct policy_filter *filter; + int len = 0; + + mutex_lock(&policy_filter_lock); + list_for_each_entry(filter, &policy_filter_list, node) + len += snprintf(buf + len, PAGE_SIZE - len, "%s\n", filter->name); + mutex_unlock(&policy_filter_lock); + + return len; +} + +static ssize_t filter_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + char name[LOGGER_POLICY_MAX_TAG_NAME + 1] = {0, }; + int ret; + + if (count > LOGGER_POLICY_MAX_TAG_NAME + 1) + return -EINVAL; + + ret = sscanf(buf, "%32s", name); + if (ret != 1) + return -EINVAL; + + ret = set_policy_filter(name); + return (ret == 0 ? count : ret); +} +static DEVICE_ATTR_RW(filter); + +static ssize_t backend_logfilter_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", get_backend_logfilter_enable()); +} + +static DEVICE_ATTR_RO(backend_logfilter); + +static ssize_t status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", get_policy_status()); +} + +static ssize_t status_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int value; + + if (kstrtoint(buf, 10, &value) < 0) { + pr_err("Failed to get value(%m)"); + return 0; + } + set_policy_status(value); + + return count; +} +static DEVICE_ATTR_RW(status); + +static ssize_t logger_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", get_policy_logger_enable()); +} + +static ssize_t logger_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int value; + + if (kstrtoint(buf, 10, &value) < 0) { + pr_err("Failed to get value(%m)"); + return 0; + } + + set_policy_logger_enable(value); + + return count; +} +static DEVICE_ATTR_RW(logger_enable); + +static ssize_t qos_interval_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", policy_qos_interval); +} + +static ssize_t qos_interval_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int value; + + if (kstrtoint(buf, 10, &value) < 0) { + pr_err("Failed to get value(%m)"); + return 0; + } + + if (value > 0) + policy_qos_interval = value; + + return count; +} +static DEVICE_ATTR_RW(qos_interval); + +static ssize_t qos_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "limit:%d interval:%d\n", + policy_qos_limit, policy_qos_interval); +} + +static ssize_t qos_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int value; + + if (!qos_is_available()) { + pr_err("QoS is not available"); + return 0; + } + + if (kstrtoint(buf, 10, &value) < 0) { + pr_err("Failed to get value(%m)"); + return 0; + } + + if (value < 0) { + pr_err("The value is invalid: %d", value); + return 0; + } + + policy_qos_limit = value; + pr_info("policy qos limit : %u\n", policy_qos_limit); + + return count; +} + +static DEVICE_ATTR_RW(qos); + +static ssize_t qos_bypass_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct policy_qos_bypass *qos_bypass; + int len = 0; + + mutex_lock(&policy_qos_bypass_lock); + list_for_each_entry(qos_bypass, &policy_qos_bypass_list, node) + len += snprintf(buf + len, PAGE_SIZE - len, "%s\n", qos_bypass->name); + + mutex_unlock(&policy_qos_bypass_lock); + + return len; +} + +static ssize_t qos_bypass_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + char name[LOGGER_POLICY_MAX_TASK_NAME] = {0, }; + int ret; + + if (count > LOGGER_POLICY_MAX_TASK_NAME) + return -EINVAL; + + ret = sscanf(buf, "%16s", name); + if (ret != 1) + return -EINVAL; + + ret = set_policy_qos_bypass(name); + return (ret == 0 ? count : ret); +} +static DEVICE_ATTR_RW(qos_bypass); + +static struct attribute *logger_policy_attributes[] = { + &dev_attr_filter.attr, + &dev_attr_status.attr, + &dev_attr_logger_enable.attr, + &dev_attr_qos_interval.attr, + &dev_attr_qos.attr, + &dev_attr_backend_logfilter.attr, + &dev_attr_qos_bypass.attr, + NULL, +}; + +static const struct attribute_group logger_policy_attr_group = { + .attrs = logger_policy_attributes, +}; + +static const char miscdev_name[] = LOGGER_POLICY_MISCDEV_NAME; +static struct miscdevice logger_policy_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = miscdev_name, + .fops = &logger_policy_fops, + .mode = (S_IRUGO|S_IWUGO), +#ifdef CONFIG_SECURITY_SMACK_SET_DEV_SMK_LABEL + .lab_smk64 = LOGGER_POLICY_SMACK_LABEL, +#endif +}; + +int logger_policy_init(void) +{ + struct device *dev; + int err = 0; + + err = misc_register(&logger_policy_miscdev); + if (err) + goto err_exit; + + dev = logger_policy_miscdev.this_device; + err = sysfs_create_group(&dev->kobj, &logger_policy_attr_group); + if (err) { + dev_err(dev, "failed to create sysfs nodes with (%d) error\n", err); + goto err_deregister; + } + + hash_init(policy_hash); + policy_qos_limit = policy_qos_limit_default; + + return 0; + +err_deregister: + misc_deregister(&logger_policy_miscdev); +err_exit: + return err; +} + +void logger_policy_exit(void) +{ + struct device *dev = logger_policy_miscdev.this_device; + + sysfs_remove_group(&dev->kobj, &logger_policy_attr_group); + misc_deregister(&logger_policy_miscdev); +} + diff --git a/kernel/vlogger/logger_stats.c b/kernel/vlogger/logger_stats.c new file mode 100644 index 0000000..2c925f7 --- /dev/null +++ b/kernel/vlogger/logger_stats.c @@ -0,0 +1,292 @@ +/* + * logger_stats: Logger Stats for Tizen TV. + * + * Copyright (C) 2019 Samsung Electronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "logger.h" +#include "logger_policy.h" +#include "logger_stats.h" + +/** + * struct logger_stats - statistics data for logger + * @bytes: number of bytes + * @count: count of logs + */ +struct logger_stats { + u64 bytes; + u64 count; +}; + +static DEFINE_MUTEX(g_stats_lock); +static int g_stats_enable = 0; +static pid_t g_owner = 0; +static struct logger_stats g_stats[NR_LOGGER_IDS][LOGGER_STATS_MAX] = {0,}; +static char g_dlogutil_stats[LOGGER_STATS_SIZE] = {0, }; + +module_param_named(stats_enable, g_stats_enable, int, 0644); + +/* + * logger_update_stats - update log stats + */ +void logger_stats_update(int stats_id, int log_id, int bytes, int pid) +{ + if (!g_stats_enable) + return; + + if ((stats_id == LOGGER_STATS_READ || stats_id == LOGGER_STATS_OVERFLOW) && pid != g_owner) + return; + + mutex_lock(&g_stats_lock); + g_stats[log_id][stats_id].bytes += bytes; + g_stats[log_id][stats_id].count ++; + mutex_unlock(&g_stats_lock); +} + +/* + * The caller needs to hold g_stats_lock. + */ +static int _print_stats(struct logger_stats st[LOGGER_STATS_MAX], char *buf) +{ + const char *HD[LOGGER_STATS_MAX] = {"write", "read", "overflow", "drop"}; + int i; + int len = 0; + + for (i = 0; i < LOGGER_STATS_MAX; i++) { + int r = snprintf(buf, PAGE_SIZE - len, "%s:%llu(%llu) ", + HD[i], st[i].bytes, st[i].count); + if (r < 0) { + pr_err("Failed to write stats to buffer(%m)"); + return -1; + } + buf += r; + len += r; + if (len > PAGE_SIZE) { + pr_err("Buffer overflow"); + return -1; + } + } + *(buf-1) = '\n'; + + return len; +} + +#define LOGGER_STAT_ATTR(dev, idx) \ +static ssize_t dev##_show(struct device *d, struct device_attribute *a, char *b) \ +{ \ + int ret; \ + mutex_lock(&g_stats_lock); \ + ret = _print_stats(g_stats[idx], b); \ + mutex_unlock(&g_stats_lock); \ + return ret; \ +} \ +static DEVICE_ATTR_RO(dev) + +LOGGER_STAT_ATTR(main, LOGGER_ID_MAIN); +LOGGER_STAT_ATTR(events, LOGGER_ID_EVENTS); +LOGGER_STAT_ATTR(radio, LOGGER_ID_RADIO); +LOGGER_STAT_ATTR(system, LOGGER_ID_SYSTEM); + +static ssize_t total_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct logger_stats total[LOGGER_STATS_MAX] = {0, }; + int i, j; + int ret; + + mutex_lock(&g_stats_lock); + for (i = 0; i < LOGGER_STATS_MAX; i++) { + for (j = 0; j < NR_LOGGER_IDS; j++) { + total[i].bytes += g_stats[j][i].bytes; + total[i].count += g_stats[j][i].count; + } + } + ret = _print_stats(total, buf); + mutex_unlock(&g_stats_lock); + + return ret; +} + +static DEVICE_ATTR_RO(total); + +static ssize_t dlogutil_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + + if (g_owner == 0) + return 0; + + mutex_lock(&g_stats_lock); + ret = snprintf(buf, PAGE_SIZE, "pid:%d %s\n", g_owner, g_dlogutil_stats); + mutex_unlock(&g_stats_lock); + + return ret; +} + +static DEVICE_ATTR_RO(dlogutil); + +static ssize_t enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", g_stats_enable); +} + +static ssize_t enable_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(%m)"); + return 0; + } + + g_stats_enable = !!value; + + if (g_stats_enable == 0) { + mutex_lock(&g_stats_lock); + memset(g_stats, 0, sizeof(g_stats)); + memset(g_dlogutil_stats, 0, sizeof(g_dlogutil_stats)); + mutex_unlock(&g_stats_lock); + } + return count; +} +static DEVICE_ATTR_RW(enable); + +static struct attribute *logger_stats_attributes[] = { + &dev_attr_main.attr, + &dev_attr_events.attr, + &dev_attr_radio.attr, + &dev_attr_system.attr, + &dev_attr_total.attr, + &dev_attr_dlogutil.attr, + &dev_attr_enable.attr, + NULL, +}; + +static const struct attribute_group logger_stats_attr_group = { + .attrs = logger_stats_attributes, +}; + +static long logger_stats_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *parg = (void __user *)arg; + long ret = 0; + + if (!g_stats_enable) + return -EPERM; + + switch (cmd) { + case LOGGER_STATS_SEND: + mutex_lock(&g_stats_lock); + if (copy_from_user(g_dlogutil_stats, parg, sizeof(g_dlogutil_stats))) + ret = -EFAULT; + + mutex_unlock(&g_stats_lock); + break; + default: + return -EINVAL; + } + return ret; +} + +static int logger_stats_open(struct inode *inode, struct file *file) +{ + int ret = 0; + + if (file->f_mode & FMODE_WRITE) { + mutex_lock(&g_stats_lock); + if (g_owner) { + pr_err("Write permission has been taken by %d", g_owner); + ret = -EPERM; + } else { + g_owner = current->tgid; + } + mutex_unlock(&g_stats_lock); + } + return ret; +} + +static int logger_stats_release(struct inode *ignored, struct file *file) +{ + int i; + + if (file->f_mode & FMODE_WRITE) { + mutex_lock(&g_stats_lock); + g_owner = 0; + for (i = 0; i < NR_LOGGER_IDS; i++) { + memset(&g_stats[i][LOGGER_STATS_READ], 0, sizeof(struct logger_stats)); + memset(&g_stats[i][LOGGER_STATS_OVERFLOW], 0, sizeof(struct logger_stats)); + } + memset(g_dlogutil_stats, 0, sizeof(g_dlogutil_stats)); + mutex_unlock(&g_stats_lock); + } + return 0; +} + +static const struct file_operations logger_stats_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = logger_stats_ioctl, +#if defined CONFIG_COMPAT + .compat_ioctl = logger_stats_ioctl, +#endif + .open = logger_stats_open, + .release = logger_stats_release, +}; + +static const char miscdev_name[] = LOGGER_STATS_MISCDEV_NAME; +static struct miscdevice logger_stats_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = miscdev_name, + .fops = &logger_stats_fops, + .mode = (S_IRUGO|S_IWUGO), +#ifdef CONFIG_SECURITY_SMACK_SET_DEV_SMK_LABEL + .lab_smk64 = LOGGER_STATS_SMACK_LABEL, +#endif +}; + +int logger_stats_init(void) +{ + struct device *dev; + int err = 0; + + err = misc_register(&logger_stats_miscdev); + if (err) + goto err_exit; + + dev = logger_stats_miscdev.this_device; + err = sysfs_create_group(&dev->kobj, &logger_stats_attr_group); + if (err) { + dev_err(dev, "failed to create sysfs nodes with (%d) error\n", err); + goto err_deregister; + } + + g_stats_enable = 1; + + return 0; + +err_deregister: + misc_deregister(&logger_stats_miscdev); +err_exit: + return err; +} + +void logger_stats_exit(void) +{ + struct device *dev = logger_stats_miscdev.this_device; + + sysfs_remove_group(&dev->kobj, &logger_stats_attr_group); + misc_deregister(&logger_stats_miscdev); +} diff --git a/kernel/vlogger/vlogger.c b/kernel/vlogger/vlogger.c new file mode 100644 index 0000000..58e3906 --- /dev/null +++ b/kernel/vlogger/vlogger.c @@ -0,0 +1,610 @@ +/* + * drivers/misc/vlogger.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) "vlogger: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_TIZEN_VLOGGER +#include +#endif + +#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 MAX_THREAD (0x8000) +#define MS_PER_SEC (1000) +#define NS_PER_SEC (1000000000UL) + +#define IOCTL_COMMAND_ALLOC (20745321) +#define VLOGGER_DEVICE_NAME "vlog" +#define VLOGGER_SMACK_LABEL "*" + +#define MAX_TAG_SIZE (32) +#define MAX_MSG_SIZE (140) + +#define BLOCK_RATIO(count) (count*100/BLOCK_COUNT) + +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; + uint16_t pid; + uint16_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; +}; + +static struct miscdevice vlogger_device; + +static int g_init; +static char *g_shm_ptr[DEVICE_COUNT]; +static struct thread_t *g_threads; + +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_vlog_enable = 1; +module_param_named(vlog_enable, g_vlog_enable, int, 0644); + +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 & 0x7FF; + char *p = get_shared_memory(index >> 11); + + if (!p) { + pr_err("[INVALID BLOCK] index:%d free:%d err:%d", block_index, g_free_q.count, g_err_count); + dump_stack(); + WARN_ON(1); + } + + return (struct block_t *)(p + (offset << 11)); +} + +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 vlog_task(void *user_data) +{ + int i; + uint16_t blk; + + do { + for (i = 1; i <= g_max_thread_id; i++) { + blk = g_threads[i].block; + if (blk && get_block(blk)->head.ts < g_start_time) { + mutex_lock(&g_block_mutex); + queue_push(&g_free_q, blk); + g_threads[i].block = 0; + mutex_unlock(&g_block_mutex); + g_free_count++; + } + } + 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) { + mutex_unlock(&g_task_mutex); + complete(&g_completion); + return; + } + g_task_on = 1; + mutex_unlock(&g_task_mutex); + + task = kthread_run(vlog_task, NULL, "vlog_task"); + if (IS_ERR(task)) { + pr_err("Failed to run vlog_task\n"); + g_task_on = 0; + } +} + +static long alloc_block_for_thread(void) +{ + int pid = current->tgid; + int tid = current->pid; + uint16_t blk; + struct block_t *block; + + if (tid >= MAX_THREAD) { + pr_err("Invalid tid: %d", tid); + return -EINVAL; + } + + if (g_max_thread_id < tid) + g_max_thread_id = tid; + + mutex_lock(&g_block_mutex); + blk = g_threads[tid].block; + if (blk) + queue_push(&g_free_q, blk); + blk = queue_pop(&g_free_q); + g_threads[tid].block = blk; + mutex_unlock(&g_block_mutex); + + 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); + return -ENOMEM; + } + + block = get_block(blk); + + 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; + + return (long)blk; +} + +static int vlogger_open(struct inode *inodep, struct file *filep) +{ + return 0; +} + +static int vlogger_release(struct inode *inodep, struct file *filep) +{ + return 0; +} + +static int vlogger_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 + page = virt_to_page((unsigned long)g_threads); + + return remap_pfn_range(vma, vma->vm_start, page_to_pfn(page), size, vma->vm_page_prot); +} + +static ssize_t vlogger_read(struct file *filep, char *buffer, size_t len, loff_t *offset) +{ + pr_err("read failed!\n"); + return -EPERM; +} + +static ssize_t vlogger_write(struct file *filep, const char *buffer, size_t len, loff_t *offset) +{ + pr_err("write failed!\n"); + return -EPERM; +} + +static long vlogger_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + if (cmd == IOCTL_COMMAND_ALLOC) + return alloc_block_for_thread(); + + return -EINVAL; +} + +static const struct file_operations vlogger_fops = { + .open = vlogger_open, + .read = vlogger_read, + .write = vlogger_write, + .release = vlogger_release, + .mmap = vlogger_mmap, + .unlocked_ioctl = vlogger_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = vlogger_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; + + for (i = 0; i < MAX_THREAD; i++) { + if (g_threads[i].block > 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\n", g_thread, g_threads[g_thread].block); +} + +static DEVICE_ATTR_RW(thread); + +static struct attribute *vlogger_attributes[] = { + &dev_attr_status.attr, + &dev_attr_time.attr, + &dev_attr_block.attr, + &dev_attr_thread.attr, + NULL, +}; + +static const struct attribute_group vlogger_attr_group = { + .attrs = vlogger_attributes, +}; + +#ifdef CONFIG_TIZEN_VLOGGER +static inline struct block_t *get_valid_block(int tid, size_t len) +{ + uint16_t blk = 0; + long r; + + if (g_threads == NULL) + return NULL; + + blk = g_threads[tid].block; + + 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(); + if (r <= 0) + return NULL; + + return get_block((uint16_t)r); +} + +static int _vlog_write(const unsigned char prio, const char *tag, const char *msg) +{ + uint64_t ts = ktime_get_ns(); + uint16_t *slt = (uint16_t *)&ts; + const int 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); + struct entry_t *entry; + struct entry_t tmp; + + if (block == NULL) + return -ENOMEM; + + entry = (struct entry_t *)(block->data + block->head.offset); + + if ((char *)entry + entry_size > (char *)block + V_BLOCK_SIZE) { + 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] = (char)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 struct vlogger_ops g_vlog_ops = { + .write = _vlog_write +}; +#endif + +int vlogger_init(void) +{ + int i = 0; + int r = 0; + + if (!g_vlog_enable) { + pr_info("vlog is disable\n"); + return 0; + } + + vlogger_device.minor = MISC_DYNAMIC_MINOR; + vlogger_device.name = VLOGGER_DEVICE_NAME; + vlogger_device.fops = &vlogger_fops; + vlogger_device.mode = 0666; +#ifdef CONFIG_SECURITY_SMACK_SET_DEV_SMK_LABEL + vlogger_device.lab_smk64 = VLOGGER_SMACK_LABEL; +#endif + r = misc_register(&vlogger_device); + if (unlikely(r)) { + pr_err("Failed to register misc device for '%s' (%d)\n", VLOGGER_DEVICE_NAME, r); + return r; + } + + r = sysfs_create_group(&vlogger_device.this_device->kobj, &vlogger_attr_group); + if (unlikely(r)) { + dev_err(vlogger_device.this_device, "failed to create sysfs nodes with (%d)\n", r); + return r; + } + + g_threads = kzalloc(sizeof(struct thread_t) * MAX_THREAD, GFP_KERNEL); + if (g_threads == NULL) + return -ENOMEM; + + for (i = 0; i < DEVICE_COUNT; i++) { + g_shm_ptr[i] = kzalloc(MAP_SIZE, GFP_KERNEL); + if (g_shm_ptr[i] == NULL) + return -ENOMEM; + } + + 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 +#ifdef CONFIG_TIZEN_VLOGGER + vlog_set_ops(&g_vlog_ops); +#endif + run_task(); + + g_init = 1; + pr_info("Init success\n"); + + return 0; +} + +int vlogger_exit(void) +{ + int i; + + queue_deinit(&g_free_q); + kfree(g_threads); + for (i = 0; i < DEVICE_COUNT; i++) + kfree(g_shm_ptr[i]); + + sysfs_remove_group(&vlogger_device.this_device->kobj, &vlogger_attr_group); + misc_deregister(&vlogger_device); + + return 0; +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("jh1009.sung, "); +MODULE_DESCRIPTION("Tizen Vlogger"); diff --git a/packaging/linux-tizen-modules-source.spec b/packaging/linux-tizen-modules-source.spec index b7e4cb2..7f8adc9 100644 --- a/packaging/linux-tizen-modules-source.spec +++ b/packaging/linux-tizen-modules-source.spec @@ -38,12 +38,16 @@ make -C tests/logger %install mkdir -p %{buildroot}/usr/src/%{name}/kdbus +mkdir -p %{buildroot}/usr/src/%{name}/vlogger +mkdir -p %{buildroot}/usr/src/%{name}/vlogger/include_internal 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/vlogger/*.c kernel/vlogger/Makefile %{buildroot}/usr/src/%{name}/vlogger +cp kernel/vlogger/include_internal/*.h %{buildroot}/usr/src/%{name}/vlogger/include_internal cp include/uapi/linux/kdbus.h %{buildroot}/%{_includedir}/linux cp include/uapi/linux/logger.h %{buildroot}/%{_includedir}/linux @@ -62,6 +66,10 @@ 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}/vlogger/*.c +/usr/src/%{name}/vlogger/include_internal/*.h +/usr/src/%{name}/vlogger/Makefile + %files -n linux-tizen-modules-headers %manifest %{name}.manifest %license COPYING