Linux3.0内核sc32440串口驱动分析(一)——初始化与注册

一、TTY简介

tty设备的名称是从过去的电传字打字机缩写(Teletypes)而来的。最初tty是指连接到Unix系统上的物理或者虚拟终端。随着时间的推移,当通过串行口能够建立起终端连接后,这个名字也用来指任何的串口设备。它还有多种类,例如串口(ttySn、ttySACn、ttyOn)、USB到串口的转换器(ttyUSBn),还有需要特殊处理才能正常工作的调制解调器(比如传统的WinModem类设备)等。tty虚设备支持虚拟控制台,它能通过键盘及网络连接或者通xterm会话登录到计算机上。

TTY框架

tty驱动分为三个部分。即tty子系统核心(TTY_core)tty链路规程(Line discipline)tty驱动(TTY driver)1、tty核心从用户那里得到将要发往tty设备的数据,将数据发送给tty线路规程驱动2、tty线路规程驱动负责将数据格式化成硬件能理解的格式数据传给tty驱动3、tty驱动发送数据给硬件

从给串口写入数据为例去分析tty驱动,写串口的调用链如下:

write->sys_write->vfs_write->redirected_tty_write->tty_write->n_tty_write->uart_write->uart_start->s3c24xx_serial_start_tx

1、tty核心(TTY_core),是对整个tty设备的抽象。对用户提供统一的接口。包括sys_write->vfs_write

2、tty线路规程(Line discipline)。是对传输数据的格式化。包括redirected_tty_write->tty_write->n_tty_write->

3、tty驱动(TTY driver)。是面向tty设备的硬件驱动。这里面真正的对硬件进行操作。包括uart_write->uart_start->s3c24xx_serial_start_tx

先留个概念在这,接下来分析s3c2440串口驱动源码会慢慢讲到

二、基于Linux3.0和s3c2440的串口驱动——初始化与注册

[wuyujun@wuyujunlocalhost ~]$ cd fl2440/linux/linux-3.0

切换到内核路径linux-3.0,看驱动代码要先从module_init开始看

[wuyujun@wuyujunlocalhost linux-3.0]$ grep -n s3c24xx_serial_modinit -r ./

-n显示行号 -r递归的寻找目录下面包含s3c24xx_serial_modinit字符串的文件,下面也都是这样追踪代码

匹配到二进制文件 ./.tmp_vmlinux1

匹配到二进制文件 ./vmlinux

./.tmp_System.map:442:c0016074 t s3c24xx_serial_modinit

./.tmp_System.map:937:c001ebac t __initcall_s3c24xx_serial_modinit6

./System.map:442:c0016074 t s3c24xx_serial_modinit

./System.map:937:c001ebac t __initcall_s3c24xx_serial_modinit6

匹配到二进制文件 ./vmlinux.o

./drivers/tty/serial/samsung.c:1244:static int __init s3c24xx_serial_modinit(void)

./drivers/tty/serial/samsung.c:1262:module_init(s3c24xx_serial_modinit);

匹配到二进制文件 ./drivers/tty/serial/samsung.o

匹配到二进制文件 ./drivers/tty/serial/built-in.o

匹配到二进制文件 ./drivers/tty/built-in.o

匹配到二进制文件 ./drivers/built-in.o

匹配到二进制文件 ./.tmp_vmlinux2

可以找到module_init在./drivers/tty/serial/samsung.c的1262行

static int __init s3c24xx_serial_modinit(void)

{

    int ret;

 

    ret = uart_register_driver(&s3c24xx_uart_drv); //注册串口驱动

    if (ret < 0) {

        printk(KERN_ERR "failed to register UART driver\n");

        return -1;

    }    

    return 0;

}

static void __exit s3c24xx_serial_modexit(void)

{

    uart_unregister_driver(&s3c24xx_uart_drv);

}

module_init(s3c24xx_serial_modinit);

module_exit(s3c24xx_serial_modexit);

这里需要注意的是注册的串口驱动结构体s3c24xx_uart_drv

static struct uart_driver s3c24xx_uart_drv = {

    .owner      = THIS_MODULE,

    .driver_name    = "s3c2410_serial",

    .nr     = CONFIG_SERIAL_SAMSUNG_UARTS,

    .cons       = S3C24XX_SERIAL_CONSOLE,

    .dev_name   = S3C24XX_SERIAL_NAME,

    .major      = S3C24XX_SERIAL_MAJOR,

    .minor      = S3C24XX_SERIAL_MINOR,

};

在结构体中只是填充了一些名字、设备号等信息没有什么关键的信息,看看struct uart_driver这个结构体是怎么定义的

struct uart_driver {

struct module *owner; /* 拥有该uart_driver的模块,一般为THIS_MODULE */

const char *driver_name; /* 串口驱动名,串口设备文件名以驱动名为基础 */

const char *dev_name; /* 串口设备名 */

int  major; /* 主设备号 */

int  minor; /* 次设备号 */

int  nr; /* uart_driver串口个数 */

struct console *cons; /* 其对应的console.若该uart_driver支持serial console,否则为NULL */



/* 下面这俩,它们应该被初始化为NULL */

struct uart_state *state; /* 下层,串口驱动层 */

struct tty_driver *tty_driver;

};

重要的是struct tty_driver *tty_driverstruct uart_statestruct uart_state这个结构体下面会说到,接下来先继续看注册串口驱动函数uart_register_driver()函数,uart_register_driver()/drivers/tty/serial/serial_core.c

int uart_register_driver(struct uart_driver *drv)

{

    struct tty_driver *normal; //struct tty_driverstruct uart_driver结构体的成员结构体

    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);  // 来申请 nr 个 uart_state 空间

    if (!drv->state)

        goto out;

 

    normal = alloc_tty_driver(drv->nr); //创建tty结构函数

    if (!normal)

        goto out_kfree;

 

    drv->tty_driver = normal;

 

    normal->owner       = drv->owner; //驱动模块拥有者

normal->driver_name = drv->driver_name;//用来在/proc/tty/drivers文件中向用户描述驱动程序的状态,并且在sysfs的tty类目录中显示当前被加载的tty驱动程序

    normal->name        = drv->dev_name; //分配给单独tty结点的名字,通过在该名字末尾添加tty设备序号来创建tty设备

    normal->major       = drv->major; //设置主设备号

normal->minor_start = drv->minor; //次设备号

normal->type        = TTY_DRIVER_TYPE_SERIAL; //设置tty驱动类型,可分控制台、串口和pty,这里是串口驱动

    normal->subtype     = SERIAL_TYPE_NORMAL; //描述向tty核心注册的是何种tty驱动,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);// struct tty_driver *normal,设置驱动中定义的操作函数,操作函数在uart_ops中定义,这个要记住后面会再看到!

 

    /*

     * Initialise the UART state(s).

     */

    for (i = 0; i < drv->nr; i++) {    //根据nr个数,初始化struct uart_state结构体里的port成员,初始化每个uart_state的tasklet

        struct uart_state *state = drv->state + i;

        struct tty_port *port = &state->port;

 

        tty_port_init(port);

        port->ops = &uart_port_ops;

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

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

        tasklet_init(&state->tlet, uart_tasklet_action,

                 (unsigned long)state); //初始化tasklet,tasklet用于中断下半部

    }

 

    retval = tty_register_driver(normal);//向tty核心注册tty驱动程序

    if (retval >= 0)

        return retval;

 

    put_tty_driver(normal); //如果注册不成功,使用put_tty_driver清除tty_driver结构

out_kfree:

    kfree(drv->state);

out:

    return -ENOMEM;

}

接下来分析tty_register_driver,看到这里就很熟悉了,函数的作用是分配字符设备,并进行初始化和注册;tty_register_driver()./drivers/tty/tty_io.c

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) {

            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);  //静态申请主次设备号

}

......

    cdev_init(&driver->cdev, &tty_fops); //初始化字符结构体

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

    error = cdev_add(&driver->cdev, dev, driver->num); //将字符结构体注册到内核

......

mutex_lock(&tty_mutex);

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++) {

d = tty_register_device(driver, i, NULL); //轮询的回调直到将所有tty驱动注册

...... 

        }    

}    

proc_tty_register_driver(driver);

driver->flags |= TTY_DRIVER_INSTALLED;

return 0;

}

void proc_tty_register_driver(struct tty_driver *driver)

{

    struct proc_dir_entry *ent;

    

    if (!driver->driver_name || driver->proc_entry ||

        !driver->ops->proc_fops)

        return;

 

    ent = proc_create_data(driver->driver_name, 0, proc_tty_driver,

                   driver->ops->proc_fops, driver);

    driver->proc_entry = ent;

}

proc_create_data()不再往下追了,大概就是用来在/proc/目录下创建文件,到这里就完成了初始化和注册。需要重点注意的几个结构体struct uart_state以及uart_state成员结构体struct uart_port,以及结构体uart_ops定义tty核心会调用到的操作函数。

struct uart_state {

struct tty_port port;

int pm_state;

struct circ_buf xmit;

struct tasklet_struct tlet;

struct uart_port *uart_port; // 对应于一个串口设备

};

在注册 driver 时,会根据 uart_driver->nr 来申请 nr 个 uart_state 空间,uart_state的结构体成员struct uart_port用来存放驱动所支持的串口(端口)的物理信息。

struct uart_port {

spinlock_t lock; /* port lock */

unsigned long iobase; /* io端口基地址(物理) */

unsigned char __iomem *membase; /* io内存基地址(虚拟) */

unsigned int (*serial_in)(struct uart_port *, int);

void (*serial_out)(struct uart_port *, int, int);

unsigned int irq; /* 中断号 */

unsigned long irqflags; /* 中断标志  */

unsigned int uartclk; /* 串口时钟 */

unsigned int fifosize; /* 串口缓冲区大小 */

unsigned char x_char; /* xon/xoff char */

unsigned char regshift; /* 寄存器位移 */

unsigned char iotype; /* IO访问方式 */

unsigned char unused1;



unsigned int read_status_mask; /* 关心 Rx error status */

unsigned int ignore_status_mask; /* 忽略 Rx error status */

struct uart_state *state; /* pointer to parent state */

struct uart_icount icount; /* 串口信息计数器 */



struct console *cons; /* struct console, if any */

#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)

unsigned long sysrq; /* sysrq timeout */

#endif



upf_t flags;



unsigned int mctrl; /* 当前的Moden 设置 */

unsigned int timeout; /* character-based timeout */

unsigned int type; /* 端口类型 */

const struct uart_ops *ops; /* 串口端口操作函数 */

unsigned int custom_divisor;

unsigned int line; /* 端口索引 */

resource_size_t mapbase; /* io内存物理基地址 */

struct device *dev; /* 父设备 */

unsigned char hub6; /* this should be in the 8250 driver */

unsigned char suspended;

unsigned char unused[2];

void *private_data; /* generic platform data pointer */

};

uart_port填充硬件相关的信息,通过 uart_add_one_port 添加到 uart_driver->uart_state->uart_port 中。串口驱动是也是基于platform总线驱动的,一开始,当串口驱动与串口设备匹配,在s3c2410_serial_probe会调用uart_add_one_port添加物理信息,下面s3c2410_serial_probe()过程摘至:

http://blog.chinaunix.net/uid-27041925-id-3999817.html

s3c2410_serial_probe

      s3c24xx_serial_probe(dev, &s3c2410_uart_inf);

            ourport = &s3c24xx_serial_ports[probe_index];  //此处引用了上面的s3c24xx_serial_ports

                s3c24xx_serial_init_port(ourport, info, dev); //完成硬件寄存器初始化

                      s3c24xx_serial_resetport(port, cfg);

                            (info->reset_port)(port, cfg); ---> s3c2410_serial_resetport

                                 uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);

                                           state = drv->state + port->line;  //将s3c24xx_serial_ports中的uart_port赋给了uart_driver中的uart_state->port

                                                  state->port = port;

                                                         uart_configure_port(drv, state, port);

                                                                  port->ops->config_port(port, flags);

                                                                          tty_register_device(drv->tty_driver, port->line, port->dev); //tty设备注册 

                                                                                  tty_register_device

                                                                                            device_create(tty_class, device, dev, name);

                                                                                                     device_register(dev);

                                                                                                              device_add(dev);

初始化与注册过程如下:

1、kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL)申请nr个 uart_state 空间,每一个 uart_state 都有一个 uart_port用来存放于应将相关的信息 。

2、alloc_tty_driver(drv->nr);分配一个 tty_driver 。

3、tty_set_operations(normal, &uart_ops);对 tty_driver 进行设置,其中包括默认波特率、校验方式等,还有一个重要的 ops ,uart_ops ,它是tty核心与我们串口驱动通信的接口。

4、tasklet_init(&state->tlet, uart_tasklet_action,(unsigned long)state); 初始化每一个 uart_state 的 tasklet 。

5、tty_register_driver(normal);注册 tty_driver 。

 

串口驱动很复杂,光是初始化与注册都这么长了...接下来的串口操作函数部分放到下篇再讲

参考:https://www.jianshu.com/p/3a9013b9569c

https://blog.csdn.net/lizuobin2/article/details/51773305

 

 

 

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值