11.3编写Linux串口驱动
2024-01-07 21:01:01
编写串口驱动主要步骤
- 构建并初始化 struct console 对象,若串口无需支持 console 可省略此步骤
//UART驱动的console
static struct uart_driver virt_uart_drv;
static struct console virt_uart_console = {
//console 的名称,配合index字段使用,如果name为“ttyVIRT”,且index字段为小于0,则可以和“console=ttyVIRT“(n=0,1,2…)来确定index字段的值
.name = "ttyVIRT",
//操作函数集合
.device = virt_uart_console_device,
.write = virt_uart_console_write,
.setup = virt_uart_console_setup,
//CON_PRINTBUFFER表示从buffer中的第一行log开始打印
//CON_CONSDEV表示从earlycon没有打印的log开始打印
.flags = CON_PRINTBUFFER,
//index小于0时通过bootargs参数确定其值
.index = -1,
//console私有数据,这里用于记录拥有此console的串口驱动
.data = &virt_uart_drv,
};
- 构建并初始化 struct uart_driver 对象
//UART驱动
static struct uart_driver virt_uart_drv = {
.owner = THIS_MODULE,
//驱动名称,在dev文件系统中以此为前缀生成设备文件名
.driver_name = "VIRT_UART",
//设备名称
.dev_name = "ttyVIRT",
//主设备号和次设备号起始值
.major = 0,
.minor = 0,
//只有一个端口
.nr = 1,
//UART的console
.cons = &virt_uart_console,
};
- 使用 uart_register_driver 函数注册串口驱动
//注册串口驱动
result = uart_register_driver(&virt_uart_drv);
if(result < 0)
{
printk("register uart driver failed\r\n");
return result;
}
- 构建并初始化 struct uart_port 对象
//UART端口
static struct uart_port virt_port = {};
//设置端口
virt_port.line = 0;
//端口所属设备
virt_port.dev = &pdev->dev;
//串口寄存器物理基地址,iobase、mapbase、membase不能全部为0,否则在初始化时不会执行端口配置操作
virt_port.mapbase = 1;
//端口类型,不能为PORT_UNKNOWN
virt_port.type = PORT_8250;
//io访问方式
virt_port.iotype = UPIO_MEM;
//串口的中断号
virt_port.irq = 0;
//串口端口发送FIFO大小
virt_port.fifosize = 32;
//操作函数集合
virt_port.ops = &virt_uart_ops;
//RS485配置函数
virt_port.rs485_config = NULL;
//执行自动配置,但不探测UART类型
virt_port.flags = UPF_BOOT_AUTOCONF | UPF_FIXED_TYPE;
- 使用 uart_add_one_port 函数在 uart_driver 添加串口端口
//在串口驱动下添加端口
result = uart_add_one_port(&virt_uart_drv, &virt_port);
if(result < 0)
{
printk("add uart port failed\n");
return result;
}
编写串口驱动
这里以一个虚拟串口为例来介绍串口驱动的编写,它在 proc 文件系统中创建了一个文件,通过向这个文件写入数据来模拟串口硬件的接收(写入数据时先将数据写入到虚拟串口接收FIFO中,然后调度工作队列,用以模拟串口中断处理函数来处理写入到虚拟串口接收FIFO中的数据),通过串口发送的数据会写入虚拟串口发送FIFO中,然后可以通过这个文件来读取虚拟串口发送FIFO中的数据,用于模拟串口硬件发送。
编写设备树
在顶层设备树根节点中加入如下节点:
virtual_uart: virtual_uart_controller {
compatible = "atk,virtual_uart";
};
用make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs -j8编译设备树(arm-none-linux-gnueabihf-是编译器前缀),然后用新的.dtb文件启动系统
驱动代码编写
驱动代码的要点前面已经介绍过了,这里给出驱动程序的完整代码:
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/console.h>
#include <linux/sysrq.h>
#include <linux/platform_device.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/rational.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/io.h>
#include <linux/dma-mapping.h>
#include <linux/proc_fs.h>
#include <linux/kfifo.h>
#include <linux/of_irq.h>
#include <linux/sched.h>
//定义一个FIFO,用于模拟串口的接收FIFO
static DEFINE_KFIFO(rx_fifo, char, 128);
//接收标志,非0表示开启接收
uint8_t rx_flag = 0;
//模拟串口接收完成中断的工作队列
static void rx_isr_work(struct work_struct *w);
DECLARE_WORK(rx_work, rx_isr_work);
//定义一个FIFO,用于模拟串口的发送FIFO
static DEFINE_KFIFO(tx_fifo, char, 128);
//发送标志,非0表示开启发送
uint8_t tx_flag = 0;
//模拟串口发送完成中断的工作队列
static void tx_isr_work(struct work_struct *w);
DECLARE_WORK(tx_work, tx_isr_work);
//UART端口
static struct uart_port virt_port = {};
//proc文件,用于模拟串口硬件收发数据
static struct proc_dir_entry *proc_file;
//从虚拟串口的发送FIFO中读取数据,用于模拟串口硬件发送数据
static ssize_t proc_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
unsigned int copied;
//从虚拟串口的发送FIFO中读取数据,模拟串口硬件发送数据
kfifo_to_user(&tx_fifo, buf, size, &copied);
//调度工作队列,模拟产生发送中断
schedule_work(&tx_work);
return copied;
}
//向虚拟串口的接收FIFO写入数据,用于模拟串口硬件接收到数据
static ssize_t proc_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
unsigned int copied;
//将数据写入虚拟串口的接收FIFO,模拟串口硬件接收到数据
kfifo_from_user(&rx_fifo, buf, size, &copied);
//调度工作队列,模拟产生接收中断
schedule_work(&rx_work);
return copied;
}
//proc文件操作函数集合
static const struct file_operations proc_fops = {
.read = proc_read,
.write = proc_write,
};
//虚拟串口接收完成中断
static void rx_isr_work(struct work_struct *w)
{
int rx_count;
int result;
unsigned long flags;
uint8_t buffer[16];
struct tty_port *ttyport = &virt_port.state->port;
if(rx_flag)
{
while(1)
{
//从虚拟串口接收FIFO中读取数据
rx_count = kfifo_out(&rx_fifo, buffer, sizeof(buffer));
if(rx_count <= 0)
break;
//获取自旋锁
spin_lock_irqsave(&virt_port.lock, flags);
//将接收的数据写入行规程
result = tty_insert_flip_string(ttyport, buffer, rx_count);
//更新统计数据
virt_port.icount.rx += result;
//释放自旋锁
spin_unlock_irqrestore(&virt_port.lock, flags);
//通知行规程进行处理
tty_flip_buffer_push(ttyport);
}
}
}
//虚拟串口发送完成中断
static void tx_isr_work(struct work_struct *w)
{
int one;
int two;
int count;
int tx_count;
unsigned long flags;
struct circ_buf *xmit = &virt_port.state->xmit;
//获取自旋锁
spin_lock_irqsave(&virt_port.lock, flags);
tx_count = 0;
//获取环形缓冲区的长度
count = uart_circ_chars_pending(xmit);
if(count > 0)
{
//将端口环形缓冲区的数据写入虚拟串口的发送FIFO
if (xmit->tail < xmit->head)
{
//一次完成拷贝
tx_count = kfifo_in(&tx_fifo, &xmit->buf[xmit->tail], count);
}
else
{
//分两次拷贝
one = UART_XMIT_SIZE - xmit->tail;
if (one > count)
one = count;
two = count - one;
tx_count = kfifo_in(&tx_fifo, &xmit->buf[xmit->tail], one);
if((two > 0) && (tx_count >= one))
tx_count += kfifo_in(&tx_fifo, &xmit->buf[0], two);
}
//更新环形缓冲区
xmit->tail = (xmit->tail + tx_count) & (UART_XMIT_SIZE - 1);
//更新统计数据
virt_port.icount.tx += tx_count;
}
else
tx_flag = 0;
//释放自旋锁
spin_unlock_irqrestore(&virt_port.lock, flags);
}
//发送是否空闲
static unsigned int virt_uart_tx_empty(struct uart_port *port)
{
/* 因为要发送的数据瞬间存入buffer */
return (!tx_flag) ? 1 : 0;
}
//配置流控
static void virt_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
{
}
//获取流控配置
static unsigned int virt_uart_get_mctrl(struct uart_port *port)
{
return 0;
}
//停止发送
static void virt_uart_stop_tx(struct uart_port *port)
{
tx_flag = 0;
}
//启动发送
static void virt_uart_start_tx(struct uart_port *port)
{
int one;
int two;
int count;
int tx_count;
struct circ_buf *xmit = &virt_port.state->xmit;
tx_count = 0;
//设置发送忙标志
tx_flag = 1;
//获取环形缓冲区的长度
count = uart_circ_chars_pending(xmit);
if(count > 0)
{
//将端口环形缓冲区的数据写入虚拟串口的发送FIFO
if(xmit->tail < xmit->head)
{
//一次完成拷贝
tx_count = kfifo_in(&tx_fifo, &xmit->buf[xmit->tail], count);
}
else
{
//分两次拷贝
one = UART_XMIT_SIZE - xmit->tail;
if (one > count)
one = count;
two = count - one;
tx_count = kfifo_in(&tx_fifo, &xmit->buf[xmit->tail], one);
if((two > 0) && (tx_count >= one))
tx_count += kfifo_in(&tx_fifo, &xmit->buf[0], two);
}
//更新环形缓冲区
xmit->tail = (xmit->tail + tx_count) & (UART_XMIT_SIZE - 1);
//更新统计数据
virt_port.icount.tx += tx_count;
}
else
tx_flag = 0;
}
//停止接收
static void virt_uart_stop_rx(struct uart_port *port)
{
rx_flag = 0;
}
//传输控制中断信号
static void virt_uart_break_ctl(struct uart_port *port, int break_state)
{
}
//启动串口
static int virt_uart_startup(struct uart_port *port)
{
//复位接收FIFO
kfifo_reset(&rx_fifo);
//启动接收
rx_flag = 1;
return 0;
}
//关闭串口
static void virt_uart_shutdown(struct uart_port *port)
{
//终止接收发送
rx_flag = 0;
tx_flag = 0;
}
//刷新输出缓冲区
static void virt_uart_flush_buffer(struct uart_port *port)
{
}
//配置端口时序
static void virt_uart_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old)
{
}
//获取端口类型
static const char *virt_uart_type(struct uart_port *port)
{
return (port->type == PORT_8250) ? "VIRTUAL_UART" : NULL;
}
//释放端口
static void virt_uart_release_port(struct uart_port *port)
{
}
//请求端口
static int virt_uart_request_port(struct uart_port *port)
{
return 0;
}
//配置端口
static void virt_uart_config_port(struct uart_port *port, int flags)
{
if (flags & UART_CONFIG_TYPE)
port->type = PORT_8250;
}
//验证端口
static int virt_uart_verify_port(struct uart_port *port, struct serial_struct *ser)
{
return -EINVAL;
}
//console发送函数
static void virt_uart_console_write(struct console *co, const char *s, unsigned int count)
{
//将数据写入发送FIFO
kfifo_in(&tx_fifo, s, count);
}
//获取console所属的tty_driver
struct tty_driver *virt_uart_console_device(struct console *co, int *index)
{
struct uart_driver *p = co->data;
*index = co->index;
return p->tty_driver;
}
//配置console
static int virt_uart_console_setup(struct console *co, char *options)
{
return 0;
}
//UART端口操作函数集合
static const struct uart_ops virt_uart_ops = {
.tx_empty = virt_uart_tx_empty,
.set_mctrl = virt_uart_set_mctrl,
.get_mctrl = virt_uart_get_mctrl,
.stop_tx = virt_uart_stop_tx,
.start_tx = virt_uart_start_tx,
.stop_rx = virt_uart_stop_rx,
.break_ctl = virt_uart_break_ctl,
.startup = virt_uart_startup,
.shutdown = virt_uart_shutdown,
.flush_buffer = virt_uart_flush_buffer,
.set_termios = virt_uart_set_termios,
.type = virt_uart_type,
.release_port = virt_uart_release_port,
.request_port = virt_uart_request_port,
.config_port = virt_uart_config_port,
.verify_port = virt_uart_verify_port,
};
//UART驱动的console
static struct uart_driver virt_uart_drv;
static struct console virt_uart_console = {
//console 的名称,配合index字段使用,如果name为“ttyVIRT”,且index字段为小于0,则可以和“console=ttyVIRT“(n=0,1,2…)来确定index字段的值
.name = "ttyVIRT",
//操作函数集合
.device = virt_uart_console_device,
.write = virt_uart_console_write,
.setup = virt_uart_console_setup,
//CON_PRINTBUFFER表示从buffer中的第一行log开始打印
//CON_CONSDEV表示从earlycon没有打印的log开始打印
.flags = CON_PRINTBUFFER,
//index小于0时通过bootargs参数确定其值
.index = -1,
//console私有数据,这里用于记录拥有此console的串口驱动
.data = &virt_uart_drv,
};
//UART驱动
static struct uart_driver virt_uart_drv = {
.owner = THIS_MODULE,
//驱动名称,在dev文件系统中以此为前缀生成设备文件名
.driver_name = "VIRT_UART",
//设备名称
.dev_name = "ttyVIRT",
//主设备号和次设备号起始值
.major = 0,
.minor = 0,
//只有一个端口
.nr = 1,
//UART的console
.cons = &virt_uart_console,
};
//设备和驱动匹配成功执行
static int virtual_uart_probe(struct platform_device *pdev)
{
int result;
printk("%s\r\n", __FUNCTION__);
//设置端口
virt_port.line = 0;
//端口所属设备
virt_port.dev = &pdev->dev;
//串口寄存器物理基地址,iobase、mapbase、membase不能全部为0,否则在初始化时不会执行端口配置操作
virt_port.mapbase = 1;
//端口类型,不能为PORT_UNKNOWN
virt_port.type = PORT_8250;
//io访问方式
virt_port.iotype = UPIO_MEM;
//串口的中断号
virt_port.irq = 0;
//串口端口发送FIFO大小
virt_port.fifosize = 32;
//操作函数集合
virt_port.ops = &virt_uart_ops;
//RS485配置函数
virt_port.rs485_config = NULL;
//执行自动配置,但不探测UART类型
virt_port.flags = UPF_BOOT_AUTOCONF | UPF_FIXED_TYPE;
//在串口驱动下添加端口
result = uart_add_one_port(&virt_uart_drv, &virt_port);
if(result < 0)
{
printk("add uart port failed\n");
return result;
}
//创建proc文件,用于模拟串口硬件的发送和接收
proc_file = proc_create("virt_uart", 0, NULL, &proc_fops);
if (!proc_file)
{
uart_remove_one_port(&virt_uart_drv, &virt_port);
printk("create proc file failed\n");
return -ENOMEM;
}
return 0;
}
//设备或驱动卸载时执行
static int virtual_uart_remove(struct platform_device *pdev)
{
printk("%s\r\n", __FUNCTION__);
//删除proc文件
proc_remove(proc_file);
//移除端口
return uart_remove_one_port(&virt_uart_drv, &virt_port);
}
//匹配列表,用于设备树和平台驱动匹配
static const struct of_device_id virtual_uart_of_match[] = {
{.compatible = "atk,virtual_uart"},
{ /* Sentinel */ }
};
//平台驱动
static struct platform_driver virtual_uart_drv = {
.driver = {
.name = "virtual_uart",
.owner = THIS_MODULE,
.pm = NULL,
.of_match_table = virtual_uart_of_match,
},
.probe = virtual_uart_probe,
.remove = virtual_uart_remove,
};
static int __init virtual_uart_init(void)
{
int result;
printk("%s\r\n", __FUNCTION__);
//注册串口驱动
result = uart_register_driver(&virt_uart_drv);
if(result < 0)
{
printk("register uart driver failed\r\n");
return result;
}
//注册平台驱动
result = platform_driver_register(&virtual_uart_drv);
if(result < 0)
{
uart_unregister_driver(&virt_uart_drv);
printk("register platform driver failed\r\n");
return result;
}
return 0;
}
static void __exit virtual_uart_exit(void)
{
printk("%s\r\n", __FUNCTION__);
//注销平台驱动
platform_driver_unregister(&virtual_uart_drv);
//注销串口驱动
uart_unregister_driver(&virt_uart_drv);
}
module_init(virtual_uart_init);
module_exit(virtual_uart_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("csdn");
MODULE_DESCRIPTION("virtual_uart_dev");
串口测试程序
测试程序可以使用11.1Linux串口应用程序开发中编写的串口回环用于程序。
上机实验
- 修改设备树,在顶层设备树根节点中加入描述虚拟串口的设备节点,然后编译设备树,用新的设备树启动目标板
- 从这里下载测试程序,并进行编译,然后拷贝到目标板根文件系统的root目录
- 从这里下载驱动程序并进行编译,然后拷贝到目标板根文件系统的root目录
- 执行命令 insmod virtual_uart.ko 加载驱动程序
- 执行命令 ./uart_teat.out /dev/ttyVIRT0 运行测试程序
- 另开一个终端,在终端中执行命令 echo 123456 > /proc/virt_uart 模拟串口硬件接收数据,执行命令 cat /proc/virt_uart 模拟串口硬件发送数据,在执行命令过程中 ./uart_teat.out 程序也会打印它收到的字节数。
文章来源:https://blog.csdn.net/lf282481431/article/details/135423039
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!