一个小小的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;
}
}
}