一、tty串口驱动框架
应用程序通过某一个设备节点来访问驱动程序,设备节点都对应了某些驱动程序。
在这些驱动程序里面,肯定会有一个cdev与驱动对应。这个cdev里面肯定会有一个file_operations结构体。
我们可以从最低层开始,对于imx6ull是drivers/tty/serial/imx.c
文件。
- APP:应用程序open、write
- tty驱动层:tty_io.c文件设置了cdev -> file_operations
- 对应的tty端口:serial_core.c(串口)、vt.c(键盘),会向上注册一个tty_driver
- 具体芯片的串口:比如imx6ull相关的驱动程序分为两部分
- imx6ull串口控制器通用的代码,.tx命令,.rx命令。读状态写数据
- 硬件信息,比如UART0的寄存器地址、中断号;UART1的寄存器地址、中断号
与串口相关的代码driver/tty/serial/imx.c
串口的通用代码是用结构体uart_driver
开头的,串口的硬件相关的代码是根据设备树的信息创建一个uart_port
开头的。
uart_port相关的信息又分为platform_driver和设备设备树上的信息。
当platform_driver和设备树匹配后,就会在probe函数里构造一个uart_port,并向上注册。(把uart_port注册进uart_driver)
假设imx6ull有两个串口,那就会有两个uart_port来对应。
二、串口驱动分析
driver/tty/serial/imx.c文件里的初始化函数:
static int __init imx_uart_init(void)
{
int ret = uart_register_driver(&imx_uart_uart_driver); //注册一个uart_driver
//...
ret = platform_driver_register(&imx_uart_platform_driver); //注册一个platfrom_driver
//...
return ret;
}
//这里的uart_driver,比较简单。没有什么操作函数
//只定义了设备名、主次设备号,支持的多少个串口
static struct uart_driver imx_uart_uart_driver = {
.owner = THIS_MODULE,
.driver_name = DRIVER_NAME,
.dev_name = DEV_NAME,
.major = SERIAL_IMX_MAJOR,
.minor = MINOR_START,
.nr = ARRAY_SIZE(imx_uart_ports),
.cons = IMX_CONSOLE,
};
//注册的平台driver,用来处理设备树中的信息的
static struct platform_driver imx_uart_platform_driver = {
.probe = imx_uart_probe,
.remove = imx_uart_remove,
.id_table = imx_uart_devtype,
.driver = {
.name = "imx-uart",
.of_match_table = imx_uart_dt_ids,
.pm = &imx_uart_pm_ops,
},
};
继续查看probe函数
static int imx_uart_probe(struct platform_device *pdev)
{
//...
sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
//...
//解析设备树
ret = imx_uart_probe_dt(sport, pdev);
//.....
//获得设备树内的资源,获得寄存器地址
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
//获得接收中断、发送中断的中断号等等
rxirq = platform_get_irq(pdev, 0);
txirq = platform_get_irq(pdev, 1);
rtsirq = platform_get_irq(pdev, 2);
//这里的port类型就是struct uart_port,这里设置的uart_port的信息
sport->port.dev = &pdev->dev;
sport->port.mapbase = res->start;
sport->port.membase = base;
sport->port.type = PORT_IMX,
sport->port.iotype = UPIO_MEM;
sport->port.irq = rxirq;
sport->port.fifosize = 32;
sport->port.ops = &imx_uart_pops; //这个operation操作函数在uart_port里
sport->port.rs485_config = imx_uart_rs485_config;
sport->port.flags = UPF_BOOT_AUTOCONF;
timer_setup(&sport->timer, imx_uart_timeout, 0);
//...硬件相关的初始化
//向imx_uart_uart_driver注册uart_port结构体
return uart_add_one_port(&imx_uart_uart_driver, &sport->port);
}
static const struct uart_ops imx_uart_pops = {
.tx_empty = imx_uart_tx_empty,
.set_mctrl = imx_uart_set_mctrl,
.get_mctrl = imx_uart_get_mctrl,
.stop_tx = imx_uart_stop_tx, //停止发送
.start_tx = imx_uart_start_tx, //开始发送
.stop_rx = imx_uart_stop_rx,
.enable_ms = imx_uart_enable_ms,
.break_ctl = imx_uart_break_ctl,
.startup = imx_uart_startup, //启动串口
.shutdown = imx_uart_shutdown, //关闭窗口
.flush_buffer = imx_uart_flush_buffer, //清空缓存
.set_termios = imx_uart_set_termios,
.type = imx_uart_type,
.config_port = imx_uart_config_port,
.verify_port = imx_uart_verify_port,
#if defined(CONFIG_CONSOLE_POLL)
.poll_init = imx_uart_poll_init,
.poll_get_char = imx_uart_poll_get_char,
.poll_put_char = imx_uart_poll_put_char,
#endif
};
对于一个芯片,它有多个芯片,但是对于不同的串口,串口的读写函数可能不一样。
所以如果字节写驱动函数,主要是要编写imx_uart_platform_driver。
三、如何去注册driver
uart_driver的注册uart_register_driver
在drivers/tty/serial/serial_core.c
文件里
//drivers/tty/serial/serial_core.c
int uart_register_driver(struct uart_driver *drv)
{
struct tty_driver *normal; //使用uart_driever去构造tty_driver
int i, retval;
//...
drv->state = kcalloc(drv->nr, sizeof(struct uart_state), GFP_KERNEL);
//...
normal = alloc_tty_driver(drv->nr); //分配一个tty_driver
//...
drv->tty_driver = normal;
//设置uart_driver的信息,直接复制
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;
tty_set_operations(normal, &uart_ops); //给tty提供了uart的operations函数
//不同的tty设备,要提供不同的操作函数
/*
* Initialise the UART state(s).
*/
for (i = 0; i < drv->nr; i++) {
struct uart_state *state = drv->state + i;
struct tty_port *port = &state->port;
tty_port_init(port);
port->ops = &uart_port_ops;
}
//注册这个tty_driver
retval = tty_register_driver(normal);
//...
}
对于每一个uart_driver结构体,都有一个usart_state指针,用来指向每个串口的state,个数是针对uart_driver的nr参数决定的。
struct uart_driver {
struct module *owner;
const char *driver_name;
const char *dev_name;
int major;
int minor;
int nr;
struct console *cons;
/*
* these are private; the low level driver should not
* touch these; they should be initialised to NULL
*/
struct uart_state *state; //每个uart_driver都有一组state,根据nr来决定个数
struct tty_driver *tty_driver;
};
//每一个port对应一个uart_state
struct uart_state {
struct tty_port port;
enum uart_pm_state pm_state;
struct circ_buf xmit;
atomic_t refcount;
wait_queue_head_t remove_wait;
struct uart_port *uart_port;
};
这里开始分析alloc_tty_driver函数,是分配tty_driver的过程
//每一个串口的个数,对应一个line
static inline struct tty_driver *alloc_tty_driver(unsigned int lines)
{
struct tty_driver *ret = tty_alloc_driver(lines, 0);
if (IS_ERR(ret))
return NULL;
return ret;
}
#define tty_alloc_driver(lines, flags) \
__tty_alloc_driver(lines, THIS_MODULE, flags)
struct tty_driver *__tty_alloc_driver(unsigned int lines, struct module *owner,
unsigned long flags)
{
//...
//首先分配了一个tty_driver
driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL);
//...
kref_init(&driver->kref);
driver->magic = TTY_DRIVER_MAGIC;
driver->num = lines; //支持多少个串口
driver->owner = owner;
driver->flags = flags;
if (!(flags & TTY_DRIVER_DEVPTS_MEM)) {
driver->ttys = kcalloc(lines, sizeof(*driver->ttys),
GFP_KERNEL);
driver->termios = kcalloc(lines, sizeof(*driver->termios),
GFP_KERNEL); //为每个串口分配了ttys和termios
//...
}
if (!(flags & TTY_DRIVER_DYNAMIC_ALLOC)) {
driver->ports = kcalloc(lines, sizeof(*driver->ports),
GFP_KERNEL); //对于每一个串口,都会分配一个tty_port指针
//...
cdevs = lines; //cdevs等于串口的个数
}
driver->cdevs = kcalloc(cdevs, sizeof(*driver->cdevs), GFP_KERNEL);
//...
}
struct tty_driver {
int magic; /* magic number for this structure */
struct kref kref; /* Reference management */
struct cdev **cdevs; //为每一个串口分配cdev,在uart_add_one_port会去注册cdev
//...
/*
* Pointer to the tty data structures
*/
struct tty_struct **ttys; //为每一个串口分配了对应的指针数组
struct tty_port **ports; //这里的每一个tty_port一项对应上面的uart_state里的port指针
struct ktermios **termios;
void *driver_state;
/*
* Driver methods
*/
const struct tty_operations *ops;
struct list_head tty_drivers;
} __randomize_layout;
四、分析uart_add_one_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;
BUG_ON(in_interrupt());
if (uport->line >= drv->nr)
return -EINVAL;
state = drv->state + uport->line;
port = &state->port;
mutex_lock(&port_mutex);
mutex_lock(&port->mutex);
if (state->uart_port) {
ret = -EINVAL;
goto out;
}
/* Link the port to the driver state table and vice versa */
atomic_set(&state->refcount, 1);
init_waitqueue_head(&state->remove_wait);
state->uart_port = uport;
uport->state = state;
state->pm_state = UART_PM_STATE_UNDEFINED;
uport->cons = drv->cons;
uport->minor = drv->tty_driver->minor_start + uport->line;
uport->name = kasprintf(GFP_KERNEL, "%s%d", drv->dev_name,
drv->tty_driver->name_base + uport->line);
if (!uport->name) {
ret = -ENOMEM;
goto out;
}
/*
* If this port is a console, then the spinlock is already
* initialised.
*/
if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) {
spin_lock_init(&uport->lock);
lockdep_set_class(&uport->lock, &port_lock_key);
}
if (uport->cons && uport->dev)
of_console_check(uport->dev->of_node, uport->cons->name, uport->line);
uart_configure_port(drv, state, uport); //配置port
port->console = uart_console(uport);
num_groups = 2;
if (uport->attr_group)
num_groups++;
uport->tty_groups = kcalloc(num_groups, sizeof(*uport->tty_groups),
GFP_KERNEL);
if (!uport->tty_groups) {
ret = -ENOMEM;
goto out;
}
uport->tty_groups[0] = &tty_dev_attr_group;
if (uport->attr_group)
uport->tty_groups[1] = uport->attr_group;
/*
* Register the port whether it's detected or not. This allows
* setserial to be used to alter this port's parameters.
*/
tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver,
uport->line, uport->dev, port, uport->tty_groups); //这里注册port
if (likely(!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);
}
/*
* Ensure UPF_DEAD is not set.
*/
uport->flags &= ~UPF_DEAD;
out:
mutex_unlock(&port->mutex);
mutex_unlock(&port_mutex);
return ret;
}
struct device *tty_port_register_device_attr_serdev(struct tty_port *port,
struct tty_driver *driver, unsigned index,
struct device *device, void *drvdata,
const struct attribute_group **attr_grp)
{
struct device *dev;
tty_port_link_device(port, driver, index);
dev = serdev_tty_port_register(port, device, driver, index);
if (PTR_ERR(dev) != -ENODEV) {
/* Skip creating cdev if we registered a serdev device */
return dev;
}
return tty_register_device_attr(driver, index, device, drvdata,
attr_grp);
}
void tty_port_link_device(struct tty_port *port,
struct tty_driver *driver, unsigned index)
{
if (WARN_ON(index >= driver->num))
return;
driver->ports[index] = port; //让tty_port指针指向uart_driver里面的
}
struct device *tty_register_device_attr(struct tty_driver *driver,
unsigned index, struct device *device,
void *drvdata,
const struct attribute_group **attr_grp)
{
//...
if (driver->type == TTY_DRIVER_TYPE_PTY)
pty_line_name(driver, index, name);
else
tty_line_name(driver, index, name); //使用uart_driver.dev_name来构造/dev/xxx
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
//...
retval = tty_cdev_add(driver, devt, index, 1); //这里构造cdev
//...
}
static int tty_cdev_add(struct tty_driver *driver, dev_t dev,
unsigned int index, unsigned int count)
{
int err;
/* init here, since reused cdevs cause crashes */
driver->cdevs[index] = cdev_alloc(); //分配cdev
if (!driver->cdevs[index])
return -ENOMEM;
driver->cdevs[index]->ops = &tty_fops; //设置cdev->ops
driver->cdevs[index]->owner = driver->owner;
err = cdev_add(driver->cdevs[index], dev, count); //cdev_add注册
if (err)
kobject_put(&driver->cdevs[index]->kobj);
return err;
}
五、总结
在imx.c文件中,会注册一个uart_driver和一个platform_driver。
uart_driver只是包含了一些串口驱动的基本信息,包括驱动名称、主从设备号、支持多少个设备等。
而platform_driver就会根据设备树来获取必要的信息,然后去设置一个uart_port结构体。这个uart_port结构体会带一个操作的结构体,包含所有用来给串口进行各种操作的函数。最后通过uart_add_one_port把uart_driver和uart_port进行关联和注册。
在serial_core.c中,包含了uart_register_driver函数。通过分析这个函数,可以了解到uart_driver的注册过程。
它通过uart_driver来分配对应的tty_driver,通过alloc_tty_driver函数,给uart_driver支持的串口个数nr,每一个分配一个tty_driver的tty_struct、tty_port、ktermios、cdev结构体(指针数组)。并把uart_driver的信息复制给tty_driver。并且给tty驱动提供了uart_ops的操作函数集。(这个操作函数集和上面的uart_port的操作函数集是不一样的,上面的针对串口,这个是针对tty的),最后注册了tty_driver
因为提前注册了tty_driver,所以在注册platform_driver后,运行probe函数的uart_add_one_port时:
注册好的&imx_uart_uart_driver和设置好的imx_port相关联,把imx_port关联到tty_driver中(tty_port_link_device函数),给tty_driver注册cdev(tty_register_device_attr函数)。imx_port包含了uart_port,uart_port又包含了ops,就是针对硬件的操作函数。
通过这些关联,tty_driver就和uart_driver进行了绑定。