/* SPDX-License-Identifier: GPL-2.0 */ #include #include #include #include #include #include #include #include #include "rdc_debug.h" #include "wcn_glb.h" #include "wcn_misc.h" #define MDBG_WRITE_SIZE (64) #define WCN_LOG_MAJOR 255 static int wcn_log_major = WCN_LOG_MAJOR; static struct class *wcnlog_class; struct wcnlog_dev { struct cdev cdev; int major; int minor; }; struct mdbg_device_t *mdbg_dev; wait_queue_head_t mdbg_wait; int wake_up_log_wait(void) { if (!mdbg_dev->exit_flag) { wake_up_interruptible(&mdbg_dev->rxwait); wake_up_interruptible(&mdbg_wait); } return 0; } void wcnlog_clear_log(void) { if (mdbg_dev->ring_dev->ring->rp != mdbg_dev->ring_dev->ring->wp) { WCN_INFO("log:%ld left in ringbuf not read\n", (long int)(mdbg_dev->ring_dev->ring->wp - mdbg_dev->ring_dev->ring->rp)); mdbg_ring_clear(mdbg_dev->ring_dev->ring); } } static int wcnlog_open(struct inode *inode, struct file *filp) { struct wcnlog_dev *dev; int minor = iminor(filp->f_path.dentry->d_inode); int minor1 = iminor(inode); int major = imajor(inode); WCN_INFO("minor=%d,minor1=%d,major=%d\n", minor, minor1, major); if (mdbg_dev->exit_flag) { WCN_INFO("wcnlog_open exit!\n"); return -EIO; } dev = container_of(inode->i_cdev, struct wcnlog_dev, cdev); filp->private_data = dev; WCN_DEBUG("wcnlog_open z=%d,major=%d,minor = %d\n", dev->cdev.dev, MAJOR(dev->cdev.dev), MINOR(dev->cdev.dev)); if (mdbg_dev->open_count != 0) MDBG_ERR("open count %d\n", mdbg_dev->open_count); mdbg_dev->open_count++; return 0; } static int wcnlog_release(struct inode *inode, struct file *filp) { struct wcnlog_dev *dev = filp->private_data; WCN_INFO("wcnlog_release z=%d,major=%d,minor = %d\n", dev->cdev.dev, MAJOR(dev->cdev.dev), MINOR(dev->cdev.dev)); mdbg_dev->open_count--; return 0; } static ssize_t wcnlog_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { long int read_size; int timeout = -1; int rval = 0; static unsigned int dum_send_size; struct wcnlog_dev *dev = filp->private_data; if (mdbg_dev->exit_flag) { WCN_INFO("wcnlog_read exit!\n"); return -EIO; } if (filp->f_flags & O_NONBLOCK) timeout = 0; WCN_DEBUG("wcnlog_read timeout=%d,major=%d, minor=%d\n", timeout, dev->major, dev->minor); WCN_DEBUG("wcnlog_read z=%d,major=%d,minor = %d\n", dev->cdev.dev, MAJOR(dev->cdev.dev), MINOR(dev->cdev.dev)); /* count :100K-log, 32K-mem ;cat :4096 */ WCN_DEBUG("wcnlog_read len = %ld\n", (long int)count); if ((functionmask[7] & CP2_FLAG_YLOG) == 1) return -EIO; if (MINOR(dev->cdev.dev) == 1) { WCN_INFO("unsupported to read slog_wcn1: BT audio data\n"); return -EPERM; } if (filp->f_flags & O_NONBLOCK) timeout = 0; if (count > MDBG_RX_RING_SIZE) count = MDBG_RX_RING_SIZE; if (timeout < 0) { /* when kernel go to sleep, it return -512 */ rval = wait_event_interruptible(mdbg_wait, (mdbg_content_len() > 0)); if (rval < 0) WCN_INFO("wait interrupted=%d\n", rval); } mutex_lock(&mdbg_dev->mdbg_lock); read_size = mdbg_receive((void *)buf, (long int)count); if (sprdwcn_bus_get_carddump_status() == 1) { dum_send_size += read_size; WCN_INFO("read_size = %ld dum_total_size= %d,remainder =%ld\n", read_size, dum_send_size, mdbg_content_len()); } /* read_size = log1040 or mem32K, 1024 */ if (read_size > 0) { WCN_DEBUG("Show %ld bytes data.", read_size); rval = read_size; } else if (read_size == 0) rval = -EAGAIN; else { rval = read_size; WCN_DEBUG("log read error %d\n", rval); } mutex_unlock(&mdbg_dev->mdbg_lock); return rval; } static ssize_t wcnlog_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { long int sent_size = 0; char *p_data = NULL; if (mdbg_dev->exit_flag) { WCN_INFO("wcnlog_write exit!\n"); return -EIO; } WCN_INFO("wcnlog_write count=%ld\n", (long int)count); if (count > MDBG_WRITE_SIZE) { WCN_ERR("mdbg_write count > MDBG_WRITE_SIZE\n"); return -ENOMEM; } if (NULL == buf || 0 == count) { WCN_ERR("Param Error!"); return count; } p_data = memdup_user(buf, count); mutex_lock(&mdbg_dev->mdbg_lock); sent_size = mdbg_send_atcmd(p_data, count, WCN_ATCMD_LOG); mutex_unlock(&mdbg_dev->mdbg_lock); kfree(p_data); WCN_DEBUG("sent_size = %ld", sent_size); return sent_size; } static unsigned int wcnlog_poll(struct file *filp, poll_table *wait) { unsigned int mask = 0; MDBG_LOG("wcnlog_poll\n"); if ((!mdbg_dev) || (mdbg_dev->exit_flag)) { WCN_INFO("wcnlog_poll exit!\n"); mask |= POLLIN | POLLERR; return mask; } poll_wait(filp, &mdbg_dev->rxwait, wait); if (mdbg_content_len() > 0) mask |= POLLIN | POLLRDNORM; return mask; } static long wcnlog_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { if (mdbg_dev->exit_flag) { WCN_INFO("wcnlog_ioctl exit!\n"); return -EIO; } return 0; } static const struct file_operations wcnlog_fops = { .open = wcnlog_open, .release = wcnlog_release, .read = wcnlog_read, .write = wcnlog_write, .poll = wcnlog_poll, .unlocked_ioctl = wcnlog_ioctl, .owner = THIS_MODULE, .llseek = default_llseek, }; static int wcnlog_register_device(struct wcnlog_dev *dev, int index) { dev_t devno; int ret; devno = MKDEV(wcn_log_major, index); dev->cdev.owner = THIS_MODULE; cdev_init(&dev->cdev, &wcnlog_fops); ret = cdev_add(&dev->cdev, devno, 1); if (ret != 0) { unregister_chrdev_region(devno, 1); WCN_ERR("Failed to add wcn log cdev\n"); return ret; } dev->major = MAJOR(devno); dev->minor = MINOR(devno); WCN_DEBUG("log dev major=%d,minor=%d\n", dev->major, dev->minor); device_create(wcnlog_class, NULL, MKDEV(MAJOR(devno), MINOR(devno)), NULL, "%s%d", "slog_wcn", index); return 0; } int log_cdev_init(void) { int ret = -1; int i; int iflag = -1; dev_t devno; struct wcnlog_dev *dev[WCN_LOG_MAX_MINOR] = {NULL}; WCN_DEBUG("log_cdev_init\n"); wcnlog_class = class_create(THIS_MODULE, "slog_wcn"); if (IS_ERR(wcnlog_class)) return PTR_ERR(wcnlog_class); devno = MKDEV(wcn_log_major, 0); if (wcn_log_major) ret = register_chrdev_region(devno, WCN_LOG_MAX_MINOR, "wcnlog"); if (ret < 0) { WCN_INFO("failed to apply for static device number"); ret = alloc_chrdev_region(&devno, 0, WCN_LOG_MAX_MINOR, "wcnlog"); wcn_log_major = MAJOR(devno); } if (ret < 0) { WCN_ERR("failed to apply for device number"); return ret; } for (i = 0; i < WCN_LOG_MAX_MINOR; i++) { dev[i] = kmalloc(sizeof(struct wcnlog_dev), GFP_KERNEL); if (!dev[i]) { WCN_ERR("failed alloc mem!\n"); continue; } iflag = 1; ret = wcnlog_register_device(dev[i], i); if (ret != 0) { kfree(dev[i]); dev[i] = NULL; continue; } mdbg_dev->dev[i] = dev[i]; } /* kmalloc fail */ if (iflag == -1) { unregister_chrdev_region(devno, 1); ret = -1; } return ret; } int log_cdev_exit(void) { struct wcnlog_dev *dev[WCN_LOG_MAX_MINOR]; int i; WCN_INFO("log_cdev_exit\n"); for (i = 0; i < WCN_LOG_MAX_MINOR; i++) { dev[i] = mdbg_dev->dev[i]; if (!dev[i]) continue; device_destroy(wcnlog_class, (&(dev[i]->cdev))->dev); cdev_del(&(dev[i]->cdev)); kfree(dev[i]); dev[i] = NULL; } class_destroy(wcnlog_class); unregister_chrdev_region(MKDEV(wcn_log_major, 0), WCN_LOG_MAX_MINOR); return 0; } int log_dev_init(void) { int err; MDBG_FUNC_ENTERY; mdbg_dev = kzalloc(sizeof(struct mdbg_device_t), GFP_KERNEL); if (!mdbg_dev) return -ENOMEM; mdbg_dev->open_count = 0; mutex_init(&mdbg_dev->mdbg_lock); init_waitqueue_head(&mdbg_dev->rxwait); init_waitqueue_head(&mdbg_wait); err = mdbg_ring_init(); if (err < 0) return -ENOMEM; log_cdev_init(); mdbg_dev->exit_flag = 0; return 0; } int log_dev_exit(void) { mdbg_dev->exit_flag = 1; log_cdev_exit(); /* free for old mdbg_dev */ mdbg_ring_remove(); mutex_destroy(&mdbg_dev->mdbg_lock); kfree(mdbg_dev); return 0; }