参考资料:
- 《LINUX设备驱动程序第三版》
- linux-5.4.9
0.前言
在今天终于离职了,办完了所有的手续,感觉一身轻松,在上一家公司,作为一名程序员,已经在偏离写代码开发的歪门邪道上越走越远了,现在疫情比较严重,非常时间,大家还是要少出门,注意安全,所以就趁这段时间总结一下之前工作中的问题和经验吧。
1.tty与串口驱动
tty设备的名称是从过去的电传打字机缩写而来,最初是指连接到Unix系统上的物理或者虚拟终端。随着时间的推移,当通过串行口能够建立起终端连接后,这个名字也用来指任何的串口设备。
有三种类型的tty'驱动程序:控制台、串口和pty。控制台和pty驱动程序已经被编写好饿了,而且可能也不必为这两类tty驱动程序编写其他的驱动程序。这使得任何使用tty核心与用户和系统交互的新驱动程序都可被看成是串口驱动程序。
2.关于tty
Linux tty驱动程序的核心紧挨着标准字符设备驱动层之下,并提供了一系列的功能,作为接口被终端类型设备使用。内核负责控制通过tty设备的数据流,并且格式化这些数据。这使得tty驱动程序把重点放在处理流向或者流出硬件的数据上,而不必重点考虑使用常规方法与用户空间的交互。为了控制数据流,有许多不同的线路规程(line discipline)可虚拟地“插入”任何的tty设备上,这由不同的tty线路规程驱动程序实现。
在这里插入图片描述tty核心从用户那里得到将被发往tty设备的数据,然后把数据发送给tty线路规程驱动程序,该驱动程序负责把数据传递给tty驱动程序。tty驱动程序对数据进行格式化,然后才能发送给硬件。从tty硬件那里接收到的数据将回溯到tty驱动程序,然后流入tty线路规程驱动程序,接着是tty核心,最后用户从tty核心哪里得到数据。有时tty驱动程序直接与tty核心通信,tty核心将数据直接发送给tty驱动程序,但通常是tty线路规程驱动程序修改在二者之间流动的数据。
3.添加Device
在arch/arm/boot/dts下的.dtsi文件下添加串口相关的device信息
在这里插入图片描述在imx.c下会通过MODULE_DEVICE_TABLE()去匹配一个device
在这里插入图片描述4.添加Driver
串口设备驱动核心结构体为uart_driver,在文件linux/drivers/serial/imx.c
static struct uart_driver imx_uart_uart_driver = {
.owner = THIS_MODULE,
.driver_name = DRIVER_NAME, //driver name
.dev_name = DEV_NAME, //device name
.major = SERIAL_IMX_MAJOR, //主设备号
.minor = MINOR_START, //次设备号
.nr = ARRAY_SIZE(imx_uart_ports),
.cons = IMX_CONSOLE,
};
串口驱动注册
static int __init imx_uart_init(void)
{
int ret = uart_register_driver(&imx_uart_uart_driver); //用uart核心层注册一个驱动程序
if (ret)
return ret;
ret = platform_driver_register(&imx_uart_platform_driver); //调用platform_driver_register向内核注册
if (ret != 0)
uart_unregister_driver(&imx_uart_uart_driver);
return ret;
}
uart_register_driver()函数,在drivers/tty/serial/serial_core.c中。
任何tty驱动程序的主要数据结构是结构tty_driver,被用来向tty核心注册和注销驱动程序
int uart_register_driver(struct uart_driver *drv)
{
struct tty_driver *normal;
int i, retval = -ENOMEM;
...
//每个端口对应一个state
drv->state = kzalloc(sizeof(struct uart_state)* drv->nr, GFP_KERNEL);
...
//分配该串口驱动对应的tty_drvier,tty_driver结构将根据tty驱动程序的需求,用正确的信息初始化
normal = alloc_tty_driver(drv->nr);
...
//让drv->tty_driver字段指向这个tty_driver
drv->tty_driver = normal;
...
//使用tty_set_operation函数拷贝在驱动程序定义的一系列操作函数
tty_set_operation(normal, &uart_ops);
...
//注册tty驱动程序
retval = tty_register_driver(normal);
}
为了向tty核心注册这个驱动程序,必须将tty_driver结构传递给tty_register_driver函数。
在注册自身后,驱动程序使用tty_register_device函数注册它所控制的设备,该函数有三个参数:
- 属于该设备的tty_driver结构指针
- 设备的次设备号
- 指向该tty设备所绑定的device结构指针,如果tty设备没有绑定任何device结构,该参数为NULL
int tty_register_driver(struct tty_driver *driver)
{
...
for (i = 0; i < driver->num; i++) {
d = tty_register_device(driver, i, NULL);
...
}
...
}
5.platform机制
在driver和device匹配成功之后,会调用probe函数
在这里插入图片描述static int imx_uart_probe(struct platform_device *pdev)
{
struct imx_port *sport;
void __iomem *base;
int ret = 0;
u32 ucr1;
struct resource *res;
int txirq, rxirq, rtsirq;
...
sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
...
//添加一个端口结构
return uart_add_one_port(&imx_uart_uart_driver, &sport->port);
}
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
struct uart_state *state;
struct tty_port *port;
int ret = 0;
struct device *tty_dev;
int num_groups;
...
//注册tty设备
tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver,
uport->line, uport->dev, port, uport->tty_groups);
if (!IS_ERR(tty_dev)) {
device_set_wakeup_capable(tty_dev, 1);
} else {
dev_err(uport->dev, "Cannot register tty device on line %d\n",
uport->line);
}
...
return ret;
}
6.串口数据流
在这里插入图片描述- open函数
static int tty_open(struct inode *inode, struct file *filp)
{
struct tty_struct *tty;
int noctty, retval;
dev_t device = inode->i_rdev; //表示设备文件的结点,这个域实际上包含了设备号
unsigned saved_flags = filp->f_flags;
...
if (tty->ops->open)
retval = tty->ops->open(tty, filp); //调用uart_open函数
else
retval = -ENODEV;
filp->f_flags = saved_flags;
...
return 0;
}
static int uart_open(struct tty_struct *tty, struct file *filp)
{
struct uart_state *state = tty->driver_data;
int retval;
retval = tty_port_open(&state->port, tty, filp);
if (retval > 0)
retval = 0;
return retval;
}
当用户使用open打开由驱动程序分配的设备节点时,tty核心将调用open函数。tty核心使用分配给该设备tty_struct结构的指针,以及一个文件描述符作为参数调用该函数。
- release函数
int tty_release(struct inode *inode, struct file *filp)
{
struct tty_struct *tty = file_tty(filp);
struct tty_struct *o_tty = NULL;
int do_sleep, final;
int idx;
long timeout = 0;
int once = 1;
...
tty_debug_hangup(tty, "releasing (count=%d)\n", tty->count);
if (tty->ops->close)
tty->ops->close(tty, filp); //调用uart_close函数
/* If tty is pty master, lock the slave pty (stable lock order) */
tty_lock_slave(o_tty);
...
tty_release_struct(tty, idx);
return 0;
}
static void uart_close(struct tty_struct *tty, struct file *filp)
{
struct uart_state *state = tty->driver_data;
if (!state) {
struct uart_driver *drv = tty->driver->driver_state;
struct tty_port *port;
state = drv->state + tty->index;
port = &state->port;
spin_lock_irq(&port->lock);
--port->count;
spin_unlock_irq(&port->lock);
return;
}
pr_debug("uart_close(%d) called\n", tty->index);
tty_port_close(tty->port, tty, filp);
}
当用户使用先前由open函数创建的文件句柄作为参数调用close时,tty核心调用close函数指针,此时该设备将被关闭。
- write函数
static ssize_t tty_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
struct tty_struct *tty = file_tty(file);
struct tty_ldisc *ld;
ssize_t ret;
if (tty_paranoia_check(tty, file_inode(file), "tty_write"))
return -EIO;
if (!tty || !tty->ops->write || tty_io_error(tty))
return -EIO;
/* Short term debug to catch buggy drivers */
if (tty->ops->write_room == NULL)
tty_err(tty, "missing write_room method\n");
ld = tty_ldisc_ref_wait(tty);
if (!ld)
return hung_up_tty_write(file, buf, count, ppos);
/*调用线路规程操作函数集中的n_tty_write()函数*/
if (!ld->ops->write)
ret = -EIO;
else
ret = do_tty_write(ld->ops->write, tty, file, buf, count);
tty_ldisc_deref(ld);
return ret;
}
static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,
const unsigned char *buf, size_t nr)
{
const unsigned char *b = buf;
DEFINE_WAIT_FUNC(wait, woken_wake_function);
int c;
ssize_t retval = 0;
/* Job control check -- must be done at start (POSIX.1 7.1.1.4). */
if (L_TOSTOP(tty) && file->f_op->write != redirected_tty_write) {
retval = tty_check_change(tty);
if (retval)
return retval;
}
down_read(&tty->termios_rwsem);
/* Write out any echoed characters that are still pending */
process_echoes(tty);
add_wait_queue(&tty->write_wait, &wait);
while (1) {
...
/*调用uart_flush_chars向硬件发送数据*/
if (tty->ops->flush_chars)
tty->ops->flush_chars(tty);
} else {
struct n_tty_data *ldata = tty->disc_data;
while (nr > 0) {
mutex_lock(&ldata->output_lock);
c = tty->ops->write(tty, b, nr); //或者调用uart_write函数
mutex_unlock(&ldata->output_lock);
if (c < 0) {
retval = c;
goto break_out;
}
if (!c)
break;
b += c;
nr -= c;
}
}
...
}
break_out:
remove_wait_queue(&tty->write_wait, &wait);
if (nr && tty->fasync)
set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
up_read(&tty->termios_rwsem);
return (b - buf) ? b - buf : retval;
}
static void uart_flush_chars(struct tty_struct *tty)
{
uart_start(tty);
}
static int uart_write(struct tty_struct *tty,
const unsigned char *buf, int count)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port;
struct circ_buf *circ;
unsigned long flags;
int c, ret = 0;
/*
* This means you called this function _after_ the port was
* closed. No cookie for you.
*/
if (!state) {
WARN_ON(1);
return -EL3HLT;
}
port = uart_port_lock(state, flags);
circ = &state->xmit;
if (!circ->buf) {
uart_port_unlock(port, flags);
return 0;
}
while (port) {
c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE);
if (count < c)
c = count;
if (c <= 0)
break;
memcpy(circ->buf + circ->head, buf, c);
circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1);
buf += c;
count -= c;
ret += c;
}
__uart_start(tty);
uart_port_unlock(port, flags);
return ret;
}
static void uart_start(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port;
unsigned long flags;
port = uart_port_lock(state, flags);
__uart_start(tty);
uart_port_unlock(port, flags);
}
static void __uart_start(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port = state->uart_port;
if (port && !uart_tx_stopped(port))
port->ops->start_tx(port); //调用imx.c中的imx_uart_start_tx函数发送数据
}
当数据要被发送给硬件时,用户调用write函数,首先tty核心接收到了该调用,然后内核将数据发送给tty驱动程序的write函数。tty核心同时也告诉tty驱动程序发功数据的大小。
- read函数
static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
loff_t *ppos)
{
int i;
struct inode *inode = file_inode(file);
struct tty_struct *tty = file_tty(file);
struct tty_ldisc *ld;
if (tty_paranoia_check(tty, inode, "tty_read"))
return -EIO;
if (!tty || tty_io_error(tty))
return -EIO;
/* We want to wait for the line discipline to sort out in this
situation */
ld = tty_ldisc_ref_wait(tty);
if (!ld)
return hung_up_tty_read(file, buf, count, ppos);
/*调用线路规程操作函数集中的n_tty_read()函数*/
if (ld->ops->read)
i = ld->ops->read(tty, file, buf, count);
else
i = -EIO;
tty_ldisc_deref(ld);
if (i > 0)
tty_update_time(&inode->i_atime);
return i;
}
static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,
unsigned char __user *buf, size_t nr)
{
struct n_tty_data *ldata = tty->disc_data;
unsigned char __user *b = buf;
DEFINE_WAIT_FUNC(wait, woken_wake_function);
int c;
int minimum, time;
ssize_t retval = 0;
long timeout;
int packet;
size_t tail;
...
while (nr) {
...
/*如果配置了icanon,那么就按canonical模式拷贝读取的数据*/
if (ldata->icanon && !L_EXTPROC(tty)) {
retval = canon_copy_from_read_buf(tty, &b, &nr);
if (retval)
break;
} else {
int uncopied;
/* Deal with packet mode. */
if (packet && b == buf) {
if (put_user(TIOCPKT_DATA, b)) {
retval = -EFAULT;
break;
}
b++;
nr--;
}
uncopied = copy_from_read_buf(tty, &b, &nr);
uncopied += copy_from_read_buf(tty, &b, &nr);
if (uncopied) {
retval = -EFAULT;
break;
}
}
n_tty_check_unthrottle(tty);
if (b - buf >= minimum)
break;
if (time)
timeout = time;
}
...
return retval;
}
- 如果配置了串口中断,串口在接收到数据后,触发中断
static irqreturn_t imx_uart_rxint(int irq, void *dev_id)
{
struct imx_port *sport = dev_id;
unsigned int rx, flg, ignored = 0;
struct tty_port *port = &sport->port.state->port;
spin_lock(&sport->port.lock);
while (imx_uart_readl(sport, USR2) & USR2_RDR) {
...
if (uart_handle_sysrq_char(&sport->port, (unsigned char)rx))
continue;
if (unlikely(rx & URXD_ERR)) {
...
/*读取寄存器的数据*/
rx &= (sport->port.read_status_mask | 0xFF);
if (rx & URXD_BRK)
flg = TTY_BREAK;
else if (rx & URXD_PRERR)
flg = TTY_PARITY;
else if (rx & URXD_FRMERR)
flg = TTY_FRAME;
if (rx & URXD_OVRRUN)
flg = TTY_OVERRUN;
#ifdef SUPPORT_SYSRQ
sport->port.sysrq = 0;
#endif
}
if (sport->port.ignore_status_mask & URXD_DUMMY_READ)
goto out;
if (tty_insert_flip_char(port, rx, flg) == 0)
sport->port.icount.buf_overrun++;
}
out:
spin_unlock(&sport->port.lock);
tty_flip_buffer_push(port);
return IRQ_HANDLED;
}
调用tty_insert_flip_char将把tty驱动程序获得的、准备发给用户的字符添加到交替缓冲区中。第一个参数是保存数据的tty_struct结构,第二个参数是需要保存的数据,第三个参数是为此字符设置的标志位。如果接收到的字符是常规字符,标志位应该被设置为TTY_NORMAL。
- 如果配置了DMA模式,从DMA拷贝数据
static int imx_uart_start_rx_dma(struct imx_port *sport)
{
struct scatterlist *sgl = &sport->rx_sgl;
struct dma_chan *chan = sport->dma_chan_rx;
struct device *dev = sport->port.dev;
struct dma_async_tx_descriptor *desc;
int ret;
sport->rx_ring.head = 0;
sport->rx_ring.tail = 0;
sport->rx_periods = RX_DMA_PERIODS;
sg_init_one(sgl, sport->rx_buf, RX_BUF_SIZE);
...
desc->callback = imx_uart_dma_rx_callback;
desc->callback_param = sport;
dev_dbg(dev, "RX: prepare for the DMA.\n");
sport->dma_is_rxing = 1;
sport->rx_cookie = dmaengine_submit(desc);
dma_async_issue_pending(chan);
return 0;
}
tty核心和tty_driver结构并未提供read函数,当tty驱动程序接收到数据后,它将负责把从硬件获取到的任何数据传递给tty核心,而不是使用传统的read函数。tty核心将缓冲数据直到接到来自用户的请求。由于tty核心已提供了缓冲逻辑,因此没有必要为每个tty驱动程序实现它们自己的缓冲区逻辑。当用户要求驱动程序开始或者停止传输数据时,tty核心将通知tty驱动程序。
7.DMA和中断的配置
static int imx_uart_startup(struct uart_port *port)
{
struct imx_port *sport = (struct imx_port *)port;
int retval, i;
unsigned long flags;
int dma_is_inited = 0;
u32 ucr1, ucr2, ucr4;
...
/*如果dma_is_inited为真,则配置DMA模式,否则使能串口中断*/
if (dma_is_inited) {
imx_uart_enable_dma(sport);
imx_uart_start_rx_dma(sport);
} else {
ucr1 = imx_uart_readl(sport, UCR1);
ucr1 |= UCR1_RRDYEN;
imx_uart_writel(sport, ucr1, UCR1);
ucr2 = imx_uart_readl(sport, UCR2);
ucr2 |= UCR2_ATEN;
imx_uart_writel(sport, ucr2, UCR2);
}
spin_unlock_irqrestore(&sport->port.lock, flags);
return 0;
}
网友评论