浅析arm平台上uart在kernel上注册和使用的上、下流程

来源:互联网 发布:广州楼盘数据详情 编辑:程序博客网 时间:2024/05/17 00:56

浅析arm平台上uart在kernel上注册和使用的上、下流程


可以参考:

【浅析tty ldisc】

【浅析char字符驱动程序是怎么和文件系统结合到一起的】

【ttyS、tty,console和pty】

【浅析2.6.24下char字符驱动tty注册和实际调用流程】

【浅析2.6.24内核printk函数】

static struct uart_driver serial_pxa_reg = {
    .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打出来,可以参看【浅析printk的emit_log_char()算法具体实现】.
}
以上就是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】.