分类: LINUX
2010-06-06 07:54:25
浅析arm平台上uart在kernel上注册和使用的上、下流程
可以参考:
【浅析2.6.24下char字符驱动tty注册和实际调用流程】
static struct uart_driver serial_pxa_reg = {
【浅析printk的emit_log_char()算法具体实现】
.owner = THIS_MODULE,
.driver_name = "PXA serial",
.dev_name = "ttyS",
.major = TTY_MAJOR,
.minor = 64,//次设备号从64开始
.nr = 4,//本驱动控制4个串口物理设备,对应的次设备号依次为64,65,66,67
.cons = PXA_CONSOLE,
};
drivers\serial\pxa.c
serial_pxa_init=>uart_register_driver(&serial_pxa_reg)=>
int uart_register_driver(struct uart_driver *drv)
{
...
normal = alloc_tty_driver(drv->nr);//本驱动一共可以控制nr个物理设备
drv->tty_driver = normal;
...
normal->owner = drv->owner;
normal->driver_name = drv->driver_name;
normal->name = drv->dev_name;
normal->major = drv->major;
//该驱动将专门控制/dev下以major和minor_start+drv->nr为节点的字符串口物理设备
normal->minor_start = drv->minor;
...
normal->driver_state = drv;
tty_set_operations(normal, &uart_ops);
...
retval = tty_register_driver(normal);
//会将tty_fops作为file操作函数集,使用cdev_add登记到cdev_map上
...
}int tty_register_driver(struct tty_driver *driver)
{
...
if (!driver->major) {
error = alloc_chrdev_region(&dev, driver->minor_start, driver->num,
driver->name);
if (!error) {
//因为major为0,所以自动获取major
driver->major = MAJOR(dev);
driver->minor_start = MINOR(dev);
}
} else {
//我们的ttyS0-ttyS3物理串口设备在/dev目录下创建节点时对应的主、次设备号,这样才可以绑定到
//我们这个驱动的操作函数集tty_fops
dev = MKDEV(driver->major, driver->minor_start);
error = register_chrdev_region(dev, driver->num, driver->name);
}
...
//程序调用ttyS0打开串口是,获得的文件操作集为:
cdev_init(&driver->cdev, &tty_fops);
driver->cdev.owner = driver->owner;
error = cdev_add(&driver->cdev, dev, driver->num);
...
//将该driver链接到tty_drivers驱动链表中去
list_add(&driver->tty_drivers, &tty_drivers);
...
}
static struct platform_driver serial_pxa_driver = {
.probe = serial_pxa_probe,
.remove = serial_pxa_remove,
.suspend = serial_pxa_suspend,
.resume = serial_pxa_resume,
.driver = {
.name = "pxa3xx-uart",
},
};
int __init serial_pxa_init(void)
{
...
ret = uart_register_driver(&serial_pxa_reg);
...
ret = platform_driver_register(&serial_pxa_driver);
...
}
//执行probe来安装物理串口,我的pxa一共有4个串口
/*
arch\arm\mach-pxa\generic.c
struct platform_device pxa_device_ffuart= {
.name = "pxa2xx-uart",
.id = 0,//对应的line号,所以这个串口在/dev下的名字为ttyS0
.resource = pxa_resource_ffuart,
.num_resources = ARRAY_SIZE(pxa_resource_ffuart),
};
struct platform_device pxa_device_btuart = {
.name = "pxa2xx-uart",
.id = 1,
.resource = pxa_resource_btuart,
.num_resources = ARRAY_SIZE(pxa_resource_btuart),
};
struct platform_device pxa_device_stuart = {
.name = "pxa2xx-uart",
.id = 2,
.resource = pxa_resource_stuart,
.num_resources = ARRAY_SIZE(pxa_resource_stuart),
};
struct platform_device pxa_device_hwuart = {
.name = "pxa2xx-uart",
.id = 3,
.resource = pxa_resource_hwuart,
.num_resources = ARRAY_SIZE(pxa_resource_hwuart),
};
以上三个在arch\arm\mach-pxa\pxa3xx.c=>pxa3xx_init=>
static struct platform_device *devices[] __initdata = {
...
&pxa_device_ffuart,
&pxa_device_btuart,
&pxa_device_stuart,
...
};
platform_add_devices(devices, ARRAY_SIZE(devices));
//批量添加arch\arm\mach-pxa\generic.c下定义的platform_device设备
*/
static int serial_pxa_probe(struct platform_device *dev)
{
...
//为该物理串口申请管理单元
sport = kzalloc(sizeof(struct uart_pxa_port), GFP_KERNEL);
...
sport->port.type = PORT_PXA;
sport->port.iotype = UPIO_MEM;
sport->port.mapbase = mmres->start;
sport->port.irq = irqres->start;
sport->port.fifosize = 64;
sport->port.ops = &serial_pxa_pops;//这就是该物理uart口,struct uart_port的操作函数.
sport->port.line = dev->id;//line号
sport->port.dev = &dev->dev;
sport->port.flags = UPF_IOREMAP | UPF_BOOT_AUTOCONF;
sport->port.uartclk = clk_get_rate(sport->clk);
...
serial_pxa_ports[dev->id] = sport;//用来统计跟踪
uart_add_one_port(&serial_pxa_reg, &sport->port);//serial_pxa_reg为驱动该port物理串口的uart驱动
platform_set_drvdata(dev, sport);
...
}
static struct console serial_pxa_console = {
.name = "ttyS",
.write = serial_pxa_console_write,
.device = uart_console_device,
.setup = serial_pxa_console_setup,
.flags = CON_PRINTBUFFER,
.index = -1,
.data = &serial_pxa_reg,
};
int uart_add_one_port(struct uart_driver *drv, struct uart_port *port)
{
//这里的drv就是serial_pxa_reg
...
if (port->line >= drv->nr)
return -EINVAL;
...
port->cons = drv->cons;//为PXA_CONSOLE,也就是serial_pxa_console
...
if (!(uart_console(port) && (port->cons->flags & CON_ENABLED))) {
spin_lock_init(&port->lock);
lockdep_set_class(&port->lock, &port_lock_key);
}
...
uart_configure_port(drv, state, port);
...
tty_dev = tty_register_device(drv->tty_driver, port->line, port->dev);
//将以drv->tty_driver的major和minor+port->line为基础创建dev字符设备在/dev/ttySx下
//drv->tty_driver的major和minor在uart_register_driver时,直接就等于serial_pxa_reg
//所以这样这个物理串口在用户应用程序打开/dev/ttyS0时,file文件操作集就是tty_fops
//用户应用程序控制物理串口的读写函数都在tty_fops中.
...
}
uart_configure_port=>register_console(port->cons);即:register_console(&serial_pxa_console);
console->setup(console, NULL)=>serial_pxa_console_setup=>
static int __init
serial_pxa_console_setup(struct console *co, char *options)
{
struct uart_pxa_port *up;
int baud = 9600;
int bits = 8;
int parity = 'n';
int flow = 'n';
if (co->index == -1 || co->index >= serial_pxa_reg.nr)
co->index = 0;
up = serial_pxa_ports[co->index];
//co->index=0,这样serial_pxa_ports[0]将作为console的默认配置口
if (!up)
return -ENODEV;
if (options)
uart_parse_options(options, &baud, &parity, &bits, &flow);
return uart_set_options(&up->port, co, baud, parity, bits, flow);
}
最后在register_console()=>将该console注册到console_drivers链表上,最后调用release_console_sem,将printk缓冲的数据打印到ttyS0上,对于release_console_sem的实现流程,可以参看《浅析printk的emit_log_char()算法具体实现》.
void register_console(struct console *console)
{
...
if (preferred_console < 0) {
if (console->index < 0)
console->index = 0;//因为小于0,所以赋值为0
if (console->setup == NULL ||
console->setup(console, NULL) == 0) {
//调用setup初始化console参数
console->flags |= CON_ENABLED | CON_CONSDEV;
preferred_console = 0;
}
}
...
for (i = 0; i < MAX_CMDLINECONSOLES && console_cmdline[i].name[0];
i++) {
//来看看从blob传给kernel的参数:
//Kernel command line: ip=192.168.100.2:192.168.100.1::255.255.255.0::usb0:on console=ttyS0,115200 mem=112M
//所以这里将根据传参console=ttyS0,115200来配置作为console的ttyS0串口.
if (strcmp(console_cmdline[i].name, console->name) != 0)
continue;
if (console->index >= 0 &&
console->index != console_cmdline[i].index)
continue;
if (console->index < 0)
console->index = console_cmdline[i].index;
if (console->setup &&
console->setup(console, console_cmdline[i].options) != 0)
break;
console->flags |= CON_ENABLED;
console->index = console_cmdline[i].index;
if (i == selected_console) {
console->flags |= CON_CONSDEV;
preferred_console = selected_console;
}
break;
}
...
if (bootconsole && (console->flags & CON_CONSDEV)) {
...
} else {
//从ttyS0打印出该log:console [ttyS0] enabled
printk(KERN_INFO "console [%s%d] enabled\n",
console->name, console->index);
}
...
//添加到console_drivers链表,我的板子最后只是注册了ttyS0一个console.
if ((console->flags & CON_CONSDEV) || console_drivers == NULL) {
console->next = console_drivers;
console_drivers = console;
if (console->next)
console->next->flags &= ~CON_CONSDEV;
} else {
console->next = console_drivers->next;
console_drivers->next = console;
}
...
release_console_sem();
//将缓存在printk缓冲区中的数据通过ttyS0打出来,可以参看.
}
以上就是arm平台uart串口是如何登记注册到kernel中,以及如何和console控制台挂接上的,进而printk也就可以将数据发送到该console对应的uart口上了.
接下来让我们看看用户应用程序open("ttyS0")的操作,
void __init console_init(void)
{
initcall_t *call;
/* Setup the default TTY line discipline. */
(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
//建立默认的tty_ldisc规程---Setup the default TTY line discipline
}
tty_open=>init_dev=>initialize_tty_struct=>tty_ldisc_assign=>
将tty_ldisc_N_TTY复制给该dev
然后tty->driver->open(tty, filp);
tty->driver为上面uart_register_driver时注册的tty_driver驱动,它的操作方法集为uart_ops.
tty_fops.tty_open=>
tty->driver->open就是uart_ops.uart_open=>uart_startup=>
port->ops->startup(port)这里port的ops就是serial_pxa_pops;也这就是该物理uart口,struct uart_port的操作函数
serial_pxa_pops.startup就是serial_pxa_startup
看看ttyS0数据发送
tty_fops.tty_write=>do_tty_write=>
tty_ldisc_N_TTY.write_chan=>
tty->driver->write(tty, b, nr)就是uart_ops.uart_write()函数首先拷贝数据到state->info->xmit的circ缓冲区,然后调用uart_start函数=>__uart_start=>
port->ops->start_tx(port);进行实际数据发送,
这里port的ops就是serial_pxa_pops;也这就是该物理uart口,struct uart_port的操作函数
serial_pxa_pops.serial_pxa_start_tx就是serial_pxa_start_tx.
至此我们已经从下到上,然后又从上到下对arm平台上的uart如何在kernel中存活的和如何被使用的做了一个概括的介绍,希望对读者能有启迪【gliethttp.Leith】.