Linux串口驱动程序设计

1. 在Linux系统中,终端是一类字符型设备,它包括多种类型,通常使用tty来简称各种类型的终端设备。

(1)串口终端(/dev/ttyS*):串口终端是使用计算机串口连接的终端设备。Linux把每个串行端口都看作是一个字符设备。这些串行端口所对应的设备名称是/dev/ttySAC0;/dev/ttySAC1……

(2)控制台终端(/dev/console):在Linux系统中,计算机的输出设备通常被称为控制台终端(Console),这里特指printk信息输出到的设备。/dev/console是一个虚拟的设备,它需要映射到真正的tty上,比如通过内核启动参数” console=ttySAC0”就把console映射到了串口0

(3)虚拟终端(/dev/tty*):

 

2. Linux tty子系统包含:tty核心,tty线路规程和tty驱动。

(1)tty核心是对整个tty设备的抽象,对用户提供统一的接口。

(2)tty线路规程是对传输数据的格式化。

(3)tty驱动则是面向tty设备的硬件驱动。

 

3. Linux中Uart框架:

 

  整个 uart 框架大概的样子如上图所示。简单来分的话可以说成两层,一层是下层我们的串口驱动层,它直接与硬件相接触,我们需要填充一个 struct uart_ops 的结构体,另一层是上层 tty 层,包括 tty 核心以及线路规程,它们各自都有一个 Ops 结构,用户空通过间是 tty 注册的字符设备节点来访问,这么说来如上图所示涉及到了4个 ops 结构了,层层跳转。

 

4. 在 s3c2440平台,注册串口驱动的步骤是,分配一个struct uart_driver 简单填充,并调用uart_register_driver 注册到内核中去。uart_driver源码

struct uart_driver {
    struct module        *owner;
    const char        *driver_name;
    const char        *dev_name;
    int             major;
    int             minor;
    int             nr;
    struct console        *cons;

    /*
     * these are private; the low level driver should not
     * touch these; they should be initialised to NULL
     */
    struct uart_state    *state;
    struct tty_driver    *tty_driver;
};

(1) 分配uart_driver结构并简单填充(state和tty_driver将会在uart_register_driver时候赋值)

static struct uart_driver s3c24xx_uart_drv = {
    .owner        = THIS_MODULE,
    .dev_name    = "s3c2410_serial",
    .nr        = CONFIG_SERIAL_SAMSUNG_UARTS,
    .cons        = S3C24XX_SERIAL_CONSOLE,
    .driver_name    = S3C24XX_SERIAL_NAME,
    .major        = S3C24XX_SERIAL_MAJOR,
    .minor        = S3C24XX_SERIAL_MINOR,
};

(2) 调用uart_register_driver主要填充uart_driver的state和tty_deriver域

int uart_register_driver(struct uart_driver *drv)
{
    struct tty_driver *normal = NULL;
    int i, retval;

    BUG_ON(drv->state);

    /*
     * Maybe we should be using a slab cache for this, especially if
     * we have a large number of ports to handle.
     */
    drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
    retval = -ENOMEM;
    if (!drv->state)
        goto out;

    normal  = alloc_tty_driver(drv->nr);
    if (!normal)
        goto out;

    drv->tty_driver = normal;

    normal->owner        = drv->owner;
    normal->driver_name    = drv->driver_name;
    normal->name        = drv->dev_name;
    normal->major        = drv->major;
    normal->minor_start    = drv->minor;
    normal->type        = TTY_DRIVER_TYPE_SERIAL;
    normal->subtype        = SERIAL_TYPE_NORMAL;
    normal->init_termios    = tty_std_termios;
    normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
    normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
    normal->flags        = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
    normal->driver_state    = drv;
    tty_set_operations(normal, &uart_ops);

    /*
     * Initialise the UART state(s).
     */
    for (i = 0; i < drv->nr; i++) {
        struct uart_state *state = drv->state + i;

        state->close_delay     = 500;    /* .5 seconds */
        state->closing_wait    = 30000;    /* 30 seconds */
        mutex_init(&state->mutex);

        tty_port_init(&state->info.port);
        init_waitqueue_head(&state->info.delta_msr_wait);
        tasklet_init(&state->info.tlet, uart_tasklet_action,
                 (unsigned long)state);
    }

    retval = tty_register_driver(normal);
 out:
    if (retval < 0) {
        put_tty_driver(normal);
        kfree(drv->state);
    }
    return retval;
}


5. uart_driver的state域(下层,主要是对uart口的统一封装与描述)

(1)state的类型为struct uart_state,定义如下

struct uart_state {
    unsigned int        close_delay;        /* msec */
    unsigned int        closing_wait;        /* msec */

#define USF_CLOSING_WAIT_INF    (0)
#define USF_CLOSING_WAIT_NONE    (~0U)

    int            count;
    int            pm_state;
    struct uart_info    info;
    struct uart_port    *port;

    struct mutex        mutex;
};

(2)在注册driver时,即在uart_register_driver()函数内,会根据 uart_driver->nr 来申请 nr 个 uart_state 空间,用来存放驱动所支持的串口(端口)的物理信息。在Linux内核中,每一个串口的信息由struct uart_port结构来描述,

struct uart_port {
    spinlock_t        lock;            /* port lock */
    unsigned long        iobase;            /* in/out[bwl] */
    unsigned char __iomem    *membase;        /* read/write[bwl] */
    unsigned int        (*serial_in)(struct uart_port *, int);
    void            (*serial_out)(struct uart_port *, int, int);
    unsigned int        irq;            /* irq number */
    unsigned int        uartclk;        /* base uart clock */
    unsigned int        fifosize;        /* tx fifo size */
    unsigned char        x_char;            /* xon/xoff char */
    unsigned char        regshift;        /* reg offset shift */
    unsigned char        iotype;            /* io access style */
    unsigned char        unused1;

#define UPIO_PORT        (0)
#define UPIO_HUB6        (1)
#define UPIO_MEM        (2)
#define UPIO_MEM32        (3)
#define UPIO_AU            (4)            /* Au1x00 type IO */
#define UPIO_TSI        (5)            /* Tsi108/109 type IO */
#define UPIO_DWAPB        (6)            /* DesignWare APB UART */
#define UPIO_RM9000        (7)            /* RM9000 type IO */

    unsigned int        read_status_mask;    /* driver specific */
    unsigned int        ignore_status_mask;    /* driver specific */
    struct uart_info    *info;            /* pointer to parent info */
    struct uart_icount    icount;            /* statistics */

    struct console        *cons;            /* struct console, if any */
#ifdef CONFIG_SERIAL_CORE_CONSOLE
    unsigned long        sysrq;            /* sysrq timeout */
#endif

    upf_t            flags;

#define UPF_FOURPORT        ((__force upf_t) (1 << 1))
#define UPF_SAK            ((__force upf_t) (1 << 2))
#define UPF_SPD_MASK        ((__force upf_t) (0x1030))
#define UPF_SPD_HI        ((__force upf_t) (0x0010))
#define UPF_SPD_VHI        ((__force upf_t) (0x0020))
#define UPF_SPD_CUST        ((__force upf_t) (0x0030))
#define UPF_SPD_SHI        ((__force upf_t) (0x1000))
#define UPF_SPD_WARP        ((__force upf_t) (0x1010))
#define UPF_SKIP_TEST        ((__force upf_t) (1 << 6))
#define UPF_AUTO_IRQ        ((__force upf_t) (1 << 7))
#define UPF_HARDPPS_CD        ((__force upf_t) (1 << 11))
#define UPF_LOW_LATENCY        ((__force upf_t) (1 << 13))
#define UPF_BUGGY_UART        ((__force upf_t) (1 << 14))
#define UPF_NO_TXEN_TEST    ((__force upf_t) (1 << 15))
#define UPF_MAGIC_MULTIPLIER    ((__force upf_t) (1 << 16))
#define UPF_CONS_FLOW        ((__force upf_t) (1 << 23))
#define UPF_SHARE_IRQ        ((__force upf_t) (1 << 24))
/* The exact UART type is known and should not be probed.  */
#define UPF_FIXED_TYPE        ((__force upf_t) (1 << 27))
#define UPF_BOOT_AUTOCONF    ((__force upf_t) (1 << 28))
#define UPF_FIXED_PORT        ((__force upf_t) (1 << 29))
#define UPF_DEAD        ((__force upf_t) (1 << 30))
#define UPF_IOREMAP        ((__force upf_t) (1 << 31))

#define UPF_CHANGE_MASK        ((__force upf_t) (0x17fff))
#define UPF_USR_MASK        ((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY))

    unsigned int        mctrl;            /* current modem ctrl settings */
    unsigned int        timeout;        /* character-based timeout */
    unsigned int        type;            /* port type */
    const struct uart_ops    *ops;
    unsigned int        custom_divisor;
    unsigned int        line;            /* port index */
    resource_size_t        mapbase;        /* for ioremap */
    struct device        *dev;            /* parent device */
    unsigned char        hub6;            /* this should be in the 8250 driver */
    unsigned char        suspended;
    unsigned char        unused[2];
    void            *private_data;        /* generic platform data pointer */
};

  注:这个结构体,是需要我们自己来填充的,比如我们 s3c2440 有3个串口,那么就需要填充3个 uart_port ,并且通过 uart_add_one_port 添加到 uart_driver->uart_state->uart_port 中去。当然 uart_driver 有多个 uart_state ,每个 uart_state 有一个 uart_port 

(3)在 uart_port 里还有一个非常重要的成员 struct uart_ops *ops ,这个也是需要我们自己来实现的。该结构体描述了针对某一串口的具体操作方法

struct uart_ops {
    unsigned int    (*tx_empty)(struct uart_port *);
    void        (*set_mctrl)(struct uart_port *, unsigned int mctrl);
    unsigned int    (*get_mctrl)(struct uart_port *);
    void        (*stop_tx)(struct uart_port *);
    void        (*start_tx)(struct uart_port *);
    void        (*send_xchar)(struct uart_port *, char ch);
    void        (*stop_rx)(struct uart_port *);
    void        (*enable_ms)(struct uart_port *);
    void        (*break_ctl)(struct uart_port *, int ctl);
    int        (*startup)(struct uart_port *);
    void        (*shutdown)(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 *);
    void        (*pm)(struct uart_port *, unsigned int state,
                  unsigned int oldstate);
    int        (*set_wake)(struct uart_port *, unsigned int state);

    /*
     * Return a string describing the type of the port
     */
    const char *(*type)(struct uart_port *);

    /*
     * Release IO and memory resources used by the port.
     * This includes iounmap if necessary.
     */
    void        (*release_port)(struct uart_port *);

    /*
     * Request IO and memory resources used by the port.
     * This includes iomapping the port if necessary.
     */
    int        (*request_port)(struct uart_port *);
    void        (*config_port)(struct uart_port *, int);
    int        (*verify_port)(struct uart_port *, struct serial_struct *);
    int        (*ioctl)(struct uart_port *, unsigned int, unsigned long);
#ifdef CONFIG_CONSOLE_POLL
    void    (*poll_put_char)(struct uart_port *, unsigned char);
    int        (*poll_get_char)(struct uart_port *);
#endif
};


6.  uart_driver的tty_driver域(上层)

(1)tty_driver 是在注册过程中构建的,即在uart_register_driver()函数中构建起来的。

(2)注册过程即uart_register_driver()所做的工作

  ① 根据driver支持的最大设备数,申请n个 uart_state 空间,每一个 uart_state 都有一个 uart_port 。

  ② 分配一个 tty_driver 结构,并将drv->tty_driver 指向它

  ③ 对 tty_driver 进行设置,其中包括默认波特率、校验方式等

  ④ 初始化每一个 uart_state 的 tasklet

  ⑤ 注册tty_driver

   注: 注册 uart_driver 实际上是注册 tty_driver,因此与用户空间打交道的工作完全交给了 tty_driver ,而且这一部分都是内核实现好的,我们不需要修改,了解一下工作原理即可。

 (3)tty_dr的注册tty_register_driver()

/*
 * Called by a tty driver to register itself.
 */
int tty_register_driver(struct tty_driver *driver)
{
    int error;
    int i;
    dev_t dev;
    void **p = NULL;

    if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM) && driver->num) {
        p = kzalloc(driver->num * 2 * sizeof(void *), GFP_KERNEL);
        if (!p)
            return -ENOMEM;
    }

    if (!driver->major) {
        error = alloc_chrdev_region(&dev, driver->minor_start,
                        driver->num, driver->name);
        if (!error) {
            driver->major = MAJOR(dev);
            driver->minor_start = MINOR(dev);
        }
    } else {
        dev = MKDEV(driver->major, driver->minor_start);
        error = register_chrdev_region(dev, driver->num, driver->name);
    }
    if (error < 0) {
        kfree(p);
        return error;
    }

    if (p) {
        driver->ttys = (struct tty_struct **)p;
        driver->termios = (struct ktermios **)(p + driver->num);
    } else {
        driver->ttys = NULL;
        driver->termios = NULL;
    }

    cdev_init(&driver->cdev, &tty_fops);
    driver->cdev.owner = driver->owner;
    error = cdev_add(&driver->cdev, dev, driver->num);
    if (error) {
        unregister_chrdev_region(dev, driver->num);
        driver->ttys = NULL;
        driver->termios = NULL;
        kfree(p);
        return error;
    }

    mutex_lock(&tty_mutex);
    list_add(&driver->tty_drivers, &tty_drivers);
    mutex_unlock(&tty_mutex);

    if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
        for (i = 0; i < driver->num; i++)
            tty_register_device(driver, i, NULL);
    }
    proc_tty_register_driver(driver);
    driver->flags |= TTY_DRIVER_INSTALLED;
    return 0;
}

  tty_driver注册过程

  ① 为线路规程和termios分配空间,并使 tty_driver 相应的成员指向它们。

  ② 注册字符设备,名字是 uart_driver->name 我们这里是“ttySAC”,文件操作函数集是 tty_fops。

  ③ 将该 uart_driver->tty_drivers 添加到全局链表 tty_drivers

  ④ 向 proc 文件系统添加 driver

 

7. 调用过程分析:tty_driver 注册了一个字符设备,我们以它的 tty_fops 入手,以 open、read、write 为例,分析用户空间如何访问到最底层的硬件操作函数

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,
};

 

(1)tty_open分析

static int tty_open(struct inode *inode, struct file *filp)
{
    int ret;

    lock_kernel();
    ret = __tty_open(inode, filp);
    unlock_kernel();
    return ret;
}

  ① tty_open的核心代码是调用函数__tty_open(),__tty_open()的简略代码如下

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;

    ...    

    //在全局tty_drivers链表中获取Core注册的tty_driver

    driver = get_tty_driver(device, &index);

    tty = tty_init_dev(driver, index, 0);    // tty->ops = driver->ops;

    filp->private_data = tty;

    if (tty->ops->open)
        /* 调用tty_driver->tty_foperation->open */
        retval = tty->ops->open(tty, filp);

    return 0;
}

  从 tty_drivers 全局链表获取到前边我们注册进去的 tty_driver ,然后分配设置一个 struct tty_struct 的结构,最后调用 tty_struct->ops->open 函数,其实 tty_struct->ops == tty_driver->ops  。

  ② tty_init_dev源代码为

struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx,
                                int first_ok)
{
    struct tty_struct *tty;
    int retval;

    /* Check if pty master is being opened multiple times */
    if (driver->subtype == PTY_TYPE_MASTER &&
        (driver->flags & TTY_DRIVER_DEVPTS_MEM) && !first_ok)
        return ERR_PTR(-EIO);

    /*
     * First time open is complex, especially for PTY devices.
     * This code guarantees that either everything succeeds and the
     * TTY is ready for operation, or else the table slots are vacated
     * and the allocated memory released.  (Except that the termios
     * and locked termios may be retained.)
     */

    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);
    }

    /*
     * Structures all installed ... call the ldisc open routines.
     * If we fail here just call release_tty to clean up.  No need
     * to decrement the use counts, as release_tty doesn't care.
     */

    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);
    release_tty(tty, idx);
    return ERR_PTR(retval);
}

  initialize_tty_struct():用tty_driver来初始化tty_struct结构

  tty_ldisc_setup():调用线路规程中的open函数

(2)tty_open总结

  ① 获取tty_driver

  ② 根据tty_driver初始化一个tty_struct结构

    * 设置 tty_struct 的线路规程为 N_TTY (不同类型的线路规程有不同的 ops)

    * 初始化一个延时工作队列,唤醒时调用flush_to_ldisc ,读函数时我们需要分析它

    * 初始化 tty_struct 里的两个等待队列头

    * 设置 tty_struct->ops == tty_driver->ops

  ③ 在 tty_ldisc_setup 函数中调用到线路规程的open函数

  ④ 如果 tty_struct->ops 也就是 tty_driver->ops 定义了 open 函数则调用,显然是有的 uart_open

 

posted @ 2018-08-29 15:38  99度的水  阅读(745)  评论(0编辑  收藏  举报