888 lines
27 KiB
C
Executable File
888 lines
27 KiB
C
Executable File
/******************************************************************************
|
|
*
|
|
* Copyright (c) 2016-2017 Samsung Electronics Co., Ltd. All rights reserved.
|
|
*
|
|
******************************************************************************/
|
|
|
|
#include <scsc/scsc_mx.h>
|
|
#include "scsc_logring_main.h"
|
|
#include "scsc_logring_debugfs.h"
|
|
|
|
static int scsc_max_records_per_read = SCSC_DEFAULT_MAX_RECORDS_PER_READ;
|
|
module_param(scsc_max_records_per_read, int, S_IRUGO | S_IWUSR);
|
|
SCSC_MODPARAM_DESC(scsc_max_records_per_read,
|
|
"Number of records a reader can try to get in a shot. 0 is infinite",
|
|
"run-time", SCSC_DEFAULT_MAX_RECORDS_PER_READ);
|
|
|
|
static int scsc_double_buffer_sz = DEFAULT_TBUF_SZ;
|
|
module_param(scsc_double_buffer_sz, int, S_IRUGO | S_IWUSR);
|
|
SCSC_MODPARAM_DESC(scsc_double_buffer_sz,
|
|
"Determines the size of the per-reader allocted double buffer.",
|
|
"run-time", DEFAULT_TBUF_SZ);
|
|
|
|
#define LOGRING_DEV_NAME "scsc_logring"
|
|
#define DRV_NAME LOGRING_DEV_NAME
|
|
|
|
/* Keep track of all the device numbers used. */
|
|
#define LOGRING_MAX_DEV 5
|
|
|
|
/**
|
|
* BIG NOTE on DOUBLE BUFFERING.
|
|
*
|
|
* In order to extract data from the ring buffer, protected by spinlocks,
|
|
* to user space we use a double buffer: data is so finally copied to
|
|
* userspace from a temporary double buffer, after having copied into it
|
|
* ALL the desired content and after all the spinlocks have been released.
|
|
* In order to avoid use of an additional mutex to protect such temporary
|
|
* buffer from multiple readers access we use a oneshot throwaway buffer
|
|
* dedicated to each reader and allocated at opening time.
|
|
* The most straightforward way to do this thing would have been to simply
|
|
* allocate such buffer inside the read method and throw it away on exit:
|
|
* this is what underlying printk mechanism does via a simple kmalloc.
|
|
* BUT we decided INSTEAD to use this buffer ALSO as a sort of caching
|
|
* area for each reader in order to cope with under-sized user read-request;
|
|
* basically no matter what the user has asked in term of size of the read
|
|
* request we'll ALWAYS RETRIEVE multiple of whole records from the ring,
|
|
* one record being the minimum internal ring-read-request this way.
|
|
* So no matter if the user ask for a few bytes, less than the next record
|
|
* size, we'll retrieve ONE WHOLE record from the ring into the double buffer:
|
|
* this way on the next read request we'll have already a cached copy of the
|
|
* record and we could deal with it inside the read callback without the
|
|
* need to access the ring anymore for such record.
|
|
* The main reason for this is that if we had instead accessed the ring and
|
|
* retrieved ONLY a fraction of the record, on the next request we could NOT
|
|
* be able to provide the remaining part of the record because, being the ring
|
|
* an overwriting buffer, it could have wrap in the meantime and we could have
|
|
* simply lost that data: this condition would have lead us to return to
|
|
* user partial truncated records when we hit this overwrap condition.
|
|
* Following instead the approach of WHOLE records retrieval we can instead be
|
|
* sure to always retrieve fully correct records, despite being vulnerable
|
|
* anyway to loss of data (whole records) while reading if fast writers
|
|
* overwrite our data. (since we'll never ever want to slow down and starve a
|
|
* writer.)
|
|
*/
|
|
#ifdef CONFIG_SCSC_LOGRING_DEBUGFS
|
|
static struct dentry *scsc_debugfs_root;
|
|
static atomic_t scsc_debugfs_root_refcnt;
|
|
#endif
|
|
static char *global_fmt_string = "%s";
|
|
|
|
#if IS_ENABLED(CONFIG_SCSC_MXLOGGER)
|
|
static struct scsc_logring_mx_cb *mx_cb_single;
|
|
|
|
int scsc_logring_register_mx_cb(struct scsc_logring_mx_cb *mx_cb)
|
|
{
|
|
mx_cb_single = mx_cb;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(scsc_logring_register_mx_cb);
|
|
|
|
int scsc_logring_unregister_mx_cb(struct scsc_logring_mx_cb *mx_cb)
|
|
{
|
|
mx_cb_single = NULL;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(scsc_logring_unregister_mx_cb);
|
|
#endif
|
|
/**
|
|
* Generic open/close calls to use with every logring debugfs file.
|
|
* Any file in debugfs has an underlying associated ring buffer:
|
|
* opening ANY of these with O_TRUNC leads to ring_buffer truncated
|
|
* to zero len.
|
|
*/
|
|
static int debugfile_open(struct inode *ino, struct file *filp)
|
|
{
|
|
struct scsc_ibox *i = NULL;
|
|
|
|
if (!filp->private_data) {
|
|
i = kzalloc(sizeof(*i), GFP_KERNEL);
|
|
if (!i)
|
|
return -EFAULT;
|
|
i->rb = ino->i_private;
|
|
filp->private_data = i;
|
|
} else {
|
|
i = filp->private_data;
|
|
}
|
|
/* tbuf sz is now runtime-configurable so try a few fallback methods */
|
|
i->tbuf = kmalloc(scsc_double_buffer_sz, GFP_KERNEL);
|
|
/* Making sure we fallback to a safe size DEFAULT_TBUF_SZ */
|
|
if (!i->tbuf) {
|
|
i->tbuf = vmalloc(scsc_double_buffer_sz);
|
|
pr_err("LogRing: FAILED tbuf allocation of %d bytes...retried vmalloc()...\n",
|
|
scsc_double_buffer_sz);
|
|
if (!i->tbuf) {
|
|
scsc_double_buffer_sz = DEFAULT_TBUF_SZ;
|
|
pr_err("LogRing: FAILED tbuf vmalloc...using DEFAULT %d bytes size.\n",
|
|
scsc_double_buffer_sz);
|
|
i->tbuf = kmalloc(scsc_double_buffer_sz, GFP_KERNEL);
|
|
if (!i->tbuf) {
|
|
pr_err("LogRing: FAILED DEFINITELY allocation...aborting\n");
|
|
kfree(i);
|
|
return -ENOMEM;
|
|
}
|
|
} else {
|
|
i->tbuf_vm = true;
|
|
}
|
|
}
|
|
i->tsz = scsc_double_buffer_sz;
|
|
pr_info("LogRing: Allocated per-reader tbuf of %d bytes\n",
|
|
scsc_double_buffer_sz);
|
|
/* Truncate when attempting to write RO files samlog and samsg */
|
|
if (filp->f_flags & (O_WRONLY | O_RDWR) &&
|
|
filp->f_flags & O_TRUNC) {
|
|
unsigned long flags;
|
|
|
|
raw_spin_lock_irqsave(&i->rb->lock, flags);
|
|
scsc_ring_truncate(i->rb);
|
|
raw_spin_unlock_irqrestore(&i->rb->lock, flags);
|
|
pr_info("LogRing Truncated to zerolen\n");
|
|
return -EACCES;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int debugfile_release(struct inode *ino, struct file *filp)
|
|
{
|
|
struct scsc_ibox *i = NULL;
|
|
|
|
if (!filp->private_data)
|
|
return -EFAULT;
|
|
i = filp->private_data;
|
|
if (!i->tbuf_vm)
|
|
kfree(i->tbuf);
|
|
else
|
|
vfree(i->tbuf);
|
|
i->tbuf = NULL;
|
|
|
|
/* Were we using a snapshot ? Free it.*/
|
|
if (i->saved_live_rb) {
|
|
vfree(i->rb->buf);
|
|
kfree(i->rb);
|
|
}
|
|
/* Being paranoid... */
|
|
filp->private_data = NULL;
|
|
kfree(i);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Initialize references for subsequent cached reads: in fact if
|
|
* data retrieved from the ring was more than the count-bytes required by
|
|
* the caller of this read, we can keep such data stored in tbuf and provide
|
|
* it to this same reader on its next read-call.
|
|
*
|
|
* @i: contains references useful to this reader
|
|
* @retrieved_bytes: how many bytes have been stored in tbuf
|
|
* @count: a pointer to the count bytes required by this reader
|
|
* for this call. We'll manipulate this to return an
|
|
* appropriate number of bytes.
|
|
*/
|
|
static inline
|
|
size_t init_cached_read(struct scsc_ibox *i,
|
|
size_t retrieved_bytes, size_t *count)
|
|
{
|
|
if (retrieved_bytes <= *count) {
|
|
*count = retrieved_bytes;
|
|
} else {
|
|
i->t_off = *count;
|
|
i->t_used = retrieved_bytes - *count;
|
|
i->cached_reads += *count;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Here we'll serve to user space the next available chunk of
|
|
* record directly from the tbuf double buffer without
|
|
* accessing the ring anymore.
|
|
*
|
|
* @i: contains references useful to this reader
|
|
* @count: a pointer to the count bytes required by this reader
|
|
* for this call. We'll manipulate this to return an
|
|
* appropriate number of bytes.
|
|
*/
|
|
static inline
|
|
size_t process_cached_read_data(struct scsc_ibox *i, size_t *count)
|
|
{
|
|
size_t offset = 0;
|
|
|
|
offset = i->t_off;
|
|
if (i->t_used <= *count) {
|
|
/* this was the last chunk cached */
|
|
*count = i->t_used;
|
|
i->t_off = 0;
|
|
i->t_used = 0;
|
|
} else {
|
|
i->t_off += *count;
|
|
i->t_used -= *count;
|
|
i->cached_reads += *count;
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
/**
|
|
* This file operation read from the ring using common routines, starting its
|
|
* read from head: in other words it immediately blocks waiting for some data to
|
|
* arrive. As soon as some data arrives and head moves away, the freshly
|
|
* available data is returned to userspace up to the required size , and this
|
|
* call goes back to sleeping waiting for more data.
|
|
*
|
|
* NOTE
|
|
* ----
|
|
* The need to copy_to_user imposes the use of a temp buffer tbuf which is used
|
|
* as a double buffer: being allocated to this reader on open() we do NOT need
|
|
* any additional form of mutual exclusion.
|
|
* Moreover we use such buffer here as an area to cache the retrieved records:
|
|
* if the retrieved record size is bigger than the count bytes required by user
|
|
* we'll return less data at first and then deal with the following requests
|
|
* pumping data directly from the double buffer without accessing the ring.
|
|
*/
|
|
static ssize_t samsg_read(struct file *filp, char __user *ubuf,
|
|
size_t count, loff_t *f_pos)
|
|
{
|
|
unsigned long flags;
|
|
loff_t current_head = 0;
|
|
struct scsc_ibox *i = NULL;
|
|
size_t off = 0;
|
|
size_t retrieved_bytes = 0;
|
|
|
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0))
|
|
if (!filp->private_data || !access_ok(VERIFY_WRITE, ubuf, count))
|
|
#else
|
|
if (!filp->private_data || !access_ok(ubuf, count))
|
|
#endif
|
|
return -ENOMEM;
|
|
if (filp->f_flags & O_NONBLOCK)
|
|
return -EAGAIN;
|
|
/* open() assures us that this private data is certainly non-NULL */
|
|
i = filp->private_data;
|
|
if (!i->t_used) {
|
|
raw_spin_lock_irqsave(&i->rb->lock, flags);
|
|
current_head = *f_pos ? i->f_pos : i->rb->head;
|
|
while (current_head == i->rb->head) {
|
|
raw_spin_unlock_irqrestore(&i->rb->lock, flags);
|
|
if (wait_event_interruptible(i->rb->wq,
|
|
current_head != i->rb->head))
|
|
return -ERESTARTSYS;
|
|
raw_spin_lock_irqsave(&i->rb->lock, flags);
|
|
}
|
|
retrieved_bytes = read_next_records(i->rb,
|
|
scsc_max_records_per_read,
|
|
¤t_head, i->tbuf, i->tsz);
|
|
/* We MUST keep track of the the last known READ record
|
|
* in order to keep going from the same place on the next
|
|
* read call coming from the same userspace process...
|
|
* ...this could NOT necessarily be the HEAD at the end of this
|
|
* read if we asked for few records.
|
|
* So we must annotate the really last read record got back,
|
|
* returned in current_head, inside i->f_pos in order to have a
|
|
* reference for the next read call by the same reader.
|
|
*/
|
|
i->f_pos = current_head;
|
|
raw_spin_unlock_irqrestore(&i->rb->lock, flags);
|
|
/* ANYWAY we could have got back more data from the ring (ONLY
|
|
* multiple of whole records) than required by usersapce.
|
|
*/
|
|
off = init_cached_read(i, retrieved_bytes, &count);
|
|
} else {
|
|
/* Serve this read-request directly from cached data without
|
|
* accessing the ring
|
|
*/
|
|
off = process_cached_read_data(i, &count);
|
|
}
|
|
if (copy_to_user(ubuf, i->tbuf + off, count))
|
|
return -EFAULT;
|
|
*f_pos += count;
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* This seek op assumes let userspace believe that it's dealing with a regular
|
|
* plain file, so f_pos is modified accordingly (linearly till the maximum
|
|
* number SCSC_LOGGED_BYTES is reached); in fact it's up to
|
|
* the read/write ops to properly 'cast' this value to a modulus value as
|
|
* required by the underlying ring buffer. This operates only on samlog.
|
|
*/
|
|
loff_t debugfile_llseek(struct file *filp, loff_t off, int whence)
|
|
{
|
|
loff_t newpos, maxpos;
|
|
struct scsc_ibox *i = NULL;
|
|
unsigned long flags;
|
|
|
|
if (!filp->private_data)
|
|
return -EFAULT;
|
|
i = filp->private_data;
|
|
raw_spin_lock_irqsave(&i->rb->lock, flags);
|
|
maxpos = SCSC_LOGGED_BYTES(i->rb) >= 1 ?
|
|
SCSC_LOGGED_BYTES(i->rb) - 1 : 0;
|
|
raw_spin_unlock_irqrestore(&i->rb->lock, flags);
|
|
switch (whence) {
|
|
case 0: /* SEEK_SET */
|
|
newpos = (off <= maxpos) ? off : maxpos;
|
|
break;
|
|
case 1: /* SEEK_CUR */
|
|
newpos = (filp->f_pos + off <= maxpos) ?
|
|
filp->f_pos + off : maxpos;
|
|
break;
|
|
case 2: /* SEEK_END */
|
|
newpos = maxpos;
|
|
break;
|
|
default: /* can't happen */
|
|
return -EINVAL;
|
|
}
|
|
if (newpos < 0)
|
|
return -EINVAL;
|
|
filp->f_pos = newpos;
|
|
return newpos;
|
|
}
|
|
|
|
static int samsg_open(struct inode *ino, struct file *filp)
|
|
{
|
|
int ret;
|
|
#ifndef CONFIG_SCSC_LOGRING_DEBUGFS
|
|
struct scsc_debugfs_info *di = (struct scsc_debugfs_info *)container_of(ino->i_cdev,
|
|
struct scsc_debugfs_info, cdev_samsg);
|
|
|
|
ino->i_private = di->rb;
|
|
filp->private_data = NULL;
|
|
#endif
|
|
ret = debugfile_open(ino, filp);
|
|
#if IS_ENABLED(CONFIG_SCSC_MXLOGGER)
|
|
if (!ret && mx_cb_single && mx_cb_single->scsc_logring_register_observer)
|
|
mx_cb_single->scsc_logring_register_observer(mx_cb_single, "LOGRING");
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
static int samsg_release(struct inode *ino, struct file *filp)
|
|
{
|
|
#if IS_ENABLED(CONFIG_SCSC_MXLOGGER)
|
|
if (mx_cb_single && mx_cb_single->scsc_logring_unregister_observer)
|
|
mx_cb_single->scsc_logring_unregister_observer(mx_cb_single, "LOGRING");
|
|
#endif
|
|
|
|
return debugfile_release(ino, filp);
|
|
}
|
|
|
|
const struct file_operations samsg_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = samsg_open,
|
|
.read = samsg_read,
|
|
.release = samsg_release,
|
|
};
|
|
|
|
/**
|
|
* This is the samlog open and it is used by samlog read-process to grab
|
|
* a per-reader dedicated static snapshot of the ring, in order to be able
|
|
* then to fetch records from a static immutable image of the ring buffer,
|
|
* without the need to stop the ring in the meantime.
|
|
* This way samlog dumps exactly a snapshot at-a-point-in-time of the ring
|
|
* limiting at the same time the contention with the writers: ring is
|
|
* 'spinlocked' ONLY durng the snapshot phase.
|
|
* Being the snapshot buffer big as the ring we use a vmalloc to limit
|
|
* possibility of failures (especially on non-AOSP builds).
|
|
* If such vmalloc allocation fails we then quietly keep on using the old
|
|
* method that reads directly from the live buffer.
|
|
*/
|
|
static int debugfile_open_snapshot(struct inode *ino, struct file *filp)
|
|
{
|
|
int ret;
|
|
|
|
#ifndef CONFIG_SCSC_LOGRING_DEBUGFS
|
|
struct scsc_debugfs_info *di = (struct scsc_debugfs_info *)container_of(ino->i_cdev,
|
|
struct scsc_debugfs_info, cdev_samlog);
|
|
|
|
ino->i_private = di->rb;
|
|
filp->private_data = NULL;
|
|
#endif
|
|
ret = debugfile_open(ino, filp);
|
|
/* if regular debug_file_open has gone through, attempt snapshot */
|
|
if (!ret) {
|
|
/* filp && filp->private_data NON-NULL by debugfile_open */
|
|
void *snap_buf;
|
|
size_t snap_sz;
|
|
struct scsc_ibox *i = filp->private_data;
|
|
|
|
/* This is read-only...no spinlocking needed */
|
|
snap_sz = i->rb->bsz + i->rb->ssz;
|
|
/* Allocate here to minimize lock time... */
|
|
snap_buf = vmalloc(snap_sz);
|
|
if (snap_buf) {
|
|
struct scsc_ring_buffer *snap_rb;
|
|
char snap_name[RNAME_SZ] = "snapshot";
|
|
unsigned long flags;
|
|
|
|
snprintf(snap_name, RNAME_SZ, "%s_snap", i->rb->name);
|
|
/* lock while snapshot is taken */
|
|
raw_spin_lock_irqsave(&i->rb->lock, flags);
|
|
snap_rb = scsc_ring_get_snapshot(i->rb, snap_buf, snap_sz, snap_name);
|
|
raw_spin_unlock_irqrestore(&i->rb->lock, flags);
|
|
if (snap_rb) {
|
|
/* save real ring and swap into the snap_shot */
|
|
i->saved_live_rb = i->rb;
|
|
i->rb = snap_rb;
|
|
} else {
|
|
vfree(snap_buf);
|
|
snap_buf = NULL;
|
|
}
|
|
}
|
|
|
|
/* Warns when not possible to use a snapshot */
|
|
if (!snap_buf)
|
|
pr_warn("LogRing: no snapshot available, samlog dump from live ring.\n");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* samlog_read - Reads from the ring buffer the required number of bytes
|
|
* starting from the start of the ring. It is usually used to dump the
|
|
* whole ring buffer taking a snapshot at-a-point-in-time.
|
|
*
|
|
* If it had been possible at opening time to take a static snapshot of
|
|
* the ring, this routine will fetch records from such a snapshot without
|
|
* the need to lock the ring; if instead no snapshot was taken it reverts
|
|
* to the usual locked-access pattern.
|
|
*
|
|
* This function as a usual .read fops returns the number of bytes
|
|
* effectively read, and this could:
|
|
* - equal the required count bytes
|
|
* - be less than the required bytes if: less data WAS available
|
|
* (since we only GOT whole records at time from the ring)
|
|
* Returning less bytes usually triggers the userapp to reissue the syscall
|
|
* to complete the read up to the originaly required number of bytes.
|
|
* - be ZERO if NO more data available..this causes the reading userspace
|
|
* process to stop reading usually.
|
|
*/
|
|
static ssize_t samlog_read(struct file *filp, char __user *ubuf,
|
|
size_t count, loff_t *f_pos)
|
|
{
|
|
struct scsc_ibox *i = NULL;
|
|
size_t off = 0, retrieved_bytes = 0;
|
|
|
|
if (!filp->private_data)
|
|
return -EFAULT;
|
|
i = filp->private_data;
|
|
if (!i->t_used) {
|
|
unsigned long flags;
|
|
|
|
/* Lock ONLY if NOT using a snapshot */
|
|
if (!i->saved_live_rb)
|
|
raw_spin_lock_irqsave(&i->rb->lock, flags);
|
|
/* On first read from userspace f_pos will be ZERO and in this
|
|
* case we'll want to trigger a read from the very beginning of
|
|
* ring (tail) and set i->f_pos accordingly.
|
|
* Internal RING API returns in i->f_pos the next record to
|
|
* read: when reading process has wrapped over you'll get back
|
|
* an f_pos ZERO as next read.
|
|
*/
|
|
if (*f_pos == 0)
|
|
i->f_pos = i->rb->tail;
|
|
retrieved_bytes = read_next_records(i->rb,
|
|
scsc_max_records_per_read,
|
|
&i->f_pos, i->tbuf, i->tsz);
|
|
if (!i->saved_live_rb)
|
|
raw_spin_unlock_irqrestore(&i->rb->lock, flags);
|
|
/* ANYWAY we could have got back more data from the ring (ONLY
|
|
* multiple of whole records) than required by userspace.
|
|
*/
|
|
off = init_cached_read(i, retrieved_bytes, &count);
|
|
} else {
|
|
/* Serve this read-request directly from cached data without
|
|
* accessing the ring
|
|
*/
|
|
off = process_cached_read_data(i, &count);
|
|
}
|
|
if (copy_to_user(ubuf, i->tbuf + off, count))
|
|
return -EFAULT;
|
|
*f_pos += count;
|
|
return count;
|
|
}
|
|
|
|
const struct file_operations samlog_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = debugfile_open_snapshot,
|
|
.read = samlog_read,
|
|
.llseek = debugfile_llseek,
|
|
.release = debugfile_release,
|
|
};
|
|
|
|
static int statfile_open(struct inode *ino, struct file *filp)
|
|
{
|
|
#ifdef CONFIG_SCSC_LOGRING_DEBUGFS
|
|
if (!filp->private_data)
|
|
filp->private_data = ino->i_private;
|
|
if (!filp->private_data)
|
|
return -EFAULT;
|
|
#else
|
|
struct scsc_debugfs_info *di = (struct scsc_debugfs_info *)container_of(ino->i_cdev,
|
|
struct scsc_debugfs_info, cdev_stat);
|
|
|
|
ino->i_private = di->rb;
|
|
filp->private_data = ino->i_private;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int statfile_release(struct inode *ino, struct file *filp)
|
|
{
|
|
#ifdef CONFIG_SCSC_LOGRING_DEBUGFS
|
|
if (!filp->private_data)
|
|
filp->private_data = ino->i_private;
|
|
if (!filp->private_data)
|
|
return -EFAULT;
|
|
#else
|
|
filp->private_data = NULL;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* A simple read to dump some stats about the ring buffer. */
|
|
static ssize_t statfile_read(struct file *filp, char __user *ubuf,
|
|
size_t count, loff_t *f_pos)
|
|
{
|
|
unsigned long flags;
|
|
size_t bsz = 0;
|
|
loff_t head = 0, tail = 0, used = 0, max_chunk = 0, logged = 0,
|
|
last = 0;
|
|
int slen = 0, records = 0, wraps = 0, oos = 0;
|
|
u64 written = 0;
|
|
char statstr[STATSTR_SZ] = {};
|
|
struct scsc_ring_buffer *rb = filp->private_data;
|
|
|
|
raw_spin_lock_irqsave(&rb->lock, flags);
|
|
bsz = rb->bsz;
|
|
head = rb->head;
|
|
tail = rb->tail;
|
|
last = rb->last;
|
|
written = rb->written;
|
|
records = rb->records;
|
|
wraps = rb->wraps;
|
|
oos = rb->oos;
|
|
used = SCSC_USED_BYTES(rb);
|
|
max_chunk = SCSC_RING_FREE_BYTES(rb);
|
|
logged = SCSC_LOGGED_BYTES(rb);
|
|
raw_spin_unlock_irqrestore(&rb->lock, flags);
|
|
|
|
slen = snprintf(statstr, STATSTR_SZ,
|
|
"sz:%zd used:%lld free:%lld logged:%lld records:%d\nhead:%lld tail:%lld last:%lld written:%lld wraps:%d oos:%d\n",
|
|
bsz, used, max_chunk, logged, records,
|
|
head, tail, last, written, wraps, oos);
|
|
if (slen >= 0 && *f_pos < slen) {
|
|
count = (count <= slen - *f_pos) ? count : (slen - *f_pos);
|
|
if (copy_to_user(ubuf, statstr + *f_pos, count))
|
|
return -EFAULT;
|
|
*f_pos += count;
|
|
} else
|
|
count = 0;
|
|
return count;
|
|
}
|
|
|
|
const struct file_operations stat_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = statfile_open,
|
|
.read = statfile_read,
|
|
.release = statfile_release,
|
|
};
|
|
|
|
/**
|
|
* This implement samwrite interface to INJECT log lines into the ring from
|
|
* user space. The support, thought as an aid for testing mainly, is
|
|
* minimal, so the interface allows only for simple %s format string injection.
|
|
*/
|
|
static int samwritefile_open(struct inode *ino, struct file *filp)
|
|
{
|
|
if (!filp->private_data) {
|
|
struct write_config *wc =
|
|
kzalloc(sizeof(struct write_config), GFP_KERNEL);
|
|
if (wc) {
|
|
wc->fmt = global_fmt_string;
|
|
wc->buf_sz = SAMWRITE_BUFSZ;
|
|
}
|
|
filp->private_data = wc;
|
|
}
|
|
if (!filp->private_data)
|
|
return -EFAULT;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int samwritefile_release(struct inode *ino, struct file *filp)
|
|
{
|
|
kfree(filp->private_data);
|
|
filp->private_data = NULL;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* User injected string content is pushed to the ring as simple %s fmt string
|
|
* content using the TEST_ME tag. Default debuglevel (6 - INFO)will be used.
|
|
*/
|
|
static ssize_t samwritefile_write(struct file *filp, const char __user *ubuf,
|
|
size_t count, loff_t *f_pos)
|
|
{
|
|
ssize_t written_bytes = 0;
|
|
struct write_config *wc = filp->private_data;
|
|
|
|
if (wc) {
|
|
/* wc->buf is null terminated as it's kzalloc'ed */
|
|
count = count < wc->buf_sz ? count : wc->buf_sz - 1;
|
|
if (copy_from_user(wc->buf, ubuf, count))
|
|
return -EINVAL;
|
|
written_bytes = scsc_printk_tag(NO_ECHO_PRK, TEST_ME,
|
|
wc->fmt, wc->buf);
|
|
/* Handle the case where the message is filtered out by
|
|
* droplevel filters...zero is returned BUT we do NOT want
|
|
* the applications to keep trying...it's NOT a transient
|
|
* error...at least till someone changes droplevels.
|
|
*/
|
|
if (!written_bytes) {
|
|
pr_info("samwrite wrote 0 bytes...droplevels filtering ?\n");
|
|
return -EPERM;
|
|
}
|
|
/* Returned written bytes should be normalized since
|
|
* lower level functions returns the number of bytes
|
|
* effectively written including the prepended header
|
|
* file... IF, when required to write n, we return n+X,
|
|
* some applications could behave badly trying to access
|
|
* file at *fpos=n+X next time, ending up in a regular
|
|
* EFAULT error anyway.
|
|
*/
|
|
if (written_bytes > count)
|
|
written_bytes = count;
|
|
*f_pos += written_bytes;
|
|
}
|
|
|
|
return written_bytes;
|
|
}
|
|
|
|
const struct file_operations samwrite_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = samwritefile_open,
|
|
.write = samwritefile_write,
|
|
.release = samwritefile_release,
|
|
};
|
|
|
|
#ifndef CONFIG_SCSC_LOGRING_DEBUGFS
|
|
static int samlog_devfs_init(struct scsc_debugfs_info *di)
|
|
{
|
|
int ret;
|
|
|
|
pr_info("%s init\n", __func__);
|
|
/* allocate device number */
|
|
ret = alloc_chrdev_region(&di->devt, 0, LOGRING_MAX_DEV, DRV_NAME);
|
|
if (ret) {
|
|
pr_err("%s. Failed to register character device\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
di->logring_class = class_create(THIS_MODULE, DRV_NAME);
|
|
if (IS_ERR(di->logring_class)) {
|
|
unregister_chrdev_region(di->devt, LOGRING_MAX_DEV);
|
|
pr_err("%s. Failed to create character class\n", __func__);
|
|
return PTR_ERR(di->logring_class);
|
|
}
|
|
|
|
pr_info("%s allocated device number major %i minor %i\n",
|
|
__func__, MAJOR(di->devt), MINOR(di->devt));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void samlog_remove_chrdev(struct scsc_debugfs_info *di)
|
|
{
|
|
pr_info("%s\n", __func__);
|
|
|
|
/* Destroy device. */
|
|
device_destroy(di->logring_class, di->devt);
|
|
|
|
/* Unregister the device class.*/
|
|
class_unregister(di->logring_class);
|
|
|
|
/* Destroy created class. */
|
|
class_destroy(di->logring_class);
|
|
|
|
unregister_chrdev_region(di->devt, LOGRING_MAX_DEV);
|
|
}
|
|
|
|
static int samlog_create_char_dev(struct scsc_debugfs_info *di, struct cdev *cdev,
|
|
const struct file_operations *fops, const char *name, int minor)
|
|
{
|
|
dev_t devn;
|
|
char dev_name[20];
|
|
int ret;
|
|
struct device *device;
|
|
|
|
pr_info("%s\n", __func__);
|
|
|
|
devn = MKDEV(MAJOR(di->devt), MINOR(minor));
|
|
|
|
cdev_init(cdev, fops);
|
|
ret = cdev_add(cdev, devn, 1);
|
|
if (ret < 0) {
|
|
pr_err( "couldn't create SAMSG char device\n");
|
|
return ret;
|
|
}
|
|
|
|
snprintf(dev_name, sizeof(dev_name), name);
|
|
/* create driver file */
|
|
device = device_create(di->logring_class, NULL, devn,
|
|
NULL, dev_name);
|
|
if (IS_ERR(device)) {
|
|
pr_err( "couldn't create samlog driver file for %s\n", name);
|
|
ret = PTR_ERR(device);
|
|
return ret;
|
|
}
|
|
pr_err("create dev %d\n", cdev->dev);
|
|
return 0;
|
|
}
|
|
|
|
static int samlog_remove_char_dev(struct scsc_debugfs_info *di, struct cdev *cdev)
|
|
{
|
|
pr_info("%s\n", __func__);
|
|
|
|
/* Destroy device. */
|
|
pr_err("destroy dev %d\n", cdev->dev);
|
|
device_destroy(di->logring_class, cdev->dev);
|
|
/* remove char device*/
|
|
cdev_del(cdev);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
/**
|
|
* Initializes debugfs support build the proper debugfs file dentries:
|
|
* - entries in debugfs are created under /sys/kernel/debugfs/scsc/@name/
|
|
* - using the provided rb ring buffer as underlying ring buffer, storing it
|
|
* into inode ptr for future retrieval (via open)
|
|
* - registers the proper fops
|
|
*/
|
|
void __init *samlog_debugfs_init(const char *root_name, void *rb)
|
|
{
|
|
struct scsc_debugfs_info *di = NULL;
|
|
|
|
if (!rb || !root_name)
|
|
return NULL;
|
|
di = kmalloc(sizeof(*di), GFP_KERNEL);
|
|
if (!di)
|
|
return NULL;
|
|
#ifdef CONFIG_SCSC_LOGRING_DEBUGFS
|
|
if (!scsc_debugfs_root) {
|
|
/* I could have multiple rings debugfs entry all rooted at
|
|
* the same /sys/kernel/debug/scsc/...so such entry could
|
|
* already exist.
|
|
*/
|
|
scsc_debugfs_root = debugfs_create_dir(SCSC_DEBUGFS_ROOT, NULL);
|
|
if (!scsc_debugfs_root)
|
|
goto no_root;
|
|
}
|
|
di->rootdir = scsc_debugfs_root;
|
|
di->bufdir = debugfs_create_dir(root_name, di->rootdir);
|
|
if (!di->bufdir)
|
|
goto no_buf;
|
|
atomic_inc(&scsc_debugfs_root_refcnt);
|
|
/* Saving ring ref @rb to Inode */
|
|
di->samsgfile = debugfs_create_file(SCSC_SAMSG_FNAME, 0444,
|
|
di->bufdir, rb, &samsg_fops);
|
|
if (!di->samsgfile)
|
|
goto no_samsg;
|
|
/* Saving ring ref @rb to Inode */
|
|
di->samlogfile = debugfs_create_file(SCSC_SAMLOG_FNAME, 0444,
|
|
di->bufdir, rb, &samlog_fops);
|
|
if (!di->samlogfile)
|
|
goto no_samlog;
|
|
di->statfile = debugfs_create_file(SCSC_STAT_FNAME, 0444,
|
|
di->bufdir, rb, &stat_fops);
|
|
if (!di->statfile)
|
|
goto no_statfile;
|
|
|
|
di->samwritefile = debugfs_create_file(SCSC_SAMWRITE_FNAME, 0220,
|
|
di->bufdir, NULL,
|
|
&samwrite_fops);
|
|
if (!di->samwritefile)
|
|
goto no_samwrite;
|
|
|
|
pr_info("Samlog Debugfs Initialized\n");
|
|
#else
|
|
di->rb = rb;
|
|
|
|
if (samlog_devfs_init(di))
|
|
goto no_chrdev;
|
|
if (samlog_create_char_dev(di, &di->cdev_samsg, &samsg_fops, SCSC_SAMSG_FNAME, 0))
|
|
goto no_samsg;
|
|
if (samlog_create_char_dev(di, &di->cdev_samlog, &samlog_fops, SCSC_SAMLOG_FNAME, 1))
|
|
goto no_samlog;
|
|
if (samlog_create_char_dev(di, &di->cdev_stat, &stat_fops, SCSC_STAT_FNAME, 2))
|
|
goto no_statfile;
|
|
if (samlog_create_char_dev(di, &di->cdev_samwrite, &samwrite_fops, SCSC_SAMWRITE_FNAME, 3))
|
|
goto no_samwrite;
|
|
|
|
pr_info("Samlog Devfs Initialized\n");
|
|
#endif
|
|
return di;
|
|
|
|
#ifdef CONFIG_SCSC_LOGRING_DEBUGFS
|
|
no_samwrite:
|
|
debugfs_remove(di->statfile);
|
|
no_statfile:
|
|
debugfs_remove(di->samlogfile);
|
|
no_samlog:
|
|
debugfs_remove(di->samsgfile);
|
|
no_samsg:
|
|
debugfs_remove(di->bufdir);
|
|
atomic_dec(&scsc_debugfs_root_refcnt);
|
|
no_buf:
|
|
if (!atomic_read(&scsc_debugfs_root_refcnt)) {
|
|
debugfs_remove(scsc_debugfs_root);
|
|
scsc_debugfs_root = NULL;
|
|
}
|
|
no_root:
|
|
#else
|
|
no_samwrite:
|
|
samlog_remove_char_dev(di, &di->cdev_stat);
|
|
no_statfile:
|
|
samlog_remove_char_dev(di, &di->cdev_samlog);
|
|
no_samlog:
|
|
samlog_remove_char_dev(di, &di->cdev_samsg);
|
|
no_samsg:
|
|
/* remove alloc_char dev*/
|
|
samlog_remove_chrdev(di);
|
|
no_chrdev:
|
|
#endif
|
|
kfree(di);
|
|
return NULL;
|
|
}
|
|
|
|
void __exit samlog_debugfs_exit(void **priv)
|
|
{
|
|
struct scsc_debugfs_info **di = NULL;
|
|
|
|
if (!priv)
|
|
return;
|
|
di = (struct scsc_debugfs_info **)priv;
|
|
if (di && *di) {
|
|
#ifdef CONFIG_SCSC_LOGRING_DEBUGFS
|
|
debugfs_remove_recursive(scsc_debugfs_root);
|
|
atomic_dec(&scsc_debugfs_root_refcnt);
|
|
if (!atomic_read(&scsc_debugfs_root_refcnt)) {
|
|
debugfs_remove(scsc_debugfs_root);
|
|
scsc_debugfs_root = NULL;
|
|
}
|
|
#else
|
|
samlog_remove_char_dev(*di, &(*di)->cdev_samwrite);
|
|
samlog_remove_char_dev(*di, &(*di)->cdev_stat);
|
|
samlog_remove_char_dev(*di, &(*di)->cdev_samlog);
|
|
samlog_remove_char_dev(*di, &(*di)->cdev_samsg);
|
|
/* remove alloc_char dev*/
|
|
samlog_remove_chrdev(*di);
|
|
#endif
|
|
kfree(*di);
|
|
*di = NULL;
|
|
}
|
|
pr_info("Debugfs Cleaned Up\n");
|
|
}
|