您的位置:首页 > 运维架构 > Linux

我对linux理解之tty一

2012-08-09 15:56 369 查看
我们从具体的uart驱动开始追踪它的层次,我的系统是mx51,故从mxc_uart.c进行。

1,初始化注册过程:

static int __init mxcuart_init(void)

{

int ret = 0;

printk(KERN_INFO "Serial: MXC Internal UART driver\n");

ret = uart_register_driver(&mxc_reg); //注册tty驱动接口

if (ret == 0) {

/* Register the device driver structure. */

ret = platform_driver_register(&mxcuart_driver);

if (ret != 0) {

uart_unregister_driver(&mxc_reg);

}

}

return ret;

}

我们先看uart_register_driver(&mxc_reg),mxc_reg的定义如下:

static struct uart_driver mxc_reg = {

.owner = THIS_MODULE,

.driver_name = "ttymxc",

.dev_name = "ttymxc",

.major = SERIAL_MXC_MAJOR,

.minor = SERIAL_MXC_MINOR,

.nr = MXC_UART_NR,

.cons = MXC_CONSOLE,

};

uart_register_driver定义在serial_core中

int uart_register_driver(struct uart_driver *drv)

{

struct tty_driver *normal = NULL;

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.

*/

drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);

retval = -ENOMEM;

if (!drv->state)

goto out;

normal = alloc_tty_driver(drv->nr); //申请一个有nr根lines的drvier

if (!normal)

goto out;

drv->tty_driver = normal; //赋值给uart_driver的私有变量,drv对应mxc_uart中的mxc_reg

normal->owner = drv->owner;

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; //私有数据,linux经常有这样的用法

tty_set_operations(normal, &uart_ops); //注意这边的uart_ops,最后调用到了mxc_uart中的mxc_ops

//上面主要是相关结构体的初始化和一些赋值

/*

* Initialise the UART state(s).

*/

for (i = 0; i < drv->nr; i++) {

struct uart_state *state = drv->state + i; //开始的时候申请了drv->nr个state,现在逐个初始化

state->close_delay = 500; /* .5 seconds */

state->closing_wait = 30000; /* 30 seconds */

mutex_init(&state->mutex);

tty_port_init(&state->info.port);

init_waitqueue_head(&state->info.delta_msr_wait);

tasklet_init(&state->info.tlet, uart_tasklet_action,

(unsigned long)state);

}

retval = tty_register_driver(normal); //注册到tty core

out:

if (retval < 0) {

put_tty_driver(normal);

kfree(drv->state);

}

return retval;

}

我们再看下tty_register_driver这个定义,它定义在tty_core中:

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 (!p)

return -ENOMEM;

}

if (!driver->major) {//根据主设备号决定是动态还是静态创建字符设备编号

error = alloc_chrdev_region(&dev, driver->minor_start,

driver->num, driver->name);

if (!error) {

driver->major = MAJOR(dev);

driver->minor_start = MINOR(dev);

}

} else {

dev = MKDEV(driver->major, driver->minor_start);

error = register_chrdev_region(dev, driver->num, driver->name);

}

if (error < 0) {

kfree(p);

return error;

}

if (p) {

driver->ttys = (struct tty_struct **)p;

driver->termios = (struct ktermios **)(p + driver->num);

} else {

driver->ttys = NULL;

driver->termios = NULL;

}

//注册字符设备

cdev_init(&driver->cdev, &tty_fops);

driver->cdev.owner = driver->owner;

error = cdev_add(&driver->cdev, dev, driver->num); //将字符设备添加到系统中

if (error) {

unregister_chrdev_region(dev, driver->num);

driver->ttys = NULL;

driver->termios = NULL;

kfree(p);

return error;

}

mutex_lock(&tty_mutex);

list_add(&driver->tty_drivers, &tty_drivers); //添加到tty core的drivers链表里面,tty_core维护着这个链表

mutex_unlock(&tty_mutex);

if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) { /*根据动态设备标记决定是否此时注册tty设备,就是上面uart_register_driver里的normal->flags,在这里mxc_uart中有设置,

它是在mxcuart_probe中的mxcuart_probe里注册的,device注册后将在sys中形成层次结构,系统启动后,udev系统(android中为vold)将在/dev/下面

生成节点文件,这些节点文件操作都是按照该字符设备的tty_fops进行*/

for (i = 0; i < driver->num; i++)

tty_register_device(driver, i, NULL);

}

proc_tty_register_driver(driver);

driver->flags |= TTY_DRIVER_INSTALLED; //设置完成标记

return 0;

}

我们看到这个tty_core中的注册函数主要工作是添加了字符设备。我们可以简单地看到注册一个uart驱动架构:

mxc_uart---->serial_core---->tty_core

我们下面再看mxcuart_init中的platform_driver_register(&mxcuart_driver)。要读懂这个过程需要理解platform bus的工作流程。

2,platform_driver_register(&mxcuart_driver)

int platform_driver_register(struct platform_driver *drv)

{

drv->driver.bus = &platform_bus_type;

if (drv->probe)

drv->driver.probe = platform_drv_probe;

if (drv->remove)

drv->driver.remove = platform_drv_remove;

if (drv->shutdown)

drv->driver.shutdown = platform_drv_shutdown;

return driver_register(&drv->driver);

}

platform_bus_type定义如下:

struct bus_type platform_bus_type = {

.name = "platform",

.dev_attrs = platform_dev_attrs,

.match = platform_match,

.uevent = platform_uevent,

.pm = PLATFORM_PM_OPS_PTR,

};

我们还记得driver_register分析中用的bus macth函数吗?platform总线的match就是对应这里的platform_match。我们下面看下它的定义

static int platform_match(struct device *dev, struct device_driver *drv)

{

struct platform_device *pdev = to_platform_device(dev);

struct platform_driver *pdrv = to_platform_driver(drv);

/* match against the id table first */

if (pdrv->id_table)

return platform_match_id(pdrv->id_table, pdev) != NULL; //如果有id_table,去匹配id

/* fall-back to driver name match */

return (strcmp(pdev->name, drv->name) == 0); //匹配设备的名字和驱动的名字

}

我们看下platform_match_id()定义:

static const struct platform_device_id *platform_match_id(

struct platform_device_id *id,

struct platform_device *pdev)

{

while (id->name[0]) {

if (strcmp(pdev->name, id->name) == 0) { //匹配设备的名字和id的名字

pdev->id_entry = id;

return id; //返回匹配的的id

}

id++;

}

return NULL;

}

所以我们看到platform匹配过程是先匹配设备的名字和id的名字,然后去匹配设备的名字和驱动的名字。

我们由driver_register分析知道,如果匹配成功的话,将会执行probe函数。

好了,我们知道platform工作流程以后,我们继续我们的uart分析platform_driver_register(&mxcuart_driver),先看下mxcuart_driver定义:

static struct platform_driver mxcuart_driver = {

.driver = {

.name = "mxcintuart",

},

.probe = mxcuart_probe,

.remove = mxcuart_remove,

.suspend = mxcuart_suspend,

.resume = mxcuart_resume,

};

我们发现它没有定义驱动的id_table,只有他的名字是"mxcintuart",所以会根据这个名字去匹配设备,我们在arch/arm/mach-mx5/serial.c发现了它的设备定义:

static struct platform_device mxc_uart_device1 = {

.name = "mxcintuart",

.id = 0,

.num_resources = ARRAY_SIZE(mxc_uart_resources1),

.resource = mxc_uart_resources1,

.dev = {

.platform_data = &mxc_ports[0],

},

};

这里只贴出第一个口子的设备定义,其它的都类似,驱动会一一去匹配的,所以这里系统初始化的时候会成功匹配,匹配完了会执行probe函数!

我们下面看他的probe函数:

static int mxcuart_probe(struct platform_device *pdev)

{

int id = pdev->id;//编号,从设备定义中获得

struct resource *res;

void __iomem *base;

mxc_ports[id] = pdev->dev.platform_data; //也是从设备定义中获得,从上面看就是port口子

mxc_ports[id]->port.ops = &mxc_ops; //对应的口子操作

/* Do not use UARTs that are disabled during integration */

if (mxc_ports[id]->enabled == 1) {

res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //从设备那边获得资源,从设备定义那边看,这些都一目了然

if (!res)

return -ENODEV;

base = ioremap(res->start, res->end - res->start + 1);

if (!base)

return -ENOMEM;

mxc_ports[id]->port.membase = base;

mxc_ports[id]->port.mapbase = res->start;

mxc_ports[id]->port.dev = &pdev->dev;

mxc_ports[id]->port.irq = platform_get_irq(pdev, 0);

mxc_ports[id]->irqs[0] = platform_get_irq(pdev, 1);

mxc_ports[id]->irqs[1] = platform_get_irq(pdev, 2);

spin_lock_init(&mxc_ports[id]->port.lock);

/* Enable the low latency flag for DMA UART ports */

if (mxc_ports[id]->dma_enabled == 1) { //这个在设备定义中可以查看的,有的打开,有的没打开

mxc_ports[id]->port.flags |= UPF_LOW_LATENCY;

}

mxc_ports[id]->clk = clk_get(&pdev->dev, "uart_clk");

if (mxc_ports[id]->clk == NULL)

return -1;

uart_add_one_port(&mxc_reg, &mxc_ports[id]->port);

platform_set_drvdata(pdev, mxc_ports[id]); //设置私有数据

}

return 0;

}

probe主要工作转入uart_add_one_port(&mxc_reg, &mxc_ports[id]->port):

int uart_add_one_port(struct uart_driver *drv, struct uart_port *port)

{

struct uart_state *state;

int ret = 0;

struct device *tty_dev;

BUG_ON(in_interrupt());

if (port->line >= drv->nr)

return -EINVAL;

state = drv->state + port->line;

mutex_lock(&port_mutex);

mutex_lock(&state->mutex);

if (state->port) {

ret = -EINVAL;

goto out;

}

state->port = port; //将port和state联系起来

state->pm_state = -1;

port->cons = drv->cons;

port->info = &state->info;

/*

* If this port is a console, then the spinlock is already

* initialised.

*/

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); //配置口子类型

/*

* Register the port whether it's detected or not. This allows

* setserial to be used to alter this ports parameters.

*/

tty_dev = tty_register_device(drv->tty_driver, port->line, port->dev); //注册到tty core

if (likely(!IS_ERR(tty_dev))) {

device_init_wakeup(tty_dev, 1); //pm部分

device_set_wakeup_enable(tty_dev, 0);

} else

printk(KERN_ERR "Cannot register tty device on line %d\n",

port->line);

/*

* Ensure UPF_DEAD is not set.

*/

port->flags &= ~UPF_DEAD;

out:

mutex_unlock(&state->mutex);

mutex_unlock(&port_mutex);

return ret;

}

我们看下tty_register_device(drv->tty_driver, port->line, port->dev):

struct device *tty_register_device(struct tty_driver *driver, unsigned index,

struct device *device)

{

char name[64];

dev_t dev = MKDEV(driver->major, driver->minor_start) + index;

if (index >= driver->num) {

printk(KERN_ERR "Attempt to register invalid tty line number "

" (%d).\n", index);

return ERR_PTR(-EINVAL);

}

if (driver->type == TTY_DRIVER_TYPE_PTY)

pty_line_name(driver, index, name); //虚拟终端的名字

else

tty_line_name(driver, index, name);//生成tty*的名字

return device_create(tty_class, device, dev, NULL, name); //创建属于tty的设备,最终将进入以前分析的device_register,也就是添加到sys层次结构中

}

这样整个probe的工作就完了,其实从上面看下来,主要就是注册tty device。同样的的注册流程也是:mxc_uart---->serial_core---->tty_core
http://www.qrsdev.com/forum.php?mod=viewthread&tid=396&extra=page%3D1
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: