/** * @file klogger_393.c * @brief Record strings with timestamps (using FPGA time) to a memory buffer (no I/O), * Read the buffer as character device * @copyright Copyright (C) 2016 Elphel, Inc. * @par <b>License</b> * 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 3 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, see <http://www.gnu.org/licenses/>. */ #include <linux/module.h> #include <linux/spinlock.h> #include <linux/io.h> #include <linux/errno.h> #include <linux/platform_device.h> // For sysfs #include <linux/slab.h> //kzalloc #include <linux/fs.h> #include <asm/uaccess.h> // copy_*_user #include <linux/of.h> // Device Tree #include <uapi/elphel/c313a.h> // PARS_FRAMES_MASK #include <uapi/elphel/x393_devices.h> // For sysfs #include "x393.h" #include "x393_fpga_functions.h" #include "klogger_393.h" #define KLOGGER_BUFFER_ANY_SIZE 1 // any buffer size, use multiple subtractions to convert file pointer to buffer pointer //#define DEV393_KLOGGER ("klogger_393", "klogger_393", 144, 1, "0666", "c") ///< kernel event logger to memory (no i/o) #ifdef LOCK_BH_KLOGGER #define FLAGS_KLOGGER_BH #define LOCK_KLOGGER_BH(x) spin_lock_bh(x) #define UNLOCK_KLOGGER_BH(x) spin_unlock_bh(x) #else #define FLAGS_KLOGGER_BH unsigned long flags; #define LOCK_KLOGGER_BH(x) spin_lock_irqsave(x,flags) #define UNLOCK_KLOGGER_BH(x) spin_unlock_irqrestore(x,flags) #endif static DEFINE_SPINLOCK(klogger_lock); static struct device *g_dev_ptr=NULL; ///< Global pointer to basic device structure. This pointer is used in debugfs output functions static u32 buffer_size = 0; #ifndef KLOGGER_BUFFER_ANY_SIZE static u32 buffer_size_order = 0; #endif static u32 klog_mode=0xff; static u32 buffer_wp = 0; static u32 num_writes = 0; static loff_t file_size = 0; // write pointer, 64 bits static char * klog393_buf = NULL; const char klogger393_of_prop_bufsize_name[] = "klogger-393,buffer_size"; const static u32 max_string_len = PAGE_SIZE; ///< maximal string length int klogger393_open(struct inode *inode, struct file *filp); int klogger393_release(struct inode *inode, struct file *filp); loff_t klogger393_llseek(struct file * file, loff_t offset, int orig); ssize_t klogger393_read (struct file * file, char * buf, size_t count, loff_t *off); // printk(); int print_klog393(const int mode, ///< bits 0: timestamp, 1 - file, 2 - function, 3 - line number, 4 - lock, irq & disable. all 0 - disabled const char *file, /// file path to show const char *function, /// function name to show const int line, // line number to show const char *fmt, ...) ///< Format and argumants as in printf { FLAGS_KLOGGER_BH char buf[1024]; const char * cp; sec_usec_t ts; va_list args; int mmode= mode & klog_mode; if (!mmode){ return 0; } if (mmode & 16){ // spin_lock_bh(&klogger_lock); LOCK_KLOGGER_BH(&klogger_lock); } if (mmode & 1) { get_fpga_rtc(&ts); snprintf(buf,sizeof(buf),"%ld.%06ld:",ts.sec,ts.usec); klog393_puts(buf); } if ((mmode & 2) && file) { cp = strrchr(file,'/'); cp = cp? (cp+1):file; snprintf(buf,sizeof(buf),"%s:",cp); klog393_puts(buf); } if ((mmode & 4) && function) { snprintf(buf,sizeof(buf),"%s:",function); klog393_puts(buf); } if ((mmode & 8) && line) { snprintf(buf,sizeof(buf),"%d:",line); klog393_puts(buf); } va_start(args, fmt); vsnprintf(buf,sizeof(buf),fmt,args); va_end(args); klog393_puts(buf); if (mmode & 16){ // spin_unlock_bh(&klogger_lock); UNLOCK_KLOGGER_BH(&klogger_lock); } return 0; } /** Put string into the logger buffer*/ int klog393_puts(const char * str) ///< String to log, limited by max_string_len (currently 4096 bytes) ///< @return 0 - OK, -EMSGSIZE - string too long { int sl = strlen(str); u32 new_wp= buffer_wp+sl; if (!(klog_mode & 0x80)){ return 0; // DEBUGGING: Do nothing if bit 7 == 0 } // u32 pl; if (sl > max_string_len){ dev_err(g_dev_ptr,"%s: String too long (%d >%d)\n", __func__,strlen(str), max_string_len ); return -EMSGSIZE; } // See if the string fits in the buffer if (likely(new_wp < buffer_size)){ memcpy(klog393_buf+buffer_wp, str,sl); buffer_wp = new_wp; } else { memcpy(klog393_buf+buffer_wp, str, buffer_size - buffer_wp); memcpy(klog393_buf, str, sl - (buffer_size - buffer_wp)); buffer_wp = new_wp - buffer_size; } file_size += sl; num_writes++; return 0; } /** Put string into the logger buffer, preceded by a timestamp "<sec>.<usec>: "*/ int klog393_ts(const char * str) ///< String to log, limited by max_string_len (currently 4096 bytes) ///< @return 0 - OK, -EMSGSIZE - string too long { sec_usec_t ts; char buf[20]; // 18 enough get_fpga_rtc(&ts); sprintf(buf,"%ld.%06ld: ",ts.sec,ts.usec); klog393_puts(buf); return klog393_puts(str); } static struct file_operations framepars_fops = { owner: THIS_MODULE, open: klogger393_open, llseek: klogger393_llseek, read: klogger393_read, release: klogger393_release }; /** Driver OPEN method */ int klogger393_open(struct inode *inode, ///< inode pointer struct file *filp) ///< file pointer ///< @return OK - 0, -EINVAL for wrong minor { int minor= MINOR(inode->i_rdev); filp->private_data = (int *) minor; // store just minor there dev_dbg(g_dev_ptr,"minor=0x%x\n", minor); switch (minor) { case DEV393_MINOR(DEV393_KLOGGER): inode->i_size = file_size; //or return 8 - number of frame pages? return 0; default: return -EINVAL; } } /** Driver RELEASE method */ int klogger393_release(struct inode *inode, ///< inode pointer struct file *filp) ///< file pointer ///< @return OK - 0, -EINVAL for wrong minor { int minor= MINOR(inode->i_rdev); switch ( minor ) { case DEV393_MINOR(DEV393_KLOGGER): dev_dbg(g_dev_ptr,"Release DONE, minor=0x%x\n", minor); break; default: return -EINVAL; } return 0; } /** Driver LLSEEK method */ loff_t klogger393_llseek(struct file * file, ///< file structure pointer loff_t offset, ///< 64-bit offset int orig) ///< Offset origin (SEEK_SET==0, SEEK_CUR==1, SEEK_END==2 - see fs.h) ///< @return new file position or negative error { int minor=(int)file->private_data; dev_dbg(g_dev_ptr,"file=%x, offset=%llx (%d), orig=%x\r\n", (int) file, offset,(int) offset, (int) orig); switch (minor) { case DEV393_MINOR(DEV393_KLOGGER): switch (orig) { case SEEK_SET: file->f_pos = offset; break; case SEEK_CUR: file->f_pos += offset; break; case SEEK_END: //!overload later? if (offset<=0) { file->f_pos = file_size + offset; } else { file->f_pos = file_size + offset; // Overload if needed as usual } break; default: dev_err(g_dev_ptr,"lseek: invalid orig=%d\n", orig); return -EINVAL; } break; default: dev_err(g_dev_ptr,"lseek: invalid minor=%d\n", minor); return -EINVAL; } /** truncate position */ if (file->f_pos < 0) { dev_err(g_dev_ptr,"negative position: minor=%d, file->f_pos=0x%llx\n", minor, file->f_pos); file->f_pos = 0; return (-EOVERFLOW); } // No sense to output overwritten data in the buffer if (file->f_pos < (file_size - buffer_size)) file->f_pos = (file_size - buffer_size); // enable seeking beyond buffer - it now is absolute position in the data stream if (file->f_pos > file_size) { file->f_pos = file_size; return (-EOVERFLOW); } return (file->f_pos); } ssize_t klogger393_read (struct file * file, ///< file structure pointer char * buf, ///< user buffer to get data size_t count, ///< number of bytes to read loff_t *off) ///< start/current position to read from ///< @return number of bytes placed in the buffer { int minor=(int)file->private_data; off_t buffer_rp; u32 len; u32 not_copied; switch (minor) { case DEV393_MINOR(DEV393_KLOGGER): // Increase *off if it points to already overwritten data if (*off < (file_size - buffer_size)) { *off = (file_size - buffer_size); // Keep count the same? } // Truncate count if it exceeds remaining size in buffer if (count > (file_size - *off)) count = file_size - *off; #ifdef KLOGGER_BUFFER_ANY_SIZE for (buffer_rp = *off; buffer_rp >= buffer_size; buffer_rp -= buffer_size); #else buffer_rp = *offt & buffer_size - 1; // buffer_size should be power of 2 #endif len = count; // first (usually the only) buffer copy if (len > (buffer_size - buffer_rp)) len = (buffer_size - buffer_rp); not_copied=copy_to_user(buf, klog393_buf + buffer_rp, len); // returns number of bytes not copied if (not_copied) { dev_err(g_dev_ptr,"1. tried to copy 0x%x bytes to offset 0x%llx, not copied =0x%x bytes\n", count, *off,not_copied); return -EFAULT; } if (len < count) { // wrapping around the buffer not_copied=copy_to_user(buf+len, klog393_buf, count - len); // returns number of bytes not copied if (not_copied) { dev_err(g_dev_ptr,"2. tried to copy 0x%x bytes to offset 0x%llx, not copied =0x%x bytes\n", count, *off,not_copied); return -EFAULT; } } *off += count; // should we update file->f_pos=*off here too?) return count; break; default: dev_err(g_dev_ptr," Wrong minor=0x%x\n",minor); return -EINVAL; } } // SysFS interface to read/modify video memory map #define SYSFS_PERMISSIONS 0644 /* default permissions for sysfs files */ #define SYSFS_READONLY 0444 #define SYSFS_WRITEONLY 0222 static ssize_t show_mode(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf,"0x%x\n", klog_mode); } static ssize_t store_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { sscanf(buf, "%i", &klog_mode); return count; } static ssize_t show_stats(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf,"Number of writes: 0x%x\n" "Buffer size: 0x%x (%d)\n" "Buffer write pointer: 0x%x (%d)\n" "File size: 0x%llx (%lld)\n" "Mode: 0x%x\n", num_writes, buffer_size, buffer_size, buffer_wp, buffer_wp, file_size, file_size, klog_mode); } static DEVICE_ATTR(klogger_mode, SYSFS_PERMISSIONS, show_mode, store_mode); static DEVICE_ATTR(klogger_stats, SYSFS_READONLY, show_stats, NULL); static struct attribute *root_dev_attrs[] = { &dev_attr_klogger_mode.attr, &dev_attr_klogger_stats.attr, NULL }; static const struct attribute_group dev_attr_root_group = { .attrs = root_dev_attrs, .name = NULL, }; static int klogger_393_sysfs_register(struct platform_device *pdev) { int retval=0; struct device *dev = &pdev->dev; if (&dev->kobj) { if (((retval = sysfs_create_group(&dev->kobj, &dev_attr_root_group)))<0) return retval; } return retval; } int klogger_393_probe(struct platform_device *pdev) { int res; struct device *dev = &pdev->dev; const char * config_string; struct device_node *node = pdev->dev.of_node; const __be32 *bufsize_be; g_dev_ptr = dev; buffer_wp = 0; if (node) { #if 0 if (of_property_read_string(node, klogger393_of_prop_bufsize_name, &config_string)) { dev_err(dev,"%s: Device tree has entry for "DEV393_NAME(DEV393_KLOGGER)", but no '%s' property is provided\n", __func__,klogger393_of_prop_bufsize_name); return -EINVAL; } if (!sscanf(config_string,"%i", &buffer_size)){ dev_err(dev,"%s: Invalid buffer size for "DEV393_NAME(DEV393_KLOGGER)".%s - %s\n", __func__,klogger393_of_prop_bufsize_name,config_string); return -EINVAL; } #endif bufsize_be = (__be32 *)of_get_property(node, klogger393_of_prop_bufsize_name, NULL); if (!bufsize_be) { dev_err(dev,"%s: Device tree has entry for "DEV393_NAME(DEV393_KLOGGER)", but no '%s' property is provided\n", __func__,klogger393_of_prop_bufsize_name); return -EINVAL; } buffer_size = be32_to_cpup(bufsize_be); //__builtin_clz #ifndef KLOGGER_BUFFER_ANY_SIZE buffer_size_order = 31 - __builtin_clz(buffer_size); if (buffer_size & (( 1 << buffer_size_order) -1)){ buffer_size_order++; buffer_size = 1 << buffer_size_order; dev_warn(dev,"%s: Increased buffer size to %d (0x%x) to make it a power of 2 bytes\n", __func__,buffer_size,buffer_size); } #endif dev_info(dev,"%s: Setting up buffer for logging "DEV393_NAME(DEV393_KLOGGER)" of %d(0x%x) bytes\n", __func__,buffer_size,buffer_size); // klog393_buf = devm_kzalloc(dev, buffer_size, GFP_KERNEL); klog393_buf = kzalloc(buffer_size, GFP_KERNEL); if (!klog393_buf){ buffer_size = 0; dev_err(dev,"%s: Failed to create buffer for "DEV393_NAME(DEV393_KLOGGER)" of %d(0x%x) bytes\n", __func__,buffer_size,buffer_size); return -ENOMEM; } dev_info(dev,"%s: Set up buffer for logging "DEV393_NAME(DEV393_KLOGGER)" of %d(0x%x) bytes @0x%08x\n", __func__,buffer_size,buffer_size, (int) klog393_buf); res = register_chrdev(DEV393_MAJOR(DEV393_KLOGGER), DEV393_NAME(DEV393_KLOGGER), &framepars_fops); if (res < 0) { dev_err(dev, "klogger_393_probe: couldn't get a major number %d (DEV393_MAJOR(DEV393_KLOGGER)).\n", DEV393_MAJOR(DEV393_KLOGGER)); return res; } } else { dev_warn(dev,"%s: No entry for "DEV393_NAME(DEV393_KLOGGER)", in Device Tree, logger is disabled\n", __func__); } res = klogger_393_sysfs_register(pdev); dev_info(dev, DEV393_NAME(DEV393_KLOGGER)": registered sysfs, result = %d\n", res); return 0; } int klogger_393_remove(struct platform_device *pdev) { if (klog393_buf) { // devm_kfree(&pdev->dev, klog393_buf); // actually not needed } unregister_chrdev(DEV393_MAJOR(DEV393_KLOGGER), DEV393_NAME(DEV393_KLOGGER)); return 0; } static const struct of_device_id klogger_393_of_match[] = { { .compatible = "elphel,klogger-393-1.00" }, { /* end of list */ } }; MODULE_DEVICE_TABLE(of, klogger_393_of_match); static struct platform_driver klogger_393 = { .probe =klogger_393_probe, .remove =klogger_393_remove, .driver = { .name = DEV393_NAME(DEV393_KLOGGER), .of_match_table = klogger_393_of_match, }, }; module_platform_driver(klogger_393); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Andrey Filippov <andrey@elphel.com>."); MODULE_DESCRIPTION("Record/playback strings with FPGA timestams");