Chinaunix首页 | 论坛 | 博客
  • 博客访问: 121057
  • 博文数量: 31
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2015-08-31 12:34
文章分类

全部博文(31)

文章存档

2017年(5)

2016年(25)

2015年(1)

我的朋友

分类: LINUX

2016-05-27 14:18:08

原文地址:内核uart驱动分析 作者:alloysystem

串口驱动分析

首先调用uart_register_driver函数,在内核中注册一个字符设备驱动。这部分内容在前一篇文章中有比较详细的叙述。

下图比较全面的展示了,uart_register_driver函数执行成功之后,在内核中形成的数据结构。

 

 


将串口驱动向内核注册成功后,再调用uart_add_one_port函数,给该驱动添加串口设备的端口。该函数将uart_port数据结构挂接到uart_state.port位置。

struct tty_driver 

    int    magic;        /* magic number for this structure */ 
    struct kref kref;    /* Reference management */ 
    struct cdev cdev; 
    struct module    *owner; 
    const char    *driver_name; 
    const char    *name; 
    int    name_base;    /* offset of printed name */ 
    int    major;        /* major device number */ 
    int    minor_start;    /* start of minor device number */ 
    int    minor_num;    /* number of *possible* devices */ 
    int    num;        /* number of devices allocated */ 
    short    type;        /* type of tty driver */ 
    short    subtype;    /* subtype of tty driver */ 
    struct ktermios init_termios; /* Initial termios */ 
    int    flags;        /* tty driver flags */ 
    struct proc_dir_entry *proc_entry; /* /proc fs entry */ 
    struct tty_driver *other; /* only used for the PTY driver */ 
 
    /* 
     * Pointer to the tty data structures 
     */ 
    struct tty_struct **ttys; 
    struct ktermios **termios; 
    struct ktermios **termios_locked; 
    void *driver_state; 
 
    /* 
     * Driver methods 
     */ 
 
    const struct tty_operations *ops; 
    struct list_head tty_drivers; 
}; 

 

下面是8250串口驱动程序初始化函数,这个函数主要完成串口驱动程序中相应的数据结构的初始化工作,得到如图1中所示的数据结构。

static int __init serial8250_init(void

    int ret; 
 
    if (nr_uarts > UART_NR) 
        nr_uarts = UART_NR; 
 
    printk(KERN_INFO "Serial: 8250/16550 driver, " 
           "%d ports, IRQ sharing %sabled\n", nr_uarts, 
           share_irqs ? "en" : "dis"); 
 
#ifdef CONFIG_SPARC 
    ret = sunserial_register_minors(&serial8250_reg, UART_NR); 
#else 
    serial8250_reg.nr = UART_NR; 
    ret = uart_register_driver(&serial8250_reg); 
#endif 
    if (ret) 
        goto out; 
 
    serial8250_isa_devs = platform_device_alloc("serial8250"
                          PLAT8250_DEV_LEGACY); 
    if (!serial8250_isa_devs) 
    { 
        ret = -ENOMEM; 
        goto unreg_uart_drv; 
    } 
 
    ret = platform_device_add(serial8250_isa_devs); 
    if (ret) 
        goto put_dev; 
 
    serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev); 
 
    ret = platform_driver_register(&serial8250_isa_driver); 
    if (ret == 0
        goto out; 
 
    platform_device_del(serial8250_isa_devs); 
put_dev: 
    platform_device_put(serial8250_isa_devs); 
unreg_uart_drv: 
#ifdef CONFIG_SPARC 
    sunserial_unregister_minors(&serial8250_reg, UART_NR); 
#else 
    uart_unregister_driver(&serial8250_reg); 
#endif 
out: 
    return ret; 

 

串口设备的打开过程

打开串口设备的过程首先会调用tty_open函数,然后调用uart_open函数,之后完成串口硬件设备的初始化,并注册中断处理函数。

int tty_register_driver(struct tty_driver *driver) 

    ... 
    cdev_init(&driver->cdev, &tty_fops); 
    driver->cdev.owner = driver->owner; 
    error = cdev_add(&driver->cdev, dev, driver->num); 
    ... 

 

从串口读取数据的过程

从串口读取数据的操作首先会调用到tty_read,然后调用n_tty_read函数。n_tty_read函数是读取数据的关键,这个函数也是我们了解进程休眠和调度的一个好的例子。函数检查目前是不是有字符数据可以读取,如果没有就会休眠,进程被调度出去。直到进程被信号量打断,或者被唤醒。如果有数据读取,则将数据拷贝到用户空间。这个逻辑还是很简单的。

 /** 
 *    n_tty_read        -    read function for tty 
 *    @tty: tty device 
 *    @file: file object 
 *    @buf: userspace buffer pointer 
 *    @nr: size of I/O 
 * 
 *    Perform reads for the line discipline. We are guaranteed that the 
 *    line discipline will not be closed under us but we may get multiple 
 *    parallel readers and must handle this ourselves. We may also get 
 *    a hangup. Always called in user context, may sleep. 
 * 
 *    This code must be sure never to sleep through a hangup. 
 */ 
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: 
 
    BUG_ON(!tty->read_buf); 
 
    c = job_control(tty, file); 
    if (c < 0
        return c; 
 
    minimum = time = 0
    timeout = MAX_SCHEDULE_TIMEOUT; 
    if (!tty->icanon) 
    { 
        time = (HZ / 10) * TIME_CHAR(tty); 
        minimum = MIN_CHAR(tty); 
        if (minimum) 
        { 
            if (time) 
                tty->minimum_to_wake = 1
            else if (!waitqueue_active(&tty->read_wait) || 
                     (tty->minimum_to_wake > minimum)) 
                tty->minimum_to_wake = minimum; 
        } 
        else 
        { 
            timeout = 0
            if (time) 
            { 
                timeout = time; 
                time = 0
            } 
            tty->minimum_to_wake = minimum = 1
        } 
    } 
 
    /* 
     *    Internal serialization of reads. 
     */ 
    if (file->f_flags & O_NONBLOCK) 
    { 
        if (!mutex_trylock(&tty->atomic_read_lock)) 
            return -EAGAIN; 
    } 
    else 
    { 
        /*根据应用态配置的是否阻塞,在加锁的时候采用不同的策略,本分支会 
         引起进程休眠*/ 
        if (mutex_lock_interruptible(&tty->atomic_read_lock)) 
            return -ERESTARTSYS; 
    } 
    packet = tty->packet; 
 
    /*首先将进程放入到等待队列,后面可能会重新调度*/ 
    add_wait_queue(&tty->read_wait, &wait); 
    while (nr) 
    { 
        /* First test for status change. */ 
        if (packet && tty->link->ctrl_status) 
        { 
            unsigned char cs; 
            if (b != buf) 
                break
            spin_lock_irqsave(&tty->link->ctrl_lock, flags); 
            cs = tty->link->ctrl_status; 
            tty->link->ctrl_status = 0
            spin_unlock_irqrestore(&tty->link->ctrl_lock, flags); 
            if (tty_put_user(tty, cs, b++)) 
            { 
                retval = -EFAULT; 
                b--; 
                break
            } 
            nr--; 
            break
        } 
        /* This statement must be first before checking for input 
           so that any interrupt will set the state back to 
           TASK_RUNNING. */ 
        set_current_state(TASK_INTERRUPTIBLE); 
 
        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
            } 
            /*没有可读的数据,暂时将进程休眠,timeout时间会被唤醒*/ 
            /* FIXME: does n_tty_set_room need locking ? */ 
            n_tty_set_room(tty); 
            timeout = schedule_timeout(timeout); 
            continue
        } 
        __set_current_state(TASK_RUNNING); 
 
        /* Deal with packet mode. */ 
        if (packet && b == buf) 
        { 
            if (tty_put_user(tty, TIOCPKT_DATA, b++)) 
            { 
                retval = -EFAULT; 
                b--; 
                break
            } 
            nr--; 
        } 
 
        if (tty->icanon) 
        { 
            /* 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) 
                { 
                    /* this test should be redundant: 
                     * we shouldn't be reading data if 
                     * canon_data is 0 
                     */ 
                    if (--tty->canon_data < 0
                        tty->canon_data = 0
                } 
                spin_unlock_irqrestore(&tty->read_lock, flags); 
 
                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 
        { 
            int uncopied; 
            /* The copy function takes the read lock and handles 
               locking internally for this case */ 
            uncopied = copy_from_read_buf(tty, &b, &nr); 
            uncopied += copy_from_read_buf(tty, &b, &nr); 
            if (uncopied) 
            { 
                retval = -EFAULT; 
                break
            } 
        } 
        /* If there is enough space in the read buffer now, let the 
         * low-level driver know. We use n_tty_chars_in_buffer() to 
         * check the buffer, as it now knows about canonical mode. 
         * Otherwise, if the driver is throttled and the line is 
         * longer than TTY_THRESHOLD_UNTHROTTLE in canonical mode, 
         * we won't get any more characters. 
         */ 
        if (n_tty_chars_in_buffer(tty) <= TTY_THRESHOLD_UNTHROTTLE) 
        { 
            n_tty_set_room(tty); 
            check_unthrottle(tty); 
        } 
 
        if (b - buf >= minimum) 
            break
        if (time) 
            timeout = time; 
    } 
    mutex_unlock(&tty->atomic_read_lock); 
    remove_wait_queue(&tty->read_wait, &wait); 
 
    if (!waitqueue_active(&tty->read_wait)) 
        tty->minimum_to_wake = minimum; 
 
    __set_current_state(TASK_RUNNING); 
    size = b - buf; 
    if (size) 
    { 
        retval = size; 
        if (nr) 
            clear_bit(TTY_PUSH, &tty->flags); 
    } 
    else if (test_and_clear_bit(TTY_PUSH, &tty->flags)) 
        goto do_it_again; 


    n_tty_set_room(tty); 
    return retval; 

 

串口驱动的发送流程分析

首先会调用do_tty_write函数。这个函数就是分配了一个write_buf的缓冲区,然后逐段的拷贝需要发送的数据到缓冲哦区来,再调用N_tty_write进行发送,没拷贝发送一段数据会调用cond_resched函数,这样可能会导致进程进入休眠N_tty_write函数的分析见下面。

/* 
 * Split writes up in sane blocksizes to avoid 
 * denial-of-service type attacks 
 */ 
/*这个函数就是分配了一个write_buf的缓冲区,然后逐段的拷贝需要发送的数据 
到缓冲哦区来,再调用N_tty_write进行发送,没拷贝发送一段数据会调用 
cond_resched函数,这样可能会导致进程进入休眠*/ 
static inline ssize_t do_tty_write( 
    ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t), 
    struct tty_struct *tty, 
    struct file *file, 
    const char __user *buf, 
    size_t count) 

    ssize_t ret, written = 0
    unsigned int chunk; 
 
    ret = tty_write_lock(tty, file->f_flags & O_NDELAY); 
    if (ret < 0
        return ret; 
 
    chunk = 2048
    if (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags)) 
        chunk = 65536
    if (count < chunk) 
        chunk = count; 
 
    /* write_buf/write_cnt is protected by the atomic_write_lock mutex */ 
    if (tty->write_cnt < chunk) 
    { 
        unsigned char *buf_chunk; 
 
        if (chunk < 1024
            chunk = 1024
 
        buf_chunk = kmalloc(chunk, GFP_KERNEL); 
        if (!buf_chunk) 
        { 
            ret = -ENOMEM; 
            goto out; 
        } 
        kfree(tty->write_buf); 
        tty->write_cnt = chunk; 
        tty->write_buf = buf_chunk; 
    } 
 
    /* Do the write .. */ 
    for (;;) 
    { 
        size_t size = count; 
        if (size > chunk) 
            size = chunk; 
        ret = -EFAULT; 
        if (copy_from_user(tty->write_buf, buf, size)) 
            break
        ret = write(tty, file, tty->write_buf, size); 
        if (ret <= 0
            break
        written += ret; 
        buf += ret; 
        count -= ret; 
        if (!count) 
            break
        ret = -ERESTARTSYS; 
        if (signal_pending(current)) 
            break
        cond_resched(); 
    } 
    if (written) 
    { 
        struct inode *inode = file->f_path.dentry->d_inode; 
        inode->i_mtime = current_fs_time(inode->i_sb); 
        ret = written; 
    } 
out: 
    tty_write_unlock(tty); 
    return ret; 

n_tty_write这个函数的位置在线路规程部分,它的作用就是调用底层serial core层的写函数进行数据输出,将所有缓冲数据写完后返回,在写的过程中发生硬件队列满的情况就休眠等待

/*这个函数的位置在线路规程部分,它的作用就是调用底层serial core层的写函数 
进行数据输出,将所有缓冲数据写完后返回,在写的过程中发生硬件队列满的情况 
就休眠等待*/ 
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); 
                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; 

 

上面的处理都是在tty层面的一些处理。N_tty_write函数最后还是要调用到uart_wrtie函数,这个函数则降到了驱动处理层面。这个函数比较简单,就是将传入buf中的数据拷贝到硬件的环形发送缓冲队列上面,然后调用硬件的发送操作。

uart_wrtie函数最后会调用硬件的发送函数,将数据写入到硬件的FIFO缓冲队列中去。

static void transmit_chars(struct uart_8250_port *up) 

    ... 
    count = up->tx_loadsz; 
    do 
    { 
        serial_out(up, UART_TX, xmit->buf[xmit->tail]); 
        xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); 
        up->port.icount.tx++; 
        if (uart_circ_empty(xmit)) 
            break
    } 
    while (--count > 0); 
 
    if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) 
        uart_write_wakeup(&up->port); 
    ... 

 

 

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
 
    if (!state) 
    { 
        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) 
            c = count; 
        if (c <= 0
            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; 

 

 


阅读(1661) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~