Uart 架构之美 -- Linux Kernel 实现浅析

一个小小的uart 蕴含了一座金矿,
uart这只麻雀虽小却五脏俱全,no !我看,它定位不是麻雀,是北冥鸿鹄. 哈哈,

以前分析的在这儿,你可以参考
http://blog.csdn.net/leesagacious/article/details/54670735

先上一幅图.
这里写图片描述

goto的魔力

先来看看这个函数抛异常的 goto
程序可以通过顺序、选择分支和循环三种方式就可以完成了,对吧, 不建议使用goto ?
也不是绝对的吧,goto在某些方面是有积极意义的

<<Structured Programming with go to Statements>> --高纳德

<<C程序设计新思维  P128>>
在出现错误需要执行清理工作时,它比其他替代方案更为清晰     

该作者可能忽略了goto更优秀的一面,Linux Kernel中 大量使用goto.且远不止是执行清理错误的工作这般,

下面是Kernel 中的例子 :
1 : USB Gadget
下面该段代码的具体分析 你看参见以前我的博文
http://blog.csdn.net/leesagacious/article/details/55804233

/*
    On successful return, the gadget is ready to respond to requests from the host

    该函数返回 0 ,表示gadget功能驱动层、gadget设备层、udc层已经建立好了,
    一个完整的usb驱动已经可以接受主机的枚举了,

    哈哈,这个函数的返回值设计的真是好,debugging的时可以直接作为切入点,

    所以,有时 即使使用工具跟踪分析的再细致,也不如函数给人的直觉来的直接、准确,
    usb_composite_probe() 
    {
        return usb_gadget_probe_driver(gadget_driver);
    }
*/
int usb_gadget_probe_driver(struct usb_gadget_driver *driver)
{
    struct usb_udc      *udc = NULL;
    int         ret;
    if (!driver || !driver->bind || !driver->setup)
        return -EINVAL;

    mutex_lock(&udc_lock);
    list_for_each_entry(udc, &udc_list, list) {
        /* For now we take the first one */
        if (!udc->driver)
            goto found;      // 在这儿 
    }

    pr_debug("couldn't find an available UDC\n");
    mutex_unlock(&udc_lock);
    return -ENODEV;
found:
    ret = udc_bind_to_driver(udc, driver);
    mutex_unlock(&udc_lock);
    return ret;
}

看一个iic的实现
具体代码分析请参见我以前的博文
http://blog.csdn.net/leesagacious/article/details/50488949

static int i2c_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat)
{
    .....
        if (i2c->state == STATE_READ)
            goto prepare_read;
 retry_write:
        if (!is_msgend(i2c)) {
        } else if (!is_lastmsg(i2c)) {
        } else {
            /* send stop */
            s3c24xx_i2c_stop(i2c, 0);
        }
        goto retry_write;
        break;
    case STATE_READ:    
 prepare_read:
        if (is_msglast(i2c)) {

        } else if (is_msgend(i2c)) {
            if (is_lastmsg(i2c)) {
            } else {
            }
        }
        break;
    }
}
所以对于外设read、write函数的读写过程都逃逸不了goto的魔掌,
且在一些优秀的协议中读写过程利用goto与Linux Kernel休眠与唤醒机制相结合,更是让系统大放异彩
你还能说goto只是用来处理错误的吗 ?goto的魔力可见一斑!

DTS 与 Uart

dtsi文件描述的外设是怎么被加载到Linux Kernel中的,启动的时候是怎么枚举的 ?

基础数据结构

uart架构围绕着 三个数据结构的对象为中心,以接口提供的功能为基础,有了原材料之后,Uart开始裂变 …
Figure 1.1
这里写图片描述

struct uart_driver {
    struct module       *owner;
    const char      *driver_name;
    const char      *dev_name;
    int          major;
    int          minor;
    int          nr;
    struct console      *cons;
    struct uart_state   *state;
    struct tty_driver   *tty_driver;
};
/*
    对应一个物理上真实的串口
*/
struct uart_port {
    spinlock_t      lock;           
    unsigned long   iobase;         
    unsigned char __iomem   *membase;       
    unsigned int    (*serial_in)(struct uart_port *, int);
    void            (*serial_out)(struct uart_port *, int, int);
    int             (*handle_irq)(struct uart_port *);
    ....
    unsigned int        irq;            
    unsigned long       irqflags;       
    unsigned int        uartclk;        
    unsigned int        fifosize;
    ....
    ....
}
/*
    物理硬件操作的入口函数集合,每个port数据路径都是隔离的,要"串线"可修改上层的线路规程
*/
struct uart_ops {
    unsigned int    (*tx_empty)(struct uart_port *);
    void            (*set_mctrl)(struct uart_port *, unsigned int mctrl);
    int             (*startup)(struct uart_port *);
    void            (*flush_buffer)(struct uart_port *);
    void            (*set_termios)(struct uart_port *, struct ktermios *new,struct ktermios *old);
    void            (*set_ldisc)(struct uart_port *, int new);
    ...
};

不是常说每个port都存在uart_port结构的一个实例,真正对应一个物理串口吗 ?
好,下面就看看Linux Kernel的架构实现

你自己的驱动要想和内核连接起来形成一体,首先你必须"迎合"内核源码的思想,
随便弄上来一个cdev + 一个file_operations + read /write,简约却很简单
直接将接口暴露给kernel的系统调用,那也不是<<大教堂与集市>>中 "减少的不能再少了" 的思想吧.
在夯实的土地上建造茅草屋,那你以后得唱 "茅屋为秋风所破歌"了,亡羊补牢,也许不会再是传说了. 

好,下面这个注册每个端口的函数,就是将UART驱动与kernel联系起来了,与 uart_register_driver函数一样很重要!
都是uart驱动 必须调用的 !

int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport);

欣赏这个函数之前,你必须要明白的一个结构体是 struct uart_state, 它真的太重要了.

The number of uart_state is equals to the number of hardware ports that ard accessed via the driver

1: 什么时候分配的
请移步: http://blog.csdn.net/leesagacious/article/details/54670735

先看下面的一个结构体

/*
    封装了基础的uart_port.
*/
struct uart_8250_port {
    struct uart_port    port;
    struct timer_list   timer;      /* "no irq" timer */
    .....
};

/*
    好,看 Figure 1.1 中的 * ops,就在这里进行了指向.
*/
serial8250_init(void)
{
    ↓
    static void __init serial8250_isa_init_ports(void)
    {
        ↓
        /* 初始化封装的每一个port.*/
        for (i = 0; i < nr_uarts; i++) {
            /* 获取每一个封装的uart_8250_port,下面进行初始化其维护的资源 */
            struct uart_8250_port *up = &serial8250_ports[i];
            /* 获取基础的uart_port */
            struct uart_port *port = &up->port;
            /* 
                数组的下标就是它的line,这个line很重要,
                遍历数组,用数组下标赋值其对象维护的成员,内核惯用的伎俩!
                类似的如:
                    1 :字符设备的主设备号就是probes[255]数组的下标.
                    2 :http://blog.csdn.net/leesagacious/article/details/50418197
            */
            port->line = i;
            /* 初始化维护的自旋锁 */
            spin_lock_init(&port->lock);
            /* 初始化维护的定时器 */
            init_timer(&up->timer);   
            /* 超时执行函数 */
            up->timer.function = serial8250_timeout; 
            ....
            /*
              就是上面说的uart_ops,操作硬件的入口函数集,

              你可能会立即联想到那个tty的那个file_operations吧,哈哈!
              在uart框架中,从系统调用的read、write、poll经过 tty维护的tty_fops提供的对应方法
              一直会调用到这里的ops提供的对应的方法,

              一路走来,按照信息流动的过程,分散了关注、松散了耦合,每层功能清晰,专注,
              虽然性能有损耗,但是可以通过缓存来减少影响,
            */
            port->ops = &serial8250_pops;
        }
    }   
}   

线路规程

这里写图片描述

猜你喜欢

转载自blog.csdn.net/leesagacious/article/details/78237306
今日推荐