/*------------------------------------------------------------------------------------------*\ * * Copyright (C) 2017 AVM GmbH * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA \*------------------------------------------------------------------------------------------*/ #include #include #include #include #include #include #include #include #include #include "avm_sammel.h" #if defined(CONFIG_PROC_FS) struct _logger_tag { #define MARK_CR (0x1 << 0) #define MARK_SD (0x1 << 1) unsigned long long ts_nsec; /* time of first print */ unsigned char marker; unsigned char cpu; unsigned short len; } __attribute__((packed)); #define LOGGER_TEXTBUF_SIZE (1024 - 32) #define LOGGER_MAGIC 0x15102009 struct _logger_priv { struct list_head list; unsigned int magic; atomic_t ref_count; char *log_name; char *log_cr_name; char *log_sd_name; char *logbuf; enum _avm_logger_flag logger_flag; unsigned int proc_marker; unsigned int proc_read_eof; unsigned int proc_read_pos; unsigned int proc_last_read_pos; unsigned int logbuf_write_pos; unsigned int first_valid_tag; unsigned int logbuf_size; spinlock_t lock; struct semaphore sema; char textbuf[LOGGER_TEXTBUF_SIZE]; }; static DEFINE_SPINLOCK(avm_logger_lock); static LIST_HEAD(avm_logger_list); #define PROC_AVM_PREFIX "avm/" #define PROC_PREFIX_CR PROC_AVM_PREFIX"log_cr" #define PROC_PREFIX_SD PROC_AVM_PREFIX"log_sd" #define PROC_PREFIX_LOG PROC_AVM_PREFIX"log" #define DBG_TRC(args...) #define DBG_LOG_TRC(args...) /*--- #define DBG_LOG_TRC(args...) printk(KERN_INFO"[avm_logger]"args) ---*/ #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 0) #define KERN_SOH_ASCII '\001' static inline int printk_get_level(const char *buffer) { if (buffer[0] == KERN_SOH_ASCII && buffer[1]) { switch (buffer[1]) { case '0' ... '7': case 'd': /* KERN_DEFAULT */ return buffer[1]; } } return 0; } static inline const char *printk_skip_level(const char *buffer) { if (printk_get_level(buffer)) { switch (buffer[1]) { case '0' ... '7': case 'd': /* KERN_DEFAULT */ return buffer + 2; } } return buffer; } #endif/*--- #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 0) ---*/ #define __get_logger_handle(p) atomic_inc(&(p)->ref_count); /** * @brief create reference on logger instance */ static inline struct _logger_priv *get_logger_handle(void *handle){ struct _logger_priv *plogpriv = (struct _logger_priv *)handle; unsigned long flags; if(plogpriv == NULL) { DBG_LOG_TRC("%s: invalid handle %p on %pS\n", __func__, plogpriv, (void *)_RET_IP_); return NULL; } __BUILD_AVM_CONTEXT_FUNC(spin_lock_irqsave)(&avm_logger_lock, flags); if(plogpriv->magic != LOGGER_MAGIC) { __BUILD_AVM_CONTEXT_FUNC(spin_unlock_irqrestore)(&avm_logger_lock, flags); pr_err("[avm_logger]%s: invalid handle %p on %pS\n", __func__, plogpriv, (void *)_RET_IP_); return NULL; } __get_logger_handle(plogpriv); __BUILD_AVM_CONTEXT_FUNC(spin_unlock_irqrestore)(&avm_logger_lock, flags); return plogpriv; } /** * @brief free reference on logger-instance */ static void put_logger_handle(struct _logger_priv *plogpriv){ unsigned long flags; __BUILD_AVM_CONTEXT_FUNC(spin_lock_irqsave)(&avm_logger_lock, flags); if(plogpriv->magic != LOGGER_MAGIC) { __BUILD_AVM_CONTEXT_FUNC(spin_unlock_irqrestore)(&avm_logger_lock, flags); pr_err("[avm_logger]%s: invalid handle %p\n", __func__, plogpriv); dump_stack(); return; } DBG_LOG_TRC("%s(%p) ref_count=%d - %pS\n", __func__, plogpriv, atomic_read(&plogpriv->ref_count), (void *)_RET_IP_); /*--- atomic_sub_and_test() returns true if the result is 0, or false for all other ---*/ if(atomic_sub_and_test(1, &plogpriv->ref_count) == false) { __BUILD_AVM_CONTEXT_FUNC(spin_unlock_irqrestore)(&avm_logger_lock, flags); return; } DBG_LOG_TRC("%s(%p) - free all\n", __func__, plogpriv); plogpriv->magic = 0; list_del(&plogpriv->list); __BUILD_AVM_CONTEXT_FUNC(spin_unlock_irqrestore)(&avm_logger_lock, flags); if(*plogpriv->log_name) remove_proc_entry(plogpriv->log_name, NULL); if(*plogpriv->log_cr_name) remove_proc_entry(plogpriv->log_cr_name, NULL); if(*plogpriv->log_sd_name) remove_proc_entry(plogpriv->log_sd_name, NULL); kfree(plogpriv->logbuf); kfree(plogpriv); } /** */ static inline unsigned int norm_position(struct _logger_priv *plogpriv, unsigned int pos) { if(pos > plogpriv->logbuf_size) { pos -= plogpriv->logbuf_size; } return pos; } /** */ static inline unsigned int tag_overlap(unsigned int start, unsigned int end, unsigned int pos) { if(start < end) { return (pos >= start) && (pos < end); } else if(pos >= start) { return 1; } else if(pos < end) { return 1; } return 0; } /** * @return new relative pos */ static inline unsigned int fill_logtagstruct(struct _logger_priv *plogpriv, unsigned int pos, struct _logger_tag *plogtag){ unsigned char *buf = (unsigned char *)plogtag; unsigned int i; for(i = 0; i < sizeof(struct _logger_tag); i++) { buf[i] = plogpriv->logbuf[pos++]; if(pos >= plogpriv->logbuf_size) { pos = 0; } } return pos; } /** * @return new relative pos */ static inline unsigned int set_logtag(struct _logger_priv *plogpriv, unsigned int pos, struct _logger_tag *plogtag){ unsigned char *dst = (unsigned char *)plogtag; unsigned int i; for(i = 0; i < sizeof(struct _logger_tag); i++) { plogpriv->logbuf[pos++] = dst[i]; if(pos >= plogpriv->logbuf_size) { pos = 0; } } return pos; } /** * @return new relative pos */ static inline unsigned int copy_to_ringbuf(struct _logger_priv *plogpriv, unsigned int pos, const char *src, unsigned int size) { if((pos + size) > plogpriv->logbuf_size) { unsigned int split; split = plogpriv->logbuf_size - pos; memcpy(&plogpriv->logbuf[pos], src, split); src += split; size -= split; pos = 0; } memcpy(&plogpriv->logbuf[pos], src, size); pos += size; return pos; } /** * @return new relative pos */ static inline unsigned int copy_from_ringbuf(struct _logger_priv *plogpriv, unsigned int pos, unsigned int logtag_len, char *dst, unsigned int max_dst_size) { unsigned int size; if(logtag_len > max_dst_size - 1) { size = max_dst_size - 1; } else { size = logtag_len; } if((pos + size) > plogpriv->logbuf_size) { unsigned int split; split = plogpriv->logbuf_size - pos; memcpy(dst, &plogpriv->logbuf[pos], split); dst += split; size -= split; logtag_len -= split; pos = 0; } memcpy(dst, &plogpriv->logbuf[pos], size); dst[size] = 0; return norm_position(plogpriv, pos + logtag_len); } /** */ static void append_tag(struct _logger_priv *plogpriv, const char *text, unsigned int textlen, unsigned int cpu, unsigned long long ts_nsec) { struct _logger_tag logtag; unsigned int spos, epos; unsigned int old_first; if(unlikely(textlen == 0)) { return; } spos = norm_position(plogpriv, plogpriv->logbuf_write_pos); epos = norm_position(plogpriv, spos + textlen + 2 * sizeof(struct _logger_tag)); /*--- new tag ovelaps old tag ? - change first_valid_tag ---*/ DBG_TRC("%s: spos=%u epos=%u first_valid_tag=%u\n", __func__, spos, epos, plogpriv->first_valid_tag); old_first = plogpriv->first_valid_tag; while(tag_overlap(spos, epos, plogpriv->first_valid_tag)) { fill_logtagstruct(plogpriv, plogpriv->first_valid_tag, &logtag); if(unlikely(logtag.len == 0)) { break; } plogpriv->first_valid_tag = norm_position(plogpriv, plogpriv->first_valid_tag + logtag.len); /*--- signalize to proc-interface, that tags overwritten ---*/ DBG_TRC("\t\t-> new first_valid_tag=%u\n", plogpriv->first_valid_tag); if(tag_overlap(old_first, plogpriv->first_valid_tag, plogpriv->proc_read_pos)) { plogpriv->proc_read_pos = plogpriv->first_valid_tag; } } logtag.len = textlen + sizeof(struct _logger_tag); logtag.marker = 0; logtag.cpu = cpu; logtag.ts_nsec = ts_nsec; spos = set_logtag(plogpriv, spos, &logtag); plogpriv->logbuf_write_pos = copy_to_ringbuf(plogpriv, spos, text, textlen); DBG_TRC("%s: textlen=%u+(%lu) ts_ns=%llu, wpos=%u\n", __func__, textlen, sizeof(struct _logger_tag), logtag.ts_nsec, plogpriv->logbuf_write_pos); logtag.len = 0; /*--- marks the end of buffer ---*/ set_logtag(plogpriv, plogpriv->logbuf_write_pos , &logtag); } /** */ static unsigned int get_text(struct _logger_priv *plogpriv, unsigned int pos, char *text, unsigned int textlen, unsigned int *cpu, unsigned long long *ts_nsec, unsigned int marker) { struct _logger_tag logtag; text[0] = 0; for(;;) { fill_logtagstruct(plogpriv, pos, &logtag); DBG_TRC("%s: pos=%u ts_ns=%llu marker=%x taglen=%u\n", __func__, pos, logtag.ts_nsec, logtag.marker, logtag.len); if(logtag.len == 0) { /*--- empty log marks an eof ---*/ plogpriv->proc_read_eof = 1; break; } if(marker & logtag.marker) { pos = norm_position(plogpriv, pos + logtag.len); continue; } logtag.marker |= marker; pos = set_logtag(plogpriv, pos, &logtag); pos = copy_from_ringbuf(plogpriv, pos, logtag.len - sizeof(struct _logger_tag), text, textlen); if(cpu) *cpu = logtag.cpu; if(ts_nsec) *ts_nsec = logtag.ts_nsec; break; } return pos; } /** */ static char *print_time(u64 ts, char *buf) { unsigned long rem_nsec; rem_nsec = do_div(ts, 1000000000); sprintf(buf, "[%5lu.%06lu] ", (unsigned long)ts, rem_nsec / 1000); return buf; } static char *print_cpu(unsigned int cpu, char *buf) { if(cpu & 0x80) { #if defined(CONFIG_AVM_FASTIRQ) sprintf(buf, "[F%x] ", cpu & 0xF); #elif defined(CONFIG_MIPS) sprintf(buf, "[Y%x:%x] ", cpu & 0xF, (cpu >> 4) & 0x7); #endif/*--- #elif defined(CONFIG_MIPS) ---*/ } else { sprintf(buf, "[%x] ", cpu); } return buf; } /** */ static void *s_next(struct seq_file *m, void *p, loff_t *pos __maybe_unused) { struct _logger_priv *plogpriv = (struct _logger_priv *)m->private; DBG_LOG_TRC("%s: readpos=%u read_eof=%u\n", __func__, plogpriv->proc_read_pos, plogpriv->proc_read_eof); if (plogpriv->proc_read_eof) { return NULL; } return p; } /** */ static void *s_start(struct seq_file *m, loff_t *pos __maybe_unused) { struct _logger_priv *plogpriv = (struct _logger_priv *)m->private; DBG_LOG_TRC("%s: readpos=%u read_eof=%u\n", __func__, plogpriv->proc_read_pos, plogpriv->proc_read_eof); if (plogpriv->proc_read_eof) { return NULL; } return plogpriv; } /** */ static void s_stop(struct seq_file *m __attribute__((unused)), void *p __maybe_unused) { } /** */ static int s_show(struct seq_file *m, void *p __maybe_unused) { struct _logger_priv *plogpriv = (struct _logger_priv *)m->private; char textbuf[LOGGER_TEXTBUF_SIZE]; char txttime[32], txtcpu[32]; int lost = 0; unsigned long flags = 0; unsigned int cpu = 0; unsigned long long ts_nsec = 0; spin_lock_irqsave(&plogpriv->lock, flags); if(plogpriv->proc_last_read_pos != plogpriv->proc_read_pos) { lost = 1; } plogpriv->proc_read_pos = get_text(plogpriv, plogpriv->proc_read_pos, textbuf, sizeof(textbuf), &cpu, &ts_nsec, plogpriv->proc_marker); plogpriv->proc_last_read_pos = plogpriv->proc_read_pos; spin_unlock_irqrestore(&plogpriv->lock, flags); if(lost) { seq_printf(m, "logger: some lines lost here - logger-memory too small?"); } if(textbuf[0]) { seq_printf(m, "%s%s%s", print_time(ts_nsec, txttime), print_cpu(cpu, txtcpu), textbuf); } return 0; } /** */ static const struct seq_operations avm_logger_op = { .start = s_start, .next = s_next, .stop = s_stop, .show = s_show }; /** */ static int proc_logger_open(struct inode *inode __attribute__((unused)), struct file *file) { int ret = 0, cr = 0, found = 0; struct _logger_priv *plogpriv; unsigned long flags; char fullpath[512]; const char *proc_name = "", *path = ""; if(file->f_path.dentry) { proc_name = file->f_path.dentry->d_name.name; if(file->f_path.dentry->d_parent) { path = file->f_path.dentry->d_parent->d_name.name; } } snprintf(fullpath, sizeof(fullpath), "%s%s/%s", PROC_AVM_PREFIX, path, proc_name); DBG_LOG_TRC("%s: '%s' '%s' -> '%s'\n", __func__, path, proc_name, fullpath); spin_lock_irqsave(&avm_logger_lock, flags); list_for_each_entry(plogpriv, &avm_logger_list, list){ if(strcmp(plogpriv->log_name, fullpath) == 0) { cr = 0, found = 1; break; } if(strcmp(plogpriv->log_cr_name, fullpath) == 0) { cr = MARK_CR, found = 1; break; } if(strcmp(plogpriv->log_sd_name, fullpath) == 0) { cr = MARK_SD, found = 1; break; } } if(found) { __get_logger_handle(plogpriv); } spin_unlock_irqrestore(&avm_logger_lock, flags); if(found == 0) { return -ENODEV; } if(down_interruptible(&plogpriv->sema)) { return -EBUSY; } plogpriv->proc_marker = cr; plogpriv->proc_read_eof = 0; spin_lock_irqsave(&plogpriv->lock, flags); plogpriv->proc_read_pos = plogpriv->first_valid_tag; plogpriv->proc_last_read_pos = plogpriv->proc_read_pos; spin_unlock_irqrestore(&plogpriv->lock, flags); /* * We keep iterator in m->private, since normal case is to * s_start from where we left off, so we avoid doing */ ret = seq_open(file, &avm_logger_op); if(ret) { up(&plogpriv->sema); put_logger_handle(plogpriv); } else { ((struct seq_file *)file->private_data)->private = plogpriv; } return ret; } /** */ static int proc_logger_close(struct inode *inode, struct file *file) { int ret; struct seq_file *seq = file->private_data; struct _logger_priv *plogpriv = (struct _logger_priv *)seq->private; up(&plogpriv->sema); seq->private = NULL; ret = seq_release(inode, file); put_logger_handle(plogpriv); return ret; } /** */ static const struct file_operations avm_logger_stat_fops = { .open = proc_logger_open, .read = seq_read, .llseek = seq_lseek, .release = proc_logger_close, }; /** * @brief: create logger * the log is located in /proc/avm/log/ * @param ringbuf_size size of ringbuffer (minsize is PAGE_SIZE) * @logger_name name of logger * @flag or-able features * * @return handle - PTR or ERR_PTR */ void *avm_logger_create(size_t ringbuf_size, const char *logger_name, enum _avm_logger_flag flag){ int name_len, result; struct _logger_priv *plogpriv = NULL; char *logbuf; if(ringbuf_size < PAGE_SIZE) { ringbuf_size = PAGE_SIZE; } if(logger_name == NULL) { return ERR_PTR(-EINVAL); } logbuf = kzalloc(ringbuf_size, GFP_KERNEL); if(logbuf == NULL) { result = -ENOMEM; goto fail; } DBG_LOG_TRC("%s(%s size=%u)\n", __func__, logger_name, ringbuf_size); name_len = strlen(logger_name) + 1; plogpriv = kzalloc(sizeof(struct _logger_priv) + 3 * (name_len + 1) + sizeof(PROC_PREFIX_SD) + sizeof(PROC_PREFIX_CR) + sizeof(PROC_PREFIX_LOG), GFP_KERNEL); if(plogpriv == NULL) { result = -ENOMEM; goto fail; } plogpriv->magic = LOGGER_MAGIC; plogpriv->logger_flag = flag; plogpriv->logbuf = logbuf; plogpriv->logbuf_size = ringbuf_size; plogpriv->log_name = (char *)(plogpriv + 1); plogpriv->log_cr_name = plogpriv->log_name + name_len + sizeof(PROC_PREFIX_LOG); plogpriv->log_sd_name = plogpriv->log_cr_name + name_len + sizeof(PROC_PREFIX_CR); sprintf(plogpriv->log_name, "%s/%s", PROC_PREFIX_LOG, logger_name); if(!proc_create(plogpriv->log_name, 0444, NULL, &avm_logger_stat_fops)) { plogpriv->log_name[0] = 0; result = -ENODEV; goto fail; } if(plogpriv->logger_flag & logger_log_cr_dir) { sprintf(plogpriv->log_cr_name, "%s/%s", PROC_PREFIX_CR, logger_name); if(!proc_create(plogpriv->log_cr_name, 0444, NULL, &avm_logger_stat_fops)) { plogpriv->log_cr_name[0] = 0; result = -ENODEV; goto fail; } } if(plogpriv->logger_flag & logger_log_sd_dir) { sprintf(plogpriv->log_sd_name, "%s/%s", PROC_PREFIX_SD, logger_name); if(!proc_create(plogpriv->log_sd_name, 0444, NULL, &avm_logger_stat_fops)) { plogpriv->log_sd_name[0] = 0; result = -ENODEV; goto fail; } } spin_lock_init(&plogpriv->lock); sema_init(&plogpriv->sema, 1); list_add(&plogpriv->list, &avm_logger_list); atomic_set(&plogpriv->ref_count, 1); DBG_LOG_TRC("%s handle=%p done\n", __func__, plogpriv); return plogpriv; fail: pr_err("[avm_logger]%s: failed error=%d: %pS\n", __func__, result, (void *)_RET_IP_); if(logbuf) kfree(logbuf); if(plogpriv) { if(*plogpriv->log_name) remove_proc_entry(plogpriv->log_name, NULL); if(*plogpriv->log_cr_name) remove_proc_entry(plogpriv->log_cr_name, NULL); if(*plogpriv->log_sd_name) remove_proc_entry(plogpriv->log_sd_name, NULL); plogpriv->magic = 0; kfree(plogpriv); } return ERR_PTR(result); } EXPORT_SYMBOL(avm_logger_create); /** */ static asmlinkage int avmlogger_vprintk(struct _logger_priv *plogpriv, const char *fmt, va_list args) { unsigned long long ts_nsec; unsigned long flags; unsigned int text_len; const char *text; int cpu; #if defined(CONFIG_SMP) && defined(CONFIG_MIPS) int nonlinux_context = is_yield_context(); #elif defined(CONFIG_AVM_FASTIRQ) int nonlinux_context = is_fiq_context(); #else #define nonlinux_context 0 #endif/*--- #if defined(CONFIG_AVM_DEBUG) ---*/ ts_nsec = local_clock(); cpu = get_cpu(); if(nonlinux_context) { cpu |= ((nonlinux_context -1) << 4) | 0x80; } __BUILD_AVM_CONTEXT_FUNC(spin_lock_irqsave)(&plogpriv->lock, flags); text_len = vscnprintf(plogpriv->textbuf, sizeof(plogpriv->textbuf), fmt, args); if((plogpriv->logger_flag & logger_no_printk_trace) == 0) { printk("%s", plogpriv->textbuf); } text = printk_skip_level(plogpriv->textbuf); text_len -= text - plogpriv->textbuf; append_tag(plogpriv, text, text_len, cpu, ts_nsec); __BUILD_AVM_CONTEXT_FUNC(spin_unlock_irqrestore)(&plogpriv->lock, flags); put_cpu(); return text_len; } /* * - rate limiting * @rs: ratelimit_state data * @func: name of calling function * * This enforces a rate limit: not more than @rs->burst callbacks * in every @rs->interval * * RETURNS: * 0 means callbacks will be suppressed. * 1 means go ahead and do it. */ int avm_logger_ratelimit(void *handle, struct ratelimit_state *rs, const char *func) { unsigned long flags; int ret; if (!rs->interval) return 1; /* * If we contend on this state's lock then almost * by definition we are too busy to print a message, * in addition to the one that will be printed by * the entity that is holding the lock already: */ if (!__BUILD_AVM_CONTEXT_FUNC(raw_spin_trylock_irqsave)(&rs->lock, flags)) return 0; if (!rs->begin) rs->begin = jiffies; if (time_is_before_jiffies(rs->begin + rs->interval)) { if (rs->missed) avm_logger_printk(handle, KERN_WARNING "%s: %d callbacks suppressed\n", func, rs->missed); rs->begin = 0; rs->printed = 0; rs->missed = 0; } if (rs->burst && rs->burst > rs->printed) { rs->printed++; ret = 1; } else { rs->missed++; ret = 0; } __BUILD_AVM_CONTEXT_FUNC(raw_spin_unlock_irqrestore)(&rs->lock, flags); return ret; } EXPORT_SYMBOL(avm_logger_ratelimit); /** * @brief logger - output also to printk * @param handle logger-handle from avm_logger_create() * @param fmt and variable list like printk */ asmlinkage int avm_logger_printk(void *handle, const char *fmt, ...) { struct _logger_priv *plogpriv = get_logger_handle(handle); va_list args; int r; va_start(args, fmt); if(plogpriv == NULL) { /*--- fallback if logger-handle invalid ---*/ r = vprintk(fmt, args); } else { r = avmlogger_vprintk(plogpriv, fmt, args); put_logger_handle(plogpriv); } va_end(args); return r; } EXPORT_SYMBOL(avm_logger_printk); /** * close logger */ void avm_logger_close(void *handle) { if(handle) { DBG_LOG_TRC("%s(%p)\n", __func__, handle); put_logger_handle(handle); } } EXPORT_SYMBOL(avm_logger_close); /** * @brief create avm/log */ static int __init avm_logger_init(void) { proc_mkdir(PROC_PREFIX_LOG, NULL); return 0; } late_initcall(avm_logger_init); #endif/*--- #if defined(CONFIG_PROC_FS) ---*/