一:前言
接着前面的终端控制台分析,接下来分析serial的驱动.在linux中,serial也对应着终端,通常被称为串口终端. 在shell上,我们看到的/dev/ttyS*就是串口终端所对应的设备节点.在分析具体的serial驱动之前.有必要先 分析uart驱动架构.uart是Universal Asynchronous Receiver and Transmitter的缩写.翻译成中文 即为”通用异步收发器”.它是串口设备驱动的封装层.
二:uart驱动架构概貌
如下图所示:
在它的操作函数中,将操作转入uart_port.在写操作的时候,先将数据放入一个叫做circ_buf的环形缓存 区.然后uart_port从缓存区中取数据,将其写入到串口设备中.当uart_port从serial设备接收到数据时 ,会将设备放入对应line discipline的缓存区中.这样.用户在编写串口驱动的时候,只先要注册一个 uart_driver.它的主要作用是定义设备节点号.然后将对设备的各项操作封装在uart_port.驱动工程师没 必要关心上层的流程,只需按硬件规范将uart_port中的接口函数完成就可以了.
三:uart驱动中重要的数据结构及其关联
我们可以自己考虑下,基于上面的架构代码应该要怎么写.首先考虑以下几点:
1: 一个uart_driver通常会注册一段设备号.即在用户空间会看到uart_driver对应有多个设备节点.例如:
/dev/ttyS0 /dev/ttyS1
每个设备节点是对应一个具体硬件的,从上面的架构来看,每个设备文件应该对应一个uart_port.
也就是说:uart_device怎么同多个uart_port关系起来?怎么去区分操作的是哪一个设备文件?
2:每个uart_port对应一个circ_buf,所以uart_port必须要和这个缓存区关系起来
回忆tty驱动架构中.tty_driver有一个叫成员指向一个数组,即tty->ttys.每个设备文件对应设数组中的 一项.而这个数组所代码的数据结构为tty_struct. 相应的tty_struct会将tty_driver和ldisc关联起来.
那在uart驱动中,是否也可用相同的方式来处理呢?
将uart驱动常用的数据结构表示如下:
之后,注册tty_driver.然后初始化uart_driver->state的存储空间.这样,就会注册uart_driver->nr 个设备节点.主设备号为uart_driver-> major. 开始的次设备号为uart_driver-> minor.值得注意的 是.在这里将tty_driver的操作集统一设为了uart_ops.其次,在tty_driver-> driver_state保存了这 个uart_driver.这样做是为了在用户空间对设备文件的操作时,很容易转到对应的uart_driver.另外: tty_driver的flags成员值为: TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV.里面包含有 TY_DRIVER_DYNAMIC_DEV标志.结合之前对tty的分析.如果包含有这个标志,是不会在初始化的时候去注册 device.也就是说在/dev/下没有动态生成结点(如果是/dev下静态创建了这个结点就另当别论了^_^).
流程图如下:
关联起来的呢?这就是uart_add_ont_port()的主要工作了.顾名思义,这个函数是在uart_driver增加 一个port.代码如下:
int uart_add_one_port(struct uart_driver *drv, struct uart_port *port)
{
struct uart_state *state;
int ret = 0;
struct device *tty_dev;
BUG_ON(in_interrupt());
if (port->line >= drv->nr)
return -EINVAL;
state = drv->state + port->line;
mutex_lock(&port_mutex);
mutex_lock(&state->mutex);
if (state->port) {
ret = -EINVAL;
goto out;
}
state->port = port;
state->pm_state = -1;
port->cons = drv->cons;
port->info = state->info;
/*
* If this port is a console, then the spinlock is already
* initialised.
*/
if (!(uart_console(port) && (port->cons->flags & CON_ENABLED))) {
spin_lock_init(&port->lock);
lockdep_set_class(&port->lock, &port_lock_key);
}
uart_configure_port(drv, state, port);
/*
* Register the port whether it's detected or not. This allows
* setserial to be used to alter this ports parameters.
*/
tty_dev = tty_register_device(drv->tty_driver, port->line, port->dev);
if (likely(!IS_ERR(tty_dev))) {
device_can_wakeup(tty_dev) = 1;
device_set_wakeup_enable(tty_dev, 0);
} else
printk(KERN_ERR "Cannot register tty device on line %d\n",
port->line);
/*
* Ensure UPF_DEAD is not set.
*/
port->flags &= ~UPF_DEAD;
out:
mutex_unlock(&state->mutex);
mutex_unlock(&port_mutex);
return ret;
}
首先这个函数不能在中断环境中使用. Uart_port->line就是对uart设备文件序号.它对应的也就是 uart_driver->state数组中的uart_port->line项.它主要初始化对应uart_driver->state项. 接着调用uart_configure_port()进行port的自动配置.然后注册tty_device.如果用户空间运行了 udev或者已经配置好了hotplug.就会在/dev下自动生成设备文件了.
操作流程图如下所示:
文件进行操作了.那uart_port也要开始工作了.即调用uart_startup()使其进入工作状态.当然,也需 要初始化uart_port所对应的环形缓冲区circ_buf.即state->info-> xmit.特别要注意,在这里将 tty->driver_data = state;这是因为以后的操作只有port相关了,不需要去了解uart_driver的 相关信息.跟踪看一下里面调用的两个重要的子函数. uart_get()和uart_startup().先分析 uart_get().代码如下:
static struct uart_state *uart_get(struct uart_driver *drv, int line)
{
struct uart_state *state;
int ret = 0;
state = drv->state + line;
if (mutex_lock_interruptible(&state->mutex)) {
ret = -ERESTARTSYS;
goto err;
}
state->count++;
if (!state->port || state->port->flags & UPF_DEAD) {
ret = -ENXIO;
goto err_unlock;
}
if (!state->info) {
state->info = kzalloc(sizeof(struct uart_info), GFP_KERNEL);
if (state->info) {
init_waitqueue_head(&state->info->open_wait);
init_waitqueue_head(&state->info->delta_msr_wait);
/*
* Link the info into the other structures.
*/
state->port->info = state->info;
tasklet_init(&state->info->tlet, uart_tasklet_action,
(unsigned long)state);
} else {
ret = -ENOMEM;
goto err_unlock;
}
}
return state;
err_unlock:
state->count--;
mutex_unlock(&state->mutex);
err:
return ERR_PTR(ret);
}
从代码中可以看出.这里注要是操作是初始化state->info.注意port->info就是state->info的一个 副本.即port直接通过port->info可以找到它要操作的缓存区.
uart_startup()代码如下:
static int uart_startup(struct uart_state *state, int init_hw)
{
struct uart_info *info = state->info;
struct uart_port *port = state->port;
unsigned long page;
int retval = 0;
if (info->flags & UIF_INITIALIZED)
return 0;
/*
* Set the TTY IO error marker - we will only clear this
* once we have successfully opened the port. Also set
* up the tty->alt_speed kludge
*/
set_bit(TTY_IO_ERROR, &info->tty->flags);
if (port->type == PORT_UNKNOWN)
return 0;
/*
* Initialise and allocate the transmit and temporary
* buffer.
*/
if (!info->xmit.buf) {
page = get_zeroed_page(GFP_KERNEL);
if (!page)
return -ENOMEM;
info->xmit.buf = (unsigned char *) page;
uart_circ_clear(&info->xmit);
}
retval = port->ops->startup(port);
if (retval == 0) {
if (init_hw) {
/*
* Initialise the hardware port settings.
*/
uart_change_speed(state, NULL);
/*
* Setup the RTS and DTR signals once the
* port is open and ready to respond.
*/
if (info->tty->termios->c_cflag & CBAUD)
uart_set_mctrl(port, TIOCM_RTS | TIOCM_DTR);
}
if (info->flags & UIF_CTS_FLOW) {
spin_lock_irq(&port->lock);
if (!(port->ops->get_mctrl(port) & TIOCM_CTS))
info->tty->hw_stopped = 1;
spin_unlock_irq(&port->lock);
}
info->flags |= UIF_INITIALIZED;
clear_bit(TTY_IO_ERROR, &info->tty->flags);
}
if (retval && capable(CAP_SYS_ADMIN))
retval = 0;
return retval;
}
在这里,注要完成对环形缓冲,即info->xmit的初始化.然后调用port->ops->startup( )将这个port带 入到工作状态.其它的是一个可调参数的设置,就不详细讲解了.
七:设备节点的write操作
Write操作对应的操作接口为uart_write( ).代码如下:
static int
uart_write(struct tty_struct *tty, const unsigned char *buf, int count)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port;
struct circ_buf *circ;
unsigned long flags;
int c, ret = 0;
/*
* This means you called this function _after_ the port was
* closed. No cookie for you.
*/
if (!state || !state->info) {
WARN_ON(1);
return -EL3HLT;
}
port = state->port;
circ = &state->info->xmit;
if (!circ->buf)
return 0;
spin_lock_irqsave(&port->lock, flags);
while (1) {
c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE);
if (count
c = count;
if (c
break;
memcpy(circ->buf + circ->head, buf, c);
circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1);
buf += c;
count -= c;
ret += c;
}
spin_unlock_irqrestore(&port->lock, flags);
uart_start(tty);
return ret;
}
Uart_start()代码如下:
static void uart_start(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port = state->port;
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
__uart_start(tty);
spin_unlock_irqrestore(&port->lock, flags);
}
static void __uart_start(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port = state->port;
if (!uart_circ_empty(&state->info->xmit) && state->info->xmit.buf &&
!tty->stopped && !tty->hw_stopped)
port->ops->start_tx(port);
}
显然,对于write操作而言,它就是将数据copy到环形缓存区.然后调用port->ops->start_tx()将数据写 到硬件寄存器.
八:Read操作
Uart的read操作同Tty的read操作相同,即都是调用ldsic->read()读取read_buf中的内容.有对这部份 内容不太清楚的,参阅http://blog.youkuaiyun.com/xp_super/article/details/8011062 九:小结
本小节是分析serial驱动的基础.在理解了tty驱动架构之后,再来理解uart驱动架构应该不是很难.随着我们 在linux设备驱动分析的深入,越来越深刻的体会到,linux的设备驱动架构很多都是相通的.只要深刻理解了一 种驱动架构.举一反三.也就很容易分析出其它架构的驱动了.
|
uart驱动与tty驱动
最新推荐文章于 2023-11-23 20:51:52 发布