本文主要是介绍Linux3.0内核sc32440串口驱动分析(二)——打开读写操作分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
三、串口驱动操作函数
[wuyujun@wuyujunlocalhost linux-3.0]$ grep -n tty_fops -r ./
匹配到二进制文件 ./.tmp_vmlinux1
匹配到二进制文件 ./vmlinux
./drivers/tty/tty_io.c:451:static const struct file_operations tty_fops = {
./drivers/tty/tty_io.c:475:static const struct file_operations hung_up_tty_fops = {
......
./drivers/tty/tty_io.c:3312: cdev_init(&tty_cdev, &tty_fops);
......
匹配到二进制文件 ./drivers/built-in.o
匹配到二进制文件 ./.tmp_vmlinux2
在tty_register_driver()里cdev_init(&tty_cdev, &tty_fops);将tty_fops文件操作结构体与tty_cdev绑定;tty_fops结构体定义在./drivers/tty/tty_io.c的451行
static const struct file_operations tty_fops = {
.llseek = no_llseek,
.read = tty_read,
.write = tty_write,
.poll = tty_poll,
.unlocked_ioctl = tty_ioctl,
.compat_ioctl = tty_compat_ioctl,
.open = tty_open,
.release = tty_release,
.fasync = tty_fasync,
};
主要看看tty_open(),tty_read(),tty_write(),几个文件操作函数,当用户空间调用open()、read()、write()就会调用到这几个函数
1、open()打开操作
Tty_open同样也在./drivers/tty/tty_io.c里
static int tty_open(struct inode *inode, struct file *filp)
{
struct tty_struct *tty = NULL;
int noctty, retval;
struct tty_driver *driver;
int index;
dev_t device = inode->i_rdev;
unsigned saved_flags = filp->f_flags;
nonseekable_open(inode, filp);
.......
driver = get_tty_driver(device, &index);//在tty_driver链表中查找该device对应的驱动,并返回在该driver中的index
......
tty = tty_init_dev(driver, index, 0); //初始化tty_struct,返回赋值给struct tty_struct *tty,建立tty_struct与tty_driver之间的关联
......
if (tty->ops->open)
retval = tty->ops->open(tty, filp);//调用tty_struct.ops.open()
else
retval = -ENODEV;
......
return 0;
}
tty_init_dev()利用tty_driver初始化tty_struct,在初始化的时候tty_driver的所有tty_operations赋值给了tty_struct的tty_operations变量,tty_open -> tty_init_dev -> initialize_tty_struct,initialize_tty_struct()
void initialize_tty_struct(struct tty_struct *tty,
struct tty_driver *driver, int idx)
{
/* 设置线路规程为 N_TTY */
tty_ldisc_init(tty);//struct tty_ldisc *ld = tty_ldisc_get(N_TTY);tty_ldisc_assign(tty, ld);
...
tty_buffer_init(tty);
...
tty->ops = driver->ops;
...
}
tty_ldisc_init(tty);和tty_buffer_init()里会涉及到tty线路规程,初始化一个延时工作队列,唤醒时调用flush_to_ldisc,在 tty_ldisc_setup 函数中调用到线路规程的open函数,对于 N_TTY 来说是 n_tty_open,对线路规程如何“格式化数据”设置 ,open()就不分析线路规程部分,到read()和write()再去说。
到最后tty->ops = driver->ops;因此下面的tty->ops->open(tty, flip)事实上是调用了Core层注册的tty_driver的 int (*open)(struct tty_struct * tty, struct file * filp)函数,那这个结构体在哪被注册上呢,就要看到之前的uart_register_driver串口注册驱动里了, tty_set_operations(normal, &uart_ops);// struct tty_driver *normal,设置驱动中定义的操作函数,操作函数在uart_ops中定义。最终可以知道调用tty_struct.ops.open(),tty_struct.ops经过tty_init_dev()来自于tty_driver.ops,而tty_driver.ops在uart_register_driver()通过tty_set_operations(normal, &uart_ops);设置,所以最后是调用了uart_open()
Uart_open()在./drivers/tty/serial/serial_core.c里
static int uart_open(struct tty_struct *tty, struct file *filp)
{
struct uart_driver *drv = (struct uart_driver *)tty->driver->driver_state;
struct uart_state *state;
struct tty_port *port;
int retval, line = tty->index;
.....
state = uart_get(drv, line);
if (IS_ERR(state)) {
retval = PTR_ERR(state);
goto fail;
}
port = &state->port;
tty->driver_data = state;
state->uart_port->state = state;
tty->low_latency = (state->uart_port->flags & UPF_LOW_LATENCY) ? 1 : 0;
tty->alt_speed = 0;
tty_port_tty_set(port, tty);
......
retval = uart_startup(tty, state, 0);
......
if (retval == 0)
retval = tty_port_block_til_ready(port, tty, filp);
fail:
return retval;
}
看到这里这些变量名就已经很熟悉了uart_state、uart_port有关串口的物理信息的结构体,根据 tty_struct 获取到 uart_driver ,再由 uart_driver 获取到里面uart_state->uart_port->ops->startup 调用它。
static int uart_startup(struct tty_struct *tty, struct uart_state *state, int init_hw)
{
struct uart_port *uport = state->uart_port;
struct tty_port *port = &state->port;
unsigned long page;
int retval = 0;
.....
if (port->flags & ASYNC_CTS_FLOW) {
spin_lock_irq(&uport->lock); //自旋锁中断
if (!(uport->ops->get_mctrl(uport) & TIOCM_CTS))
tty->hw_stopped = 1;
spin_unlock_irq(&uport->lock);
}
set_bit(ASYNCB_INITIALIZED, &port->flags);
clear_bit(TTY_IO_ERROR, &tty->flags);
}
if (retval && capable(CAP_SYS_ADMIN))
retval = 0;
return retval;
}
uart_startup()完成了硬件初始化工作,并申请了中断处理例程.硬件此时只是打开了接收终端,到这里open()就结束了
2、write()写操作
同样的从tty_write看起,在./drivers/tty/tty_io.c里
static ssize_t tty_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
struct inode *inode = file->f_path.dentry->d_inode;
struct tty_struct *tty = file_tty(file);
struct tty_ldisc *ld;
ssize_t ret;
if (tty_paranoia_check(tty, inode, "tty_write"))
return -EIO;
if (!tty || !tty->ops->write ||
(test_bit(TTY_IO_ERROR, &tty->flags)))
return -EIO;
/* Short term debug to catch buggy drivers */
if (tty->ops->write_room == NULL)
printk(KERN_ERR "tty driver %s lacks a write_room method.\n",
tty->driver->name);
ld = tty_ldisc_ref_wait(tty); //获取线路规程
if (!ld->ops->write)
ret = -EIO;
else
ret = do_tty_write(ld->ops->write, tty, file, buf, count); //ld->ops->write实际上是调用到调用线路规程 n_tty_write 函数
tty_ldisc_deref(ld);
return ret;
}
接下来看线路规程的n_tty_write函数
static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,
const unsigned char *buf, size_t nr)
{
const unsigned char *b = buf;
DECLARE_WAITQUEUE(wait, current);
int c;
ssize_t retval = 0;
/* Job control check -- must be done at start (POSIX.1 7.1.1.4). */
if (L_TOSTOP(tty) && file->f_op->write != redirected_tty_write) {
retval = tty_check_change(tty);
if (retval)
return retval;
}
/* Write out any echoed characters that are still pending */
process_echoes(tty);
add_wait_queue(&tty->write_wait, &wait); // 添加到等待队列
while (1) {
set_current_state(TASK_INTERRUPTIBLE); //设置成可中断
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) {
retval = -EIO;
break;
}
if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) {
while (nr > 0) {
ssize_t num = process_output_block(tty, b, nr);
if (num < 0) {
if (num == -EAGAIN)
break;
retval = num;
goto break_out;
}
b += num;
nr -= num;
if (nr == 0)
break;
c = *b;
if (process_output(c, tty) < 0)
break;
b++; nr--;
}
if (tty->ops->flush_chars)
tty->ops->flush_chars(tty);
} else {
while (nr > 0) {
c = tty->ops->write(tty, b, nr); // tty->ops->write 也就是 uart_write
if (c < 0) {
retval = c;
goto break_out;
}
if (!c)
break;
b += c;
nr -= c;
}
}
if (!nr)
break;
if (file->f_flags & O_NONBLOCK) {
retval = -EAGAIN;
break;
}
schedule();
}
break_out:
__set_current_state(TASK_RUNNING);
remove_wait_queue(&tty->write_wait, &wait);
if (b - buf != nr && tty->fasync)
set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
return (b - buf) ? b - buf : retval;
}
n_tty_write 调用 tty->ops->write 也就是 uart_write
static int uart_write(struct tty_struct *tty, const unsigned char *buf, int count)
{
uart_start(tty);
return ret;
}
static void uart_start(struct tty_struct *tty)
{
__uart_start(tty);
}
static void __uart_start(struct tty_struct *tty)
{
struct uart_state *state = tty->driver_data;
struct uart_port *port = state->uart_port;
if (!uart_circ_empty(&state->xmit) && state->xmit.buf &&
!tty->stopped && !tty->hw_stopped)
/* 调用到最底层的 start_tx */
port->ops->start_tx(port);
}
最终会调用到s3c24xx_serial_start_tx函数
static void s3c24xx_serial_start_tx(struct uart_port *port)
{
struct s3c24xx_uart_port *ourport = to_ourport(port);
static int a =1;//temp
......
if (!tx_enabled(port)) {
if (port->flags & UPF_CONS_FLOW)
s3c24xx_serial_rx_disable(port);
enable_irq(ourport->tx_irq);
tx_enabled(port) = 1;
}
}
使能发送并没有真正的发送过程,而只是使能发送中断,在中断处理里用户从write系统调用传下来的数据就会写入这个UTXH0寄存器。发送完事之后处理器会产生一个内部中断。
下面才是发送中断的ISR(Interrupt Service Routine)中断服务例程。一个irqreturn_t类型的函数。
static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)
{
struct s3c24xx_uart_port *ourport = id;
struct uart_port *port = &ourport->port;
struct circ_buf *xmit = &port->state->xmit;
int count = 256;
if (port->x_char) { //判断x_char是否为0,如果不为0则发送x_char。
wr_regb(port, S3C2410_UTXH, port->x_char); //往特定寄存器写的过程
port->icount.tx++;
port->x_char = 0;
goto out;
}
/* if there isn't anything more to transmit, or the uart is now
* stopped, disable the uart and exit
*/
if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { //如果循环发送缓冲区为空或者驱动被设置为停止发送,则取消发送
s3c24xx_serial_stop_tx(port);
goto out;
}
/* try and drain the buffer... */
while (!uart_circ_empty(xmit) && count-- > 0) { //循环发送缓冲不为空
if (rd_regl(port, S3C2410_UFSTAT) & ourport->info->tx_fifofull) //如果读寄存器fifo队列满了,退出
break;
wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]); //写入寄存器
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); //更新循环缓冲尾部位置
port->icount.tx++;
}
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) //如果循环缓冲发送剩余量小于WAKEUP_CHARS,则唤醒之前设置的等待队列
uart_write_wakeup(port);
if (uart_circ_empty(xmit)) //如果循环缓冲区为空,则停止发送
s3c24xx_serial_stop_tx(port);
out:
return IRQ_HANDLED;
}
到这里写操作也分析完了
3、read()读操作
一样的从tty_read函数开始看
static ssize_t tty_read(struct file *file, char __user *buf, size_t count,
loff_t *ppos)
{
int i;
struct inode *inode = file->f_path.dentry->d_inode;
struct tty_struct *tty = file_tty(file);
struct tty_ldisc *ld;
if (tty_paranoia_check(tty, inode, "tty_read"))
return -EIO;
if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags)))
return -EIO;
/* We want to wait for the line discipline to sort out in this
situation */
ld = tty_ldisc_ref_wait(tty); //获取线路规程
if (ld->ops->read)
i = (ld->ops->read)(tty, file, buf, count); //ld->ops->read实际上是调用到调用线路规程 n_tty_read函数
else
i = -EIO;
tty_ldisc_deref(ld);
if (i > 0)
inode->i_atime = current_fs_time(inode->i_sb);
return i;
}
ld->ops->read实际上是调用到调用线路规程 n_tty_read函数,n_tty_read()在./drivers/tty/n_tty.c里
static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,
unsigned char __user *buf, size_t nr)
{
unsigned char __user *b = buf;
DECLARE_WAITQUEUE(wait, current); //等待队列
int c;
int minimum, time;
ssize_t retval = 0;
ssize_t size;
long timeout;
unsigned long flags;
int packet;
do_it_again:
.......
add_wait_queue(&tty->read_wait, &wait);//添加到等待队列
if (((minimum - (b - buf)) < tty->minimum_to_wake) &&
((minimum - (b - buf)) >= 1))
tty->minimum_to_wake = (minimum - (b - buf));
......
if (!input_available_p(tty, 0)) { //无数据可读
if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {
retval = -EIO;
break;
}
if (tty_hung_up_p(file))
break;
if (!timeout)
break;
if (file->f_flags & O_NONBLOCK) { //无数据可读且非阻塞模式,直接返回
retval = -EAGAIN;
break;
}
if (signal_pending(current)) { //因为信号唤醒,不是因为有数据可读被唤醒也直接返回
retval = -ERESTARTSYS;
break;
}
/* FIXME: does n_tty_set_room need locking ? */
n_tty_set_room(tty);
timeout = schedule_timeout(timeout);
BUG_ON(!tty->read_buf);
continue;
}
__set_current_state(TASK_RUNNING);
.......
if (tty->icanon && !L_EXTPROC(tty)) { //标准模式
/* N.B. avoid overrun if nr == 0 */
while (nr && tty->read_cnt) {
int eol;
eol = test_and_clear_bit(tty->read_tail,
tty->read_flags);
c = tty->read_buf[tty->read_tail];
spin_lock_irqsave(&tty->read_lock, flags);
tty->read_tail = ((tty->read_tail+1) &
(N_TTY_BUF_SIZE-1));
tty->read_cnt--;
.......
if (!eol || (c != __DISABLED_CHAR)) {
if (tty_put_user(tty, c, b++)) { //把字符拷贝到用户空间
retval = -EFAULT;
b--;
break;
}
nr--;
}
if (eol) {
tty_audit_push(tty);
break;
}
}
if (retval)
break;
} else {
.....
/* 非标准模式 */
}
}
.......
mutex_unlock(&tty->atomic_read_lock);
remove_wait_queue(&tty->read_wait, &wait); //删除等待队列
......
n_tty_set_room(tty);
return retval;
}
static inline int tty_put_user(struct tty_struct *tty, unsigned char x,
unsigned char __user *ptr)
{
tty_audit_add_data(tty, &x, 1);
return put_user(x, ptr); //拷贝字符到用户空间
}
主要用到了等待队列,先查询数据是否可用,如果不可用这让出CPU,等待其他线程来(即中断中间接调用flush_to_ldisc)唤醒。当硬件接收到数据后,会将数据传到ldata.read_buf[]中,然后唤醒改线程,之后该线程就会被唤醒调用tty_put_user完成数据从ldisc(ldata.read_buf[])层到应用缓存区中的拷贝。
具体实现上中断分两步:
(1)通过tty_insert_flip_char(),将RX-FIFO中的数据保存在struct tty_buffer中。
(2)通过tty_flip_buffer_push()来唤醒队列,即flush_to_ldisc()。将数据从struct tty_buffer传到ldata.read_buf[]缓冲区中。
tty_insert_flip_char()在./include/linux/tty_flip.h里
static inline int tty_insert_flip_char(struct tty_struct *tty,
unsigned char ch, char flag)
{
struct tty_buffer *tb = tty->buf.tail;
if (tb && tb->used < tb->size) { //当空间充足时,将数据拷贝到tty_buffer中去
tb->flag_buf_ptr[tb->used] = flag;
tb->char_buf_ptr[tb->used++] = ch;
return 1;
}
return tty_insert_flip_string_flags(tty, &ch, &flag, 1); //空间不足时
}
可以在linux-3.0/include/linux/tty.h找到有关这些结构体的定义
/* Each of a tty's open files has private_data pointing to tty_file_private */
struct tty_file_private {
struct tty_struct *tty;
struct file *file;
struct list_head list;
};
struct tty_struct {
int magic;
struct kref kref;
struct device *dev;
struct tty_driver *driver;
const struct tty_operations *ops;
int index;
/* Protects ldisc changes: Lock tty not pty */
struct mutex ldisc_mutex;
struct tty_ldisc *ldisc;
......
struct tty_bufhead buf; /* Locked internally */
int alt_speed; /* For magic substitution of 38400 bps */
wait_queue_head_t write_wait;
....
struct tty_port *port;
};
再看里面的struct tty_bufhead这个作为临时数据缓冲区的结构体
struct tty_bufhead {
struct work_struct work;
spinlock_t lock;
struct tty_buffer *head; /* Queue head */
struct tty_buffer *tail; /* Active buffer */
struct tty_buffer *free; /* Free queue head */
int memory_used; /* Buffer space used excluding
free queue */
};
struct tty_buffer {
struct tty_buffer *next;
char *char_buf_ptr;
unsigned char *flag_buf_ptr;
int used;
int size;
int commit;
int read;
/* Data points here */
unsigned long data[0];
};
struct tty_bufhead和struct tty_buffer共同承担数据临时保存的任务
tty_flip_buffer_push()它在linux-3.0/drivers/tty/tty_buffer.c
void tty_flip_buffer_push(struct tty_struct *tty)
{
unsigned long flags;
spin_lock_irqsave(&tty->buf.lock, flags);
if (tty->buf.tail != NULL)
tty->buf.tail->commit = tty->buf.tail->used;
spin_unlock_irqrestore(&tty->buf.lock, flags);
if (tty->low_latency)
flush_to_ldisc(&tty->buf.work); //调用flush_to_ldisc来唤醒队列将数据从struct tty_buffer传到ldata.read_buf[]缓冲区中。
else
schedule_work(&tty->buf.work); //调度执行工作队列
}
最后看flush_to_ldisc();在linux-3.0/drivers/tty/tty_buffer.c里
static void flush_to_ldisc(struct work_struct *work)
{
struct tty_struct *tty =
container_of(work, struct tty_struct, buf.work);
unsigned long flags;
struct tty_ldisc *disc;
......
if (test_bit(TTY_FLUSHPENDING, &tty->flags)) {
__tty_buffer_flush(tty);
clear_bit(TTY_FLUSHPENDING, &tty->flags);
wake_up(&tty->read_wait); //唤醒读操作的等待队列
}
spin_unlock_irqrestore(&tty->buf.lock, flags);
tty_ldisc_deref(disc);
}
在由用户空间发起读操作的时,线程可能会阻塞在add_wait_queue()-->wait_woken()-->TASK_INTERRUPTIBLE()中,等待数据区可读;另一边当中断接收到数据后,先保存到tty_buffer数据结构中,然后通过工作队列tty_bufhead.work(对应flush_to_ldisc),将数据传到ldisc层(ldata.read_buf[]),同时唤醒线程来读取数据。摘至:http://www.wowotech.net/tty_framework/435.html
串口驱动太复杂了...分析完也只懂个大概的流程,有机会再深入研究深刻理解
参考:https://blog.csdn.net/lizuobin2/article/details/51773305
http://www.wowotech.net/tty_framework/435.html
这篇关于Linux3.0内核sc32440串口驱动分析(二)——打开读写操作分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!