Linux下自定义虚拟串口驱动
2015-11-28 23:03
555 查看
前些天给新的板子修改BUG的时候,发现这块板子的串口是接在板载MCU上,我们的主SOC(海思HI3520D)上已经没有多余的串口。于是问题来了… 我们很多的上层应用都是通过串口和传感器通讯的,而且程序都是默认使用SOC上的串口,即打开/dev/ttyAMAx,然后调用Linux的select系统调用去和传感器交互数据的,代码都是C++写的,如果要做到兼容,必须修改基类的方法,这不符合软件的开闭原则…. 所以我打算写一个虚拟串口驱动,让内核去欺骗上层应用:”喂,你打开的真的是一个串口哦”。然后再定义一个虚拟串口管理进程,让这个进程去和单片机通讯进行实际串口收发。
明天开始调试…. (2015-11-28)
今天终于完成了单元测试,虚拟串口的select读写均正常,另外使用了异步通知的方式来通知管理者进程,去更新实际的串口,先上一串调试通过的代码。(2015-12-1)
添加了多串口的支持,修正了资源竞争的问题。(2015-12-2)
驱动代码
今天先上一个尚未测试的驱动代码,按照linux uart框架写的,并且添加了一个给虚拟串口管理进程使用设备节点。这段代码可能会因为内核版本不同而编译不过,目前在3.16.0-30-generic内核版本下编译成功。明天开始调试…. (2015-11-28)
今天终于完成了单元测试,虚拟串口的select读写均正常,另外使用了异步通知的方式来通知管理者进程,去更新实际的串口,先上一串调试通过的代码。(2015-12-1)
添加了多串口的支持,修正了资源竞争的问题。(2015-12-2)
[code]/* * Dummy serial driver by sdliu <sdliu@hongdian.com> * The real hardware was installed in the MCU which is attached to our SOC(HI3520D). * This driver depends on a serial manager which will process in the userspace */ #include <linux/module.h> #include <linux/ioport.h> #include <linux/init.h> #include <linux/console.h> #include <linux/sysrq.h> #include <linux/device.h> #include <linux/tty.h> #include <linux/tty_flip.h> #include <linux/serial_core.h> #include <linux/serial.h> #include <linux/amba/bus.h> #include <linux/amba/serial.h> #include <linux/clk.h> #include <linux/slab.h> #include <linux/dmaengine.h> #include <linux/dma-mapping.h> #include <linux/scatterlist.h> #include <linux/delay.h> #include <linux/poll.h> #include <linux/tty_flip.h> #include <linux/circ_buf.h> #include <asm/io.h> #include <linux/platform_device.h> // #include <asm/sizes.h> #define prt_dbg #ifdef prt_dbg #define drintk printk #else #define drintk(...) #endif #define DUMMY_SERIAL_NR 4 static int dummy_serial_major = 0; static int dummy_serial_minor_start = 0; static struct dummy_uart_port *dummy_array[DUMMY_SERIAL_NR]; unsigned int dummy_serial_nr = 1; module_param(dummy_serial_nr, uint, S_IRUGO); // static DECLARE_WAIT_QUEUE_HEAD(dummy_wq); int dummy_fasync(int fd, struct file *filp, int mode); struct dummy_port_data { // unsigned char port_idx; unsigned long tx_fifo_size; unsigned long rx_fifo_size; }; struct dummy_uart_port { struct uart_port port; struct dummy_port_data *port_data; char type[12]; unsigned char *tx_fifo; unsigned char *rx_fifo; unsigned long tx_len; unsigned long rx_len; unsigned int mctrl; unsigned int baud; struct ktermios termios; struct fasync_struct *async_queue; struct semaphore async_sem; struct completion manager_activie; struct completion write_ok; wait_queue_head_t poll_wq; int manager_reset; int is_default_termios : 1; unsigned long status; struct cdev c_dev; int index; }; static struct class *dummy_class; static unsigned int dummy_tx_empty(struct uart_port *port) { struct dummy_uart_port *dummy = (struct dummy_uart_port *)port; drintk("dummy_tx_empty %d\n", dummy->tx_len); // return TIOCSER_TEMT; return dummy->tx_len > 0 ? 0 : TIOCSER_TEMT; } static void dummy_set_mctrl(struct uart_port *port, unsigned int mctrl) { struct dummy_uart_port *dummy = (struct dummy_uart_port *)port; dummy->mctrl = mctrl; drintk("dummy_set_mctrl!\n"); } static unsigned int dummy_get_mctrl(struct uart_port *port) { struct dummy_uart_port *dummy = (struct dummy_uart_port *)port; drintk("dummy_get_mctrl!\n"); return dummy->mctrl; } static void dummy_stop_tx(struct uart_port *port) { drintk("dummy_stop_tx!\n"); } static void dummy_start_tx(struct uart_port *port) { struct dummy_uart_port *dummy = (struct dummy_uart_port*)port; struct circ_buf *xmit = &port->state->xmit; int i = 0; drintk("dummy_start_tx!\n"); // dummy->tx_len = 0; do { dummy->tx_fifo[dummy->tx_len++] = xmit->buf[xmit->tail]; xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); port->icount.tx++; if (uart_circ_empty(xmit)) { break; } } while (dummy->tx_len < dummy->port_data->tx_fifo_size); for (i = 0; i < dummy->tx_len; i++) { drintk("%c ", dummy->tx_fifo[i]); } drintk("\n"); wake_up(&dummy->poll_wq); init_completion(&dummy->write_ok); wait_for_completion(&dummy->write_ok); } static void dummy_stop_rx(struct uart_port *port) { drintk("dummy_stop_rx!\n"); } static void dummy_enable_ms(struct uart_port *port) { drintk("dummy_enable_ms!\n"); } static void dummy_break_ctl(struct uart_port *port, int break_state) { drintk("dummy_break_ctl!\n"); } static int dummy_startup(struct uart_port *port) { drintk("dummy_startup!\n"); return 0; } static void dummy_shutdown(struct uart_port *port) { drintk("dummy_shutdown!\n"); } static void dummy_flush_buffer(struct uart_port *port) { drintk("dummy_flush_buffer!\n"); } static void dummy_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old) { unsigned int baud = 0; struct dummy_uart_port *dummy = (struct dummy_uart_port*)port; baud = uart_get_baud_rate(port, termios, old, 0, 460800); dummy->baud = baud; memcpy(&dummy->termios, termios, sizeof(struct ktermios)); drintk("set baudrate to %d\n", baud); #if 1 down(&dummy->async_sem); if (dummy->async_queue == NULL) { up(&dummy->async_sem); init_completion(&dummy->manager_activie); wait_for_completion(&dummy->manager_activie); } else { up(&dummy->async_sem); } kill_fasync(&dummy->async_queue, SIGIO, POLL_IN); #endif } static const char *dummy_type(struct uart_port *port) { struct dummy_uart_port *dummy = (struct dummy_uart_port*)port; drintk("dummy_type\n"); return dummy->type; } static void dummy_release_port(struct uart_port *port) { drintk("dummy_release_port\n"); } static int dummy_request_port(struct uart_port *port) { drintk("dummy_request_port\n"); return 0; } static void dummy_config_port(struct uart_port *port, int flags) { drintk("dummy_config_port\n"); dummy_request_port(port); drintk("port->type = %d\n", port->type); port->type = PORT_AMBA; } static int dummy_verify_port(struct uart_port *port, struct serial_struct *ser) { drintk("dummy_verify_port\n"); return 0; } int dummy_open(struct inode *i, struct file *file) { int minor = iminor(i); int index = minor - dummy_serial_minor_start; file->private_data = dummy_array[index]; drintk("minor %d index %d private_data %p\n", minor, index, file->private_data); return 0; } int dummy_release(struct inode *i, struct file *file) { struct dummy_uart_port *dummy = (struct dummy_uart_port *)file->private_data; dummy_fasync(-1, file, 0); down(&dummy->async_sem); dummy->async_queue = NULL; dummy->manager_reset = 1; up(&dummy->async_sem); file->private_data = NULL; return 0; } ssize_t dummy_read(struct file *file, char __user *buf, size_t size, loff_t *offset) { struct dummy_uart_port *dummy = (struct dummy_uart_port *)file->private_data; unsigned long tx_len = dummy->tx_len; int read_len = 0; read_len = size > tx_len ? tx_len : size; copy_to_user(buf, dummy->tx_fifo, read_len); dummy->tx_len -= read_len; if (dummy->tx_len == 0) complete(&dummy->write_ok); return read_len; } ssize_t dummy_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) { struct dummy_uart_port *dummy = (struct dummy_uart_port *)file->private_data; // struct tty_struct *tty = NULL; struct uart_port *port = NULL; unsigned char ch = 0; int i = 0; // struct tty_port *tty = NULL; if (dummy == NULL) { printk("dummy is nullptr\n"); return -EIO; } port = &dummy->port; if (dummy->port.state == NULL) { printk("dummy->port.state is nullptr\n"); return -EIO; } struct tty_port *tty = NULL; tty = &dummy->port.state->port; // tty = dummy->port.state->port.tty; if (tty == NULL) { printk("tty is nullptr\n"); return -EIO; } /* tb = tty->buf.tail; if (tb) printk("tb->used %d tb->size %d\n", tb->used, tb->size); */ copy_from_user(dummy->rx_fifo, buf, size); dummy->rx_len = size; // printk("size %lu buf %s\n", dummy->rx_len, dummy->rx_fifo); // insert chars for (i = 0; i < size ; i++) { ch = *(dummy->rx_fifo + i); port->icount.rx++; tty_insert_flip_char(tty, ch, 0); } tty_flip_buffer_push(tty); return size; } long dummy_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct dummy_uart_port *dummy = (struct dummy_uart_port *)file->private_data; switch (cmd){ case 0xde: printk("dummy_ioctl baud %d\n", dummy->baud); copy_to_user((void *)arg, &dummy->baud, sizeof(unsigned int)); break; default: break; } return 0; } unsigned int dummy_poll(struct file *file, struct poll_table_struct *pwait) { struct dummy_uart_port *dummy = (struct dummy_uart_port *)file->private_data; unsigned int mask = 0; printk("%d tx_len %d\n", dummy->index, dummy->tx_len); poll_wait(file, &dummy->poll_wq, pwait); if (dummy->tx_len) mask |= POLLIN | POLLRDNORM; return mask; } int dummy_fasync(int fd, struct file *filp, int mode) { struct dummy_uart_port *dummy = (struct dummy_uart_port *)filp->private_data; int ret = 0; down(&dummy->async_sem); ret = fasync_helper(fd, filp, mode, &dummy->async_queue); // 如果管理进程重置过,则需要主动通知其更新termios if (dummy->manager_reset) { dummy->manager_reset = 0; kill_fasync(&dummy->async_queue, SIGIO, POLL_IN); } up(&dummy->async_sem); complete(&dummy->manager_activie); return ret; } static struct uart_ops dummy_uart_ops = { .tx_empty = dummy_tx_empty, .set_mctrl = dummy_set_mctrl, .get_mctrl = dummy_get_mctrl, .stop_tx = dummy_stop_tx, .start_tx = dummy_start_tx, .stop_rx = dummy_stop_rx, .enable_ms = dummy_enable_ms, .break_ctl = dummy_break_ctl, .startup = dummy_startup, .shutdown = dummy_shutdown, .flush_buffer = dummy_flush_buffer, .set_termios = dummy_set_termios, .type = dummy_type, .release_port = dummy_release_port, .request_port = dummy_request_port, .config_port = dummy_config_port, .verify_port = dummy_verify_port, // .wake_peer = dummy_wake_peer, #ifdef CONFIG_CONSOLE_POLL .poll_get_char = NULL, .poll_put_char = NULL, #endif }; static struct uart_driver dummy_driver = { .owner = THIS_MODULE, .driver_name = "ttyDUM", .dev_name = "ttyDUM", .major = 0, // AUTO allocate .minor = 0, .nr = DUMMY_SERIAL_NR, .cons = NULL, }; struct file_operations dummy_fops = { .open = dummy_open, .release = dummy_release, .read = dummy_read, .write = dummy_write, .unlocked_ioctl = dummy_ioctl, .poll = dummy_poll, .fasync = dummy_fasync, }; int create_manager_device(struct platform_device *pdev, int index) { struct dummy_uart_port *dummy = NULL; struct dummy_port_data *data = NULL; struct device *tmp = NULL; int ret = 0; dev_t dev = 0; char dev_name[64] = {0}; data = (struct dummy_port_data *)pdev->dev.platform_data; if (!data) { printk("not platform data\n"); return -EINVAL; } /* if (data->port_idx >= DUMMY_SERIAL_NR) { printk("invalid dummy serial device\n"); return -EINVAL; } */ dummy = (struct dummy_uart_port *)\ kmalloc(sizeof(struct dummy_uart_port), GFP_KERNEL); if (!dummy) { printk("malloc dummy error\n"); return -ENOMEM; } memset(dummy, 0, sizeof(struct dummy_uart_port)); dummy->index = index; dummy->is_default_termios = 1; sema_init(&dummy->async_sem, 1); init_completion(&dummy->manager_activie); init_waitqueue_head(&dummy->poll_wq); dummy->tx_fifo = (unsigned char*)kmalloc(data->tx_fifo_size, GFP_KERNEL); dummy->rx_fifo = (unsigned char*)kmalloc(data->rx_fifo_size, GFP_KERNEL); if (!dummy->tx_fifo || !dummy->rx_fifo) { printk("dummy fifo kmalloc err rx=%p tx=%p\n", \ dummy->rx_fifo, dummy->tx_fifo); ret = -ENOMEM; goto FIFO_ERR; } dummy->port_data = data; dummy->port.dev = &(pdev->dev); dummy->port.mapbase = 0; dummy->port.membase = (unsigned char *)(0xdeadbeef); dummy->port.iotype = UPIO_MEM; dummy->port.irq = 0; dummy->port.fifosize = 16; dummy->port.ops = &dummy_uart_ops; dummy->port.flags = UPF_BOOT_AUTOCONF; dummy->port.line = index; dummy->port.type = PORT_AMBA; ret = uart_add_one_port(&dummy_driver, &dummy->port); if (ret) { printk("uart drv add one port err. ret = 0x%08x\n", ret); goto PORT_ERR; } dummy_array[index] = dummy; if (!dummy_serial_major) { sprintf(dev_name, "serialdum%d", 0); ret = alloc_chrdev_region(&dev, 0, DUMMY_SERIAL_NR, dev_name); if (!ret) { dummy_serial_major = MAJOR(dev); dummy_serial_minor_start = MINOR(dev); // printk("major %d minor_start %d\n", dummy_serial_major, dummy_serial_minor_start); } else { printk("register cdev err, ret=%d\n", ret); goto DEV_ERR; } } else { dev = MKDEV(dummy_serial_major, dummy_serial_minor_start + index); sprintf(dev_name, "serialdum%d", index); } cdev_init(&dummy->c_dev, &dummy_fops); dummy->c_dev.owner = THIS_MODULE; cdev_add(&dummy->c_dev, dev, DUMMY_SERIAL_NR); tmp = device_create(dummy_class, NULL, dev, NULL, dev_name); if (NULL == tmp) { printk("create device err! %d, %s\n", dev, dev_name); } printk("create dummy serial manager device /dev/%s\n", dev_name); // platform_set_drvdata(pdev, dummy); return ret; DEV_ERR: uart_remove_one_port(&dummy_driver, &dummy->port); PORT_ERR: if (dummy->rx_fifo) kfree(dummy->rx_fifo); if (dummy->tx_fifo) kfree(dummy->tx_fifo); FIFO_ERR: kfree(dummy); return ret; } static int serial_dummy_probe(struct platform_device *pdev) { int i = 0; int ret = 0; for(i = 0; i < dummy_serial_nr; i++) { ret = create_manager_device(pdev, i); if (ret) { printk("create dummy manager device err, index = %d\n", i); } } return 0; } static int serial_dummy_remove(struct platform_device *dev) { struct dummy_uart_port *dummy = NULL; int i = 0; dev_t dev_num = 0; for (i = 0; i < dummy_serial_nr; i++) { dummy = dummy_array[i]; dev_num = MKDEV(dummy_serial_major, dummy_serial_minor_start + i); device_destroy(dummy_class, dev_num); cdev_del(&dummy->c_dev); uart_remove_one_port(&dummy_driver, &dummy->port); if (dummy->rx_fifo) kfree(dummy->rx_fifo); if (dummy->tx_fifo) kfree(dummy->tx_fifo); kfree(dummy); } dev_num = MKDEV(dummy_serial_major, dummy_serial_minor_start); unregister_chrdev_region(dev_num, DUMMY_SERIAL_NR); return 0; } static struct platform_driver dummy_serial_dirver = { .probe = serial_dummy_probe, .remove = serial_dummy_remove, .driver = { .name = "dummy_serial", .owner = THIS_MODULE, } }; static struct dummy_port_data dummy_serial_dev_data = { .tx_fifo_size = 2 * 1024, .rx_fifo_size = 2 * 1024, }; void dummy_serial_release(struct device *dev); static struct platform_device dummy_serial_dev = { .name = "dummy_serial", .dev = { .release = dummy_serial_release, .platform_data = (void *)(&dummy_serial_dev_data), } }; void dummy_serial_release(struct device *dev) { } static int __init serial_dummy_init(void) { int ret = 0; printk("Hi3520d dummy serial drv, by sdliu ver.20151202\n"); if (dummy_serial_nr > DUMMY_SERIAL_NR) { printk("dummy serial nr(%d) is more than max(%d)\n", \ dummy_serial_nr, DUMMY_SERIAL_NR); return -EINVAL; } // 注册平台设备(应当分离出去) ret = platform_device_register(&dummy_serial_dev); dummy_class = class_create(THIS_MODULE, "dumtty"); ret = uart_register_driver(&dummy_driver); if (ret) { printk("dummy drv register err ret = 0x%08x\n", ret); return -EINVAL; } ret = platform_driver_register(&dummy_serial_dirver); if (ret) { printk("dummy platform drv register err ret = %d\n", ret); platform_device_unregister(&dummy_serial_dev); uart_unregister_driver(&dummy_driver); } return ret; } static void __exit serial_dummy_exit(void) { platform_driver_unregister(&dummy_serial_dirver); uart_unregister_driver(&dummy_driver); class_destroy(dummy_class); platform_device_unregister(&dummy_serial_dev); } module_init(serial_dummy_init); module_exit(serial_dummy_exit); MODULE_AUTHOR("sdliu"); MODULE_DESCRIPTION("DUMMY serial driver"); MODULE_LICENSE("GPL");
相关文章推荐
- 解决:CentOS下的 error while loading shared libraries: libmysqlclient.so.16: cannot open shared object fi
- Linux常用服务部署与优化
- Linux笔记分享-crazychao
- CentOS下安装xampp
- 登录linux系统设置默认目录
- linux配置java环境变量(详细)
- [Linux学习笔记] Linux软件包管理
- Linux下查看文件和文件夹大小
- Linux下的磁盘分割和文件系统
- CentOS Linux 监控安装之Zabbix
- Linux Netcat命令
- ARM Linux学习,从宿主机传程序到ARM——环境搭建2
- Linux cscope命令
- linux mount/umount挂载命令解析。
- linux 下 cat
- Linux(centos6.5)下安装jenkins(可行)
- Linux命令学习笔记(1)----cat命令
- linux小结之一:常见文件查找方法
- Linux之——基础命令
- Linux系统下Qt动态库的生成已经动态库的使用实例图解