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

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

2017-10-14 21:57 295 查看
一个小小的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;
}
}
}


线路规程

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: