tty驱动初步学习
一、linux tty驱动框架
本人是linux驱动初学者,最近在初步学习uart驱动,在这记录下来自己的理解
linux3.10
soc:君正x1000e
四位大佬写的很好
https://blog.csdn.net/cosmoslhf/article/details/16945009
https://blog.csdn.net/lizuobin2/article/details/51801183
https://blog.csdn.net/Luckiers/article/details/123577836
https://blog.csdn.net/mike8825/article/details/107598833
在Linux kernel中,tty驱动不像于spi,iic等那么架构简单,它是一个庞大的系统,它的框架大体如下图一。我们作为普通的驱动开发移植人员,不会从零写tty驱动,一般都是厂家根据现有的tty驱动和自家芯片修改,拿到板子按照厂家的配置,串口应该使能直接使用的。但是开发的过程中也有可能需要用到串口,一般会修改serial驱动,这样我们不会动tty_core层。
Linux tty子系统包含:tty核心(tty_core),tty线路规程(tty_line_discipine)和tty驱动(tty_driver)。tty核心是对整个tty设备的抽象,对用户提供统一的接口,tty线路规程是对传输数据的格式化,tty驱动则是面向tty设备的硬件驱动。
图一
二、uart驱动数据架构
1、主要用到的数据架构图二
图二
uart_driver描述uart设备,其他下面有2个重要的结构体uart_state(用于描述底层硬件信息),tty_drvier(tty层驱动)。
uart_driver结构体
struct uart_driver {
struct module *owner;
const char *driver_name;//串口驱动名字
const char *dev_name;//设备名字
int major;
int minor;
int nr;
struct console *cons;
/*
有两个成员未被赋值,对于tty_driver 代表的是上层,它会在 register_uart_driver 中的过程中赋值,
而uart_state 则代表下层,uart_state 也会在register_uart_driver 的过程中分配空间,
但是它里面真正设置硬件相关的东西是 uart_state->uart_port ,
这个uart_port 是需要我们从其它地方调用 uart_add_one_port 来添加的。
*/
struct uart_state *state;//底层指定硬件参数
struct tty_driver *tty_driver;//tty层
};
一般来说一个芯片具有多个串口,比如君正x1000e具有3个串口,那uart_state应该有多个,每个保存一个串口信息。
tty_prot是TTY device的一种抽象
circ_buf是一个发送缓存,在写数据时,当tty层调用驱动提供的写函数时,数据会首先进入circ_buf的环形缓存区,然后由uart_port从缓存区中取数据,将其写入到串口设备中。
当uart_port从串口设备接收到数据时,它会直接将数据放入tty的缓存中(tty缓存属于tty_port),进而放入对应的line discipline的缓存区中。
uart_state结构体
uart_state中最重要的结构体成员uart_port
uart_state->uart_port(在这保存硬件串口信息)
truct uart_port {
spinlock_t lock; /* port lock */
unsigned long iobase; /* io基地址 */
unsigned char __iomem *membase; /*内存基地址 */
unsigned int (*serial_in)(struct uart_port *, int);//串口接受函数指针
void (*serial_out)(struct uart_port *, int, int);//串口发送函数指针
void (*set_termios)(struct uart_port *,
struct ktermios *new,
struct ktermios *old);
int (*handle_irq)(struct uart_port *);
void (*pm)(struct uart_port *, unsigned int state,
unsigned int old);
void (*handle_break)(struct uart_port *);
unsigned int irq; /* irq number */
unsigned long irqflags; /* irq flags */
unsigned int uartclk; /* base uart clock */
unsigned int fifosize; /* tx fifo size */
unsigned char x_char; /* xon/xoff char */
unsigned char regshift; /* reg offset shift */
unsigned char iotype; /* io访问方式 */
unsigned char unused1;
....
unsigned int mctrl; /* 当前控制模式*/
unsigned int timeout; /* character-based timeout */
unsigned int type; /* port type */
const struct uart_ops *ops;
unsigned int custom_divisor;
unsigned int line; /* port index */
resource_size_t mapbase; /* for ioremap */
struct device *dev; /* parent device */
unsigned char hub6; /* this should be in the 8250 driver */
unsigned char suspended;
unsigned char irq_wake;
unsigned char unused[2];
void *private_data; /* generic platform data pointer */
};
tty_port结构体
保存着tty层驱动信息
struct tty_driver {
int magic; /* magic number for this structure */
struct kref kref; /* Reference management */
struct cdev *cdevs;
struct module *owner;
const char *driver_name;
const char *name;
int name_base; /* offset of printed name */
int major; /* major device number */
int minor_start; /* start of minor device number */
unsigned int num; /* number of devices allocated */
short type; /* type of tty driver */
short subtype; /* subtype of tty driver */
struct ktermios init_termios; /* Initial termios */
unsigned long flags; /* tty driver flags */
struct proc_dir_entry *proc_entry; /* /proc fs entry */
struct tty_driver *other; /* only used for the PTY driver */
/*
* Pointer to the tty data structures
*/
struct tty_struct **ttys;
struct tty_port **ports;
struct ktermios **termios;
void *driver_state;
/*
* Driver methods
*/
const struct tty_operations *ops;
struct list_head tty_drivers;
};
三、uart驱动编写步骤
uart它是linux在tty的基础上又做了一层封装,得益于该封装层,可以比较容易的编写新的串口驱动程序。
只需要2大步
(1)uart_register_driver函数注册一个串口设备
(2)platform_driver_register注册一个平台设备(最后执行uart_add_one_port)
驱动大概框架
static int serial_jz47xx_startup(struct uart_port *port)
{
/*request_irq注册驱动*/
retval = request_irq(up->port.irq, serial_jz47xx_irq, 0, up->name, up);//request irq
if (retval)
return retval;
/*清理寄存器*/
/*初始化串口*/
/*使能中断*/
}
/*操作集合*/
struct uart_ops serial_jz47xx_pops = {
/*回调函数*/
.startup = serial_jz47xx_startup,//start endport
....
};
static struct uart_driver serial_jz47xx_reg = {
.owner = THIS_MODULE,
.driver_name = "JZ47XX serial", //串口驱动名字
.dev_name = "ttyS", //串口设备名字
.major = TTY_MAJOR, //主设备号
.minor = 64,
.nr = PORT_NR,/* 该uart_driver支持的串口个数(最大) */
.cons = JZ47XX_CONSOLE, /* 其对应的console.若该uart_driver支持serial console,否则为NULL */
};
static int serial_jz47xx_probe(struct platform_device *pdev)
{
/*获取平台资源*/
/*申请内存空间*/
/*开启时钟*/
/*初始化port结构体(和硬件有关的)*/
/*开启DMA*/
/*初始化栈*/
/*uart_add_one_port*/
}
static struct platform_driver serial_jz47xx_driver = {
.probe = serial_jz47xx_probe,
.remove = serial_jz47xx_remove,
.driver = {
.name = "jz-uart",
.owner = THIS_MODULE,
},
};
/*注册uart设备和平台设备*/
int __init serial_jz47xx_init(void)
{
int ret;
ret = uart_register_driver(&serial_jz47xx_reg);
ret = platform_driver_register(&serial_jz47xx_driver)
return ret;
}
void __exit serial_jz47xx_exit(void)
{
platform_driver_unregister(&serial_jz47xx_driver);
uart_unregister_driver(&serial_jz47xx_reg);
}
module_init(serial_jz47xx_init);
module_exit(serial_jz47xx_exit);
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:jz47xx-uart");
驱动分析
uart设备依托在platform平台设备上实现,主要在platform设备的probe函数中初始化uart_prot结构体
- 调用uart_register_driver注册一个uart驱动
uart_register_driver(&serial_jz47xx_reg);
- platform_driver_register平台设备注册
platform_driver_register(&serial_jz47xx_driver);
- 在platform设备的probe函数中主要做了4件事
获取平台资源
mmres = platform_get_resource(pdev, IORESOURCE_MEM, 0);//获取io资源
irqres = platform_get_resource(pdev, IORESOURCE_IRQ, 0);//获取中断资源
dma_filter = platform_get_resource(pdev, IORESOURCE_DMA, 0);//获取DMA资源
初始化uart_port结构体
up->port.type = PORT_JZ47XX;
up->port.iotype = UPIO_MEM;
up->port.mapbase = mmres->start;
up->port.irq = irqres->start;
up->port.fifosize = 64;
up->port.ops = &serial_jz47xx_pops;
up->port.line = pdev->id;
up->port.dev = &pdev->dev;
up->port.flags = UPF_IOREMAP | UPF_BOOT_AUTOCONF;
up->port.uartclk = clk_get_rate(up->clk);
up->port.membase = ioremap(mmres->start, mmres->end - mmres->start 1);
if (!up->port.membase) {
ret = -ENOMEM;
goto err_clk;
}
开时钟和DMA
clk_enable(up->clk);//开启时钟
serial_jz47xx_dma_init(up);//dma初始化
最后uart_add_one_port
uart_add_one_port(&serial_jz47xx_reg, &up->port);//根据uart_port的信息配置串口,并将其注册到系统中
再看ops操作集合
struct uart_ops serial_jz47xx_pops = {
.tx_empty = serial_jz47xx_tx_empty,//TX Buffer empty
.set_mctrl = serial_jz47xx_set_mctrl,//set Modem control
.get_mctrl = serial_jz47xx_get_mctrl,//get Modem control
.stop_tx = serial_jz47xx_stop_tx,//stop TX
.start_tx = serial_jz47xx_start_tx,//start tx
.stop_rx = serial_jz47xx_stop_rx,//stop rx
.enable_ms = serial_jz47xx_enable_ms,//modem status enable
.break_ctl = serial_jz47xx_break_ctl,//
.startup = serial_jz47xx_startup,//start endport
.shutdown = serial_jz47xx_shutdown,//shutdown endport
.set_termios = serial_jz47xx_set_termios,//change para of endport
.type = serial_jz47xx_type,//
.release_port = serial_jz47xx_release_port,//release port I/O
.request_port = serial_jz47xx_request_port,//
.config_port = serial_jz47xx_config_port,//
.verify_port = serial_jz47xx_verify_port,//
};
这些回调函数一般是厂家写的???
看startup回调函数(主要是申请中断)
static int serial_jz47xx_startup(struct uart_port *port)
{
struct uart_jz47xx_port *up = (struct uart_jz47xx_port *)port;
unsigned long flags;
int retval;
up->port.uartclk = clk_get_rate(up->clk);//get clk
//1、先判断当前dma是否在用,如果在用就添加到软中断等待处理
if(up->use_dma)
tasklet_schedule(&up->tasklet_dma_rx);
/*
* Allocate the IRQ
*/
//2、申请中断,串口接收和发送都是以中断为入口的吧?
retval = request_irq(up->port.irq, serial_jz47xx_irq, 0, up->name, up);//request irq
if (retval)
return retval;
/*
* Clear the FIFO buffers and disable them.
* (they will be reenabled in set_termios())
*/
//3、清理FIFO
serial_out(up, UART_FCR, UART_FCR_ENABLE_FIFO);
serial_out(up, UART_FCR, UART_FCR_ENABLE_FIFO
| UART_FCR_CLEAR_RCVR
| UART_FCR_CLEAR_XMIT
| UART_FCR_UME);
serial_out(up, UART_FCR, 0);
/*
* Clear the interrupt registers.
*/
//4、清理寄存器
(void) serial_in(up, UART_LSR);
(void) serial_in(up, UART_RX);
(void) serial_in(up, UART_IIR);
(void) serial_in(up, UART_MSR);
/*
* Now, initialize the UART
*/
//5、初始化串口
serial_out(up, UART_LCR, UART_LCR_WLEN8);
serial_out(up, UART_ISR, 0);
spin_lock_irqsave(&up->port.lock, flags);
up->port.mctrl |= TIOCM_OUT2;
//6、设置模式?
serial_jz47xx_set_mctrl(&up->port, up->port.mctrl);
spin_unlock_irqrestore(&up->port.lock, flags);
/*
* Finally, enable interrupts. Note: Modem status interrupts
* are set via set_termos(), which will be occurring imminently
* anyway, so we don't enable them here.
*/
//7、开DMA,开中断
if(up->use_dma)
up->ier = UART_IER_RLSI | UART_IER_RTOIE;
else
up->ier = UART_IER_RLSI | UART_IER_RDI | UART_IER_RTOIE;
serial_out(up, UART_IER, up->ier);
/*
* And clear the interrupt registers again for luck.
*/
(void) serial_in(up, UART_LSR);
(void) serial_in(up, UART_RX);
(void) serial_in(up, UART_IIR);
(void) serial_in(up, UART_MSR);
return 0;
}
1、先判断当前dma是否在用,如果在用就添加到软中断等待处理
2、申请中断,串口接收和发送都是以中断为入口的吧?
3、清理FIFO
4、清理寄存器
5、初始化串口
6、设置模式?
7、开DMA,开中断
进一步了解uart_register_driver和uart_add_one_port的实现过程
uart_register_driver
int uart_register_driver(struct uart_driver *drv)
{
struct tty_driver *normal;
int i, retval;
BUG_ON(drv->state);
/*
* Maybe we should be using a slab cache for this, especially if
* we have a large number of ports to handle.
*/
//1、为多个串口分配缓存内存空间,drv->nr是串口数量
drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
if (!drv->state)
goto out;
//2、申请tty设备
normal = alloc_tty_driver(drv->nr);
if (!normal)
goto out_kfree;
drv->tty_driver = normal;
//3、tty设备初始化
normal->driver_name = drv->driver_name;
normal->name = drv->dev_name;
normal->major = drv->major;
normal->minor_start = drv->minor;
normal->type = TTY_DRIVER_TYPE_SERIAL;
normal->subtype = SERIAL_TYPE_NORMAL;
normal->init_termios = tty_std_termios;
normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
normal->driver_state = drv;
//4、注册tty函数操作集合
tty_set_operations(normal, &uart_ops);
/*
* //5、初始化所有UART和TTY
*/
for (i = 0; i < drv->nr; i ) {
struct uart_state *state = drv->state i;
struct tty_port *port = &state->port;
//6、tty_port保存着各个tty_drvice的共性属性,tty_port_init初始化缓冲区,互彻锁
tty_port_init(port);
port->ops = &uart_port_ops;
port->close_delay = HZ / 2; /* .5 seconds */
port->closing_wait = 30 * HZ;/* 30 seconds */
}
//7、注册tty设备
retval = tty_register_driver(normal);
if (retval >= 0)
return retval;
//如果到了这步,那么说明上面tty_register_driver失败,那么需要释放资源
for (i = 0; i < drv->nr; i )
tty_port_destroy(&drv->state[i].port);
put_tty_driver(normal);
out_kfree:
kfree(drv->state);
out:
return -ENOMEM;
}
tty层的操作集
static const struct tty_operations uart_ops = {
.open = uart_open,
.close = uart_close,
.write = uart_write,
.put_char = uart_put_char, // 单字节写函数
.flush_chars = uart_flush_chars, // 刷新数据到硬件函数
.write_room = uart_write_room, // 指示多少缓冲空闲的函数
.chars_in_buffer= uart_chars_in_buffer, // 只是多少缓冲满的函数
.flush_buffer = uart_flush_buffer, // 刷新数据到硬件
.ioctl = uart_ioctl,
.throttle = uart_throttle,
.unthrottle = uart_unthrottle,
.send_xchar = uart_send_xchar,
.set_termios = uart_set_termios, // 当termios设置被改变时又tty核心调用
.set_ldisc = uart_set_ldisc, // 设置线路规程函数
.stop = uart_stop,
.start = uart_start,
.hangup = uart_hangup, // 挂起函数,当驱动挂起tty设备时调用
.break_ctl = uart_break_ctl, // 线路中断控制函数
.wait_until_sent= uart_wait_until_sent,
#ifdef CONFIG_PROC_FS
.proc_fops = &uart_proc_fops,
#endif
.tiocmget = uart_tiocmget, // 获得当前tty的线路规程的设置
.tiocmset = uart_tiocmset, // 设置当前tty线路规程的设置
#ifdef CONFIG_CONSOLE_POLL
.poll_init = uart_poll_init,
.poll_get_char = uart_poll_get_char,
.poll_put_char = uart_poll_put_char,
#endif
};
tty_register_driver驱动注册流程(也就是字符设备注册)
int tty_register_driver(struct tty_driver *driver)
{
int error;
int i;
dev_t dev;
void **p = NULL;
if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {
p = kzalloc(driver->num * 2 * sizeof(void *), GFP_KERNEL);
}
/* 如果没有主设备号则申请 */
if (!driver->major) {
error = alloc_chrdev_region(&dev, driver->minor_start,
driver->num, driver->name);
} else {
dev = MKDEV(driver->major, driver->minor_start);
error = register_chrdev_region(dev, driver->num, driver->name);
}
if (p) { /* 为线路规程和termios分配空间 */
driver->ttys = (struct tty_struct **)p;
driver->termios = (struct ktermios **)(p driver->num);
} else {
driver->ttys = NULL;
driver->termios = NULL;
}
/* 创建字符设备,使用 tty_fops */
cdev_init(&driver->cdev, &tty_fops);
driver->cdev.owner = driver->owner;
error = cdev_add(&driver->cdev, dev, driver->num);
mutex_lock(&tty_mutex);
/* 将该 driver->tty_drivers 添加到全局链表 tty_drivers */
list_add(&driver->tty_drivers, &tty_drivers);
mutex_unlock(&tty_mutex);
if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
for (i = 0; i < driver->num; i )
tty_register_device(driver, i, NULL);
}
/* 向proc文件系统注册driver */
proc_tty_register_driver(driver);
driver->flags |= TTY_DRIVER_INSTALLED;
return 0;
}
四、读写操作调用流程
static const struct file_operations tty_fops = {
.open = tty_open,
.read = tty_read,
.write = tty_write,
};
static struct tty_ldisc_ops n_tty_ops = {
.open = n_tty_open,
.read = n_tty_read,
.write = n_tty_write,
};
static const struct tty_operations uart_ops = {
.open = uart_open,
.write = uart_write,
};
static const struct uart_ops serial_sprd_ops = {
.stop_tx = sprd_stop_tx,
.start_tx = sprd_start_tx,
.stop_rx = sprd_stop_rx,
.set_termios = sprd_set_termios,
};
//open
static int tty_open(struct inode *inode, struct file *filp)
{
struct tty_struct *tty;
tty = tty_open_by_driver(device, inode, filp);
retval = tty->ops->open(tty, filp);----->
}
//寻找driver,比如有usb转串口driver,cpu的uart driver
static struct tty_struct *tty_open_by_driver(dev_t device, struct inode *inode,struct file *filp)
{
driver = tty_lookup_driver(device, filp, &index);
-->tty = tty_init_dev(driver, index);
tty = alloc_tty_struct(driver, idx);---->tty_ldisc_init(tty);
tty->ops = driver->ops;
retval = tty_driver_install_tty(driver, tty);
retval = tty_ldisc_setup(tty, tty->link);
}
static struct uart_driver sprd_uart_driver = {
.owner = THIS_MODULE,
.driver_name = "sprd_serial",
.dev_name = SPRD_TTY_NAME,
.major = 0,
.minor = 0,
.nr = UART_NR_MAX,
.cons = SPRD_CONSOLE,
};
static int uart_open(struct tty_struct *tty, struct file *filp)
{
retval = uart_startup(tty, state, 0);--->retval = uport->ops->startup(uport);
uart_change_speed(tty, state, NULL);--->uport->ops->set_termios(uport, termios, old_termios);
}
static const struct tty_operations uart_ops = {
.open = uart_open,
.write = uart_write,
};
int uart_register_driver(struct uart_driver *drv)
{
tty_set_operations(normal, &uart_ops);--->driver->ops = op;
}
串口发送过程
static ssize_t tty_write(struct file *file, const char __user *buf,size_t count, loff_t *ppos)
{
ret = do_tty_write(ld->ops->write, tty, file, buf, count);
}
static inline ssize_t do_tty_write(ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t),struct tty_struct *tty,struct file *file,const char __user *buf,size_t count)
{
ret = write(tty, file, tty->write_buf, size);--->n_tty_write
}
static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,const unsigned char *buf, size_t nr)
{
while (nr > 0) {
c = tty->ops->write(tty, b, nr);
}
}
static int uart_write(struct tty_struct *tty,const unsigned char *buf, int count)
{
while (1) {
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);
}
static void __uart_start(struct tty_struct *tty)
{
port->ops->start_tx(port);
}
static void sprd_start_tx(struct uart_port *port)
{
unsigned int ien;
ien = serial_in(port, SPRD_IEN);
if (!(ien & SPRD_IEN_TX_EMPTY)) {
ien |= SPRD_IEN_TX_EMPTY;
serial_out(port, SPRD_IEN, ien);
}
}
//准备好了,把数据发过去
static irqreturn_t sprd_handle_irq(int irq, void *dev_id)
{
if (ims & SPRD_IMSR_TX_FIFO_EMPTY)
sprd_tx(port);
}
static inline void sprd_tx(struct uart_port *port)
{
do {
serial_out(port, SPRD_TXD, xmit->buf[xmit->tail]);
xmit->tail = (xmit->tail 1) & (UART_XMIT_SIZE - 1);
port->icount.tx ;
if (uart_circ_empty(xmit))
break;
} while (--count > 0);
}
串口接收过程
//中断来了,开始读数据,并存储
static irqreturn_t sprd_handle_irq(int irq, void *dev_id){
ims = serial_in(port, SPRD_IMSR);
if (ims & (SPRD_IMSR_RX_FIFO_FULL |
SPRD_IMSR_BREAK_DETECT | SPRD_IMSR_TIMEOUT))
sprd_rx(port);
}
static inline void sprd_rx(struct uart_port *port)
{
struct tty_port *tty = &port->state->port;
unsigned int ch, flag, lsr, max_count = SPRD_TIMEOUT;
while ((serial_in(port, SPRD_STS1) & 0x00ff) && max_count--) {
lsr = serial_in(port, SPRD_LSR);
ch = serial_in(port, SPRD_RXD);//把数据一个接一个读处理,然后放缓冲区里
uart_insert_char(port, lsr, SPRD_LSR_OE, ch, flag);--->uart_insert_char--->
}
tty_flip_buffer_push(tty);
}
static ssize_t tty_read(struct file *file, char __user *buf, size_t count,loff_t *ppos)
{
i = ld->ops->read(tty, file, buf, count);--->n_tty_read
}
static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,unsigned char __user *buf, size_t nr)
{
uncopied = copy_from_read_buf(tty, &b, &nr);
}
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhgjakea
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01 -
怎样阻止微信小程序自动打开
PHP中文网 06-13