Linux tty驱动学习 - UART驱动的open操作

来源:互联网 发布:数据挖掘工具 linux 编辑:程序博客网 时间:2024/05/17 22:48

/dev/ttyS*属于字符设备,所以在用户空间调用open系统调用打开该类设备,进入到tty核心层后调用的第一个函数是tty_fops中的tty_oepn。tty_open的前面三个判断是根据设备主次设备号来判断设备的类型,分别为/dev/tty(5,0),/dev/tty0(4.0),/dev/console(5,1),分别表示当前控制终端,当前虚拟终端,当前控制台终端。而uart设备对应的设备名为/dev/tty*,另外从uart_driver的定义中也可以看到设备的主设备号为4,次设备号从64开始,所以前面三个判断条件都不满足,直接跳到get_tty_driver()。get_tty_driver()函数就是利用设备的设备号跟tty_drivers链表上面所有的驱动去做比对,看当前设备的设备号是否在驱动所支持的范围里,如果有找到这样的驱动,则返回tty_driver的描述符,并返回该设备在驱动设备链表中的索引位置。

static struct tty_driver *get_tty_driver(dev_t device, int *index){struct tty_driver *p;list_for_each_entry(p, &tty_drivers, tty_drivers) {dev_t base = MKDEV(p->major, p->minor_start);if (device < base || device >= base + p->num)continue;*index = device - base;return tty_driver_kref_get(p);}return NULL;}

找到对应的tty_driver之后,接下来会生成一个tty_struct。tty_driver_lookup_tty()用来检测当前要打开的是否是已经被打开过的tyy设备,其实就是根据设备的索引号直接返回driver->ttys[idx]。如果返回的tty不为空,表示是一个已经打开的tty设备,则调用tty_reopen()函数来增加该设备的引用计数,否则调用tty_init_dev()函数来打开一个tty设备。

struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx,int first_ok){struct tty_struct *tty;int retval;lock_kernel();/* Check if pty master is being opened multiple times */if (driver->subtype == PTY_TYPE_MASTER &&(driver->flags & TTY_DRIVER_DEVPTS_MEM) && !first_ok) {unlock_kernel();return ERR_PTR(-EIO);}unlock_kernel();if (!try_module_get(driver->owner))return ERR_PTR(-ENODEV);tty = alloc_tty_struct();if (!tty)goto fail_no_mem;initialize_tty_struct(tty, driver, idx);retval = tty_driver_install_tty(driver, tty);if (retval < 0) {free_tty_struct(tty);module_put(driver->owner);return ERR_PTR(retval);}retval = tty_ldisc_setup(tty, tty->link);if (retval)goto release_mem_out;return tty;fail_no_mem:module_put(driver->owner);return ERR_PTR(-ENOMEM);/* call the tty release_tty routine to clean out this slot */release_mem_out:if (printk_ratelimit())printk(KERN_INFO "tty_init_dev: ldisc open failed, " "clearing slot %d\n", idx);lock_kernel();release_tty(tty, idx);unlock_kernel();return ERR_PTR(retval);}
在tty_init_dev()中先是分配一个tty_struct,然后调用initialize_tty_struct()对此进行初始化。首先是指定该设备的线路规程类别为N_TTY,接下来是对该tty_struct操作的各种锁以及等待队列进行初始化,最后就是把tty_struct和tty_driver之间的相应关系链接起来。tty_driver_install_tty()从名字上就可以知道它的作用是把刚生成的tty_struct安装到tty_driver->ttys中去。tty_ldisc_setup()用来打开一个线路规程,在initialize_tty_struct()中指定了线路规程为N_TTY。在tty_ldisc.c有把N_TTY对应的tty_ldisc_ops赋值为tty_ldisc_N_TTY。而tty_ldisc_N_TTY在n_tty.c中定义,其open操作为n_tty_open。_

继续回到tty_open中,把tty_struct赋值给filp的私有数据结构,这样的话read和write的操作就可以从filp描述符中找到对应的tty_struct。接下来会执行tty->ops->open操作,tty->ops就是tty_driver->ops,而在注册uart驱动的时候tty_driver->ops被赋值为uart_ops,所以最后被执行的是uart_ops里面的uart_open。从tty核心层的tty_open进入到tty驱动层的uart_open,所以首先从tty_struct中找出uart_state和tty_port,并把uart_state赋值给tty_struct的私有数据结构。然后就是调用uart_startup来开启uart硬件设备,在uart_startup()里面首先会为uart_state的发送缓冲池xmit分配内存空间,接着会调用uart_port->ops->startup函数。uart_port在uart驱动层里面表示一个uart端口设备,所以表示从tty驱动层进入到了uart驱动层,对应到了具体的硬件操作。uart_port->ops在serial8250_isa_init_ports里面被赋值为serial8250_pops,其startup被定义为serial8250_startup函数。

serial8250_startup函数主要是调用serial_outp/serial_inp对uart的硬件寄存器进行读写操作,从而完成硬件的初始化工作。而serial_out/serial_inp最终是由uart_port里面的serial_out/serial_in实现的,这两个函数在注册uart_port的时候就已经定义好了。在serial8250_register_port中由set_io_from_upio()函数实现初始化操作,根据iotype类型的不同,实现方式也不同。serial_8250_clear_fifos()先清掉FIFO缓存,然后把FIFO的功能给关掉,在set_termios里面再重新把它们打开。接着是清掉中断寄存器,之后会做一个测试来判断中断是否有正常工作,如果不能正常工作则会采用计时器的功能替换中断功能。如果中断能正常工作,则调用serial_link_irq_chain()函数来完成中断注册的工作。注册的中断处理函数为serial8250_interrupt,当有中断发生时,该函数会被调用。在serial8250_interrupt中可以看到,真正处理的工作是由serial8250_handle_port完成的。在该函数里面,根据中断的来源来做不同的处理,如果是接收到数据产生中断,则调用receive_chars()来处理接收到的数据。如果是发送hold寄存器为空产生的中断,则调用transmit_charts()来发送数据。

#define serial_inp(up, offset)serial_in(up, offset)#define serial_outp(up, offset, value)serial_out(up, offset, value)

#define serial_in(up, offset)\(up->port.serial_in(&(up)->port, (offset)))#define serial_out(up, offset, value)\(up->port.serial_out(&(up)->port, (offset), (value)))

static void serial8250_handle_port(struct uart_8250_port *up){unsigned int status;unsigned long flags;spin_lock_irqsave(&up->port.lock, flags);status = serial_inp(up, UART_LSR);DEBUG_INTR("status = %x...", status);if (status & (UART_LSR_DR | UART_LSR_BI))receive_chars(up, &status);check_modem_status(up);if (status & UART_LSR_THRE)transmit_chars(up);spin_unlock_irqrestore(&up->port.lock, flags);}

回到serial8250_startup函数中,完成中断的注册工作后,剩下的就是对硬件的初始化。这部分主要是对LCR和MCR寄存器进行设置,让硬件工作在一个正常的状态。整个设备打开操作的流程是从tty核心层的tty_open到tty驱动层的uart_open,然后再到uart驱动的serial8250_startup,其中还包括的线路规程N_TTY的open操作,这跟整个uart和tty驱动框架是一致的。




0 0
原创粉丝点击