----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------

一、UART驱动框架

UART设备驱动框架如下图所示,下图是来自其他博客,仅供参考:

上图展示了四种注册UART设备驱动的方式,分别为case A到case D,这张图主要由4个部分组成:

  • character device:这里指的就是tty字符设备节点,比如/dev/ttyN;
  • tty_register_device:用于注册tty字符设备;
  • tty_driver:tty驱动的核心结构体,与之关联的数据结构有tty_struct、、tty_port、tty_operations、tty_ldisc、tty_ldisc_ops等,通过tty_register_driver将tty_driver注册到内核;
  • uart_driver:uart驱动的核心结构体,也是UART驱动需要实现的部分,与之关联的数据结构有uart_state、uart_port、uart_ops等,通过uart_register_driver将uart_driver注册到内核即可;

二、UART核心数据结构

学习uart驱动,首先要了解驱动框架涉及到的数据结构,知道每个数据结构以及成员的含义之后,再去看源码就容易了。

2.1 struct uart_driver

每一款SoC的UART都需要去实现并定义个属于它自己uart_driver结构,uart_driver包含了串口设备名,串口驱动名,主次设备号,串口控制台(可选))等信息,还封装了tty_driver,定义在include/linux/serial_core.h:

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

其中部分参数含义如下:

  • owner:这个驱动的模块拥有者;
  • driver_name:串口驱动的名称;
  • dev_name:串口设备的名称;
  • major:主设备号;
  • minor:次设备号;
  • nr:该驱动支持的串口数量;比如,某某芯片的介绍:”多达3个UART接口“,那么这个nr指的就是这个3;
  • cons:其对应的console,若该uart_driver支持serial console;否则为NULL;
  • state:下层,串口驱动层;指向一个数组,对应了芯片的每一个UART(比如 uart_state[0] 对应了芯片的 UART0以此类推),需要初始化为NULL;
  • tty_driver:上层,tty驱动层;需要初始化为NULL;、

2.2 struct console

struct console用于实现控制台打印功能,定义在include/linux/console.h:

struct console {
        char    name[16];
        void    (*write)(struct console *, const char *, unsigned);
        int     (*read)(struct console *, char *, unsigned);
        struct tty_driver *(*device)(struct console *, int *);
        void    (*unblank)(void);
        int     (*setup)(struct console *, char *);
        int     (*match)(struct console *, char *name, int idx, char *options);
        short   flags;
        short   index;
        int     cflag;
        void    *data;
        struct   console *next;
};

其中部分参数含义如下:

  • name:控制台名称;
  • write:写函数;
  • read:读函数;
  • device:控制台设备;
  • unblank:
  • setup:初始化函数, 以串口驱动控制台为例,函数内容设置串口波特率、发送、接收等功能 ;
  • match:
  • flags:标志位;
  • index:索引值;
  • pflag:
  • data:控制台数据,一以串口驱动控制台为例,存储串口驱动uart_driver;
  • next:

2.3 struct uart_state

每一个uart端口对应着一个uart_state,该结构体将uart_port与对应的circ_buf联系起来。uart_state有两个成员在底层串口驱动会用到:xmit和port。

用户空间程序通过串口发送数据时,上层驱动将用户数据保存在xmit;而串口发送中断处理函数就是通过xmit获取到用户数据并将它们发送出去。

串口接收中断处理函数需要通过port将接收到的数据传递给线路规程层。定义在include/linux/serial_core.h:

/*
 * This is the state information which is persistent across opens.
 */
struct uart_state {
        struct tty_port         port;

        enum uart_pm_state      pm_state;
        struct circ_buf         xmit;

        atomic_t                refcount;
        wait_queue_head_t       remove_wait;
        struct uart_port        *uart_port;
};

其中部分参数含义如下:

  • port:tty port;
  • pm_state:
  • xmit:串口待发送数据环形缓冲区;
  • refcount:
  • remove_wait:
  • uart_port:uart port;

2.4 struct uart_port

uart_port存储的是与UART串口硬件相关的信息,需要芯片厂家定义自己的 uart_port 结构并填充它,该结构定义在include/linux/serial_core.h:

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);
        void                    (*set_termios)(struct uart_port *,
                                               struct ktermios *new,
                                               struct ktermios *old);
        void                    (*set_ldisc)(struct uart_port *,
                                             struct ktermios *);
        unsigned int            (*get_mctrl)(struct uart_port *);
        void                    (*set_mctrl)(struct uart_port *, unsigned int);
        unsigned int            (*get_divisor)(struct uart_port *,
                                               unsigned int baud,
                                               unsigned int *frac);
        void                    (*set_divisor)(struct uart_port *,
                                               unsigned int baud,
                                               unsigned int quot,
                                               unsigned int quot_frac);
        int                     (*startup)(struct uart_port *port);
        void                    (*shutdown)(struct uart_port *port);
        void                    (*throttle)(struct uart_port *port);
        void                    (*unthrottle)(struct uart_port *port);
        int                     (*handle_irq)(struct uart_port *);
        void                    (*pm)(struct uart_port *, unsigned int state,
                                      unsigned int old);
        void                    (*handle_break)(struct uart_port *);
        int                     (*rs485_config)(struct uart_port *,
                                                struct serial_rs485 *rs485);
        int                     (*iso7816_config)(struct uart_port *,
                                                  struct serial_iso7816 *iso7816);
        unsigned int            irq;                    /* irq number */
        unsigned long           irqflags;               /* irq flags  */
        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           quirks;                 /* internal quirks */

#define UPIO_PORT               (SERIAL_IO_PORT)        /* 8b I/O port access */
#define UPIO_HUB6               (SERIAL_IO_HUB6)        /* Hub6 ISA card */
#define UPIO_MEM                (SERIAL_IO_MEM)         /* driver-specific */
#define UPIO_MEM32              (SERIAL_IO_MEM32)       /* 32b little endian */
#define UPIO_AU                 (SERIAL_IO_AU)          /* Au1x00 and RT288x type IO */
#define UPIO_TSI                (SERIAL_IO_TSI)         /* Tsi108/109 type IO */
#define UPIO_MEM32BE            (SERIAL_IO_MEM32BE)     /* 32b big endian */
#define UPIO_MEM16              (SERIAL_IO_MEM16)       /* 16b little endian */

        /* quirks must be updated while holding port mutex */
#define UPQ_NO_TXEN_TEST        BIT(0)

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

        struct console          *cons;                  /* struct console, if any */
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)
        unsigned long           sysrq;                  /* sysrq timeout */
        unsigned int            sysrq_ch;               /* char for sysrq */
#endif
        /*
         * These flags must be equivalent to the flags defined in
         * include/uapi/linux/tty_flags.h which are the userspace definitions
         * assigned from the serial_struct flags in uart_set_info()
         * [for bit definitions in the UPF_CHANGE_MASK]
         *
         * Bits [0..UPF_LAST_USER] are userspace defined/visible/changeable
         * The remaining bits are serial-core specific and not modifiable by
         * userspace.
         */
#define UPF_FOURPORT            ((__force upf_t) ASYNC_FOURPORT       /* 1  */ )
#define UPF_SAK                 ((__force upf_t) ASYNC_SAK            /* 2  */ )
#define UPF_SPD_HI              ((__force upf_t) ASYNC_SPD_HI         /* 4  */ )
#define UPF_SPD_VHI             ((__force upf_t) ASYNC_SPD_VHI        /* 5  */ )
#define UPF_SPD_CUST            ((__force upf_t) ASYNC_SPD_CUST   /* 0x0030 */ )
#define UPF_SPD_WARP            ((__force upf_t) ASYNC_SPD_WARP   /* 0x1010 */ )
#define UPF_SPD_MASK            ((__force upf_t) ASYNC_SPD_MASK   /* 0x1030 */ )
#define UPF_SKIP_TEST           ((__force upf_t) ASYNC_SKIP_TEST      /* 6  */ )
#define UPF_AUTO_IRQ            ((__force upf_t) ASYNC_AUTO_IRQ       /* 7  */ )
#define UPF_HARDPPS_CD          ((__force upf_t) ASYNC_HARDPPS_CD     /* 11 */ )
#define UPF_SPD_SHI             ((__force upf_t) ASYNC_SPD_SHI        /* 12 */ )
#define UPF_LOW_LATENCY         ((__force upf_t) ASYNC_LOW_LATENCY    /* 13 */ )
#define UPF_BUGGY_UART          ((__force upf_t) ASYNC_BUGGY_UART     /* 14 */ )
#define UPF_MAGIC_MULTIPLIER    ((__force upf_t) ASYNC_MAGIC_MULTIPLIER /* 16 */ )

#define UPF_NO_THRE_TEST        ((__force upf_t) (1 << 19))
/* Port has hardware-assisted h/w flow control */
#define UPF_AUTO_CTS            ((__force upf_t) (1 << 20))
#define UPF_AUTO_RTS            ((__force upf_t) (1 << 21))
#define UPF_HARD_FLOW           ((__force upf_t) (UPF_AUTO_CTS | UPF_AUTO_RTS))
/* Port has hardware-assisted s/w flow control */
#define UPF_SOFT_FLOW           ((__force upf_t) (1 << 22))
#define UPF_CONS_FLOW           ((__force upf_t) (1 << 23))
#define UPF_SHARE_IRQ           ((__force upf_t) (1 << 24))
#define UPF_EXAR_EFR            ((__force upf_t) (1 << 25))
#define UPF_BUG_THRE            ((__force upf_t) (1 << 26))
/* 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       0x17fff
#define UPF_CHANGE_MASK         ((__force upf_t) __UPF_CHANGE_MASK)
#define UPF_USR_MASK            ((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY))

#if __UPF_CHANGE_MASK > ASYNC_FLAGS
#error Change mask not equivalent to userspace-visible bit defines
#endif

        /*
         * Must hold termios_rwsem, port mutex and port lock to change;
         * can hold any one lock to read.
         */
        upstat_t                status;

#define UPSTAT_CTS_ENABLE       ((__force upstat_t) (1 << 0))
#define UPSTAT_DCD_ENABLE       ((__force upstat_t) (1 << 1))
#define UPSTAT_AUTORTS          ((__force upstat_t) (1 << 2))
#define UPSTAT_AUTOCTS          ((__force upstat_t) (1 << 3))
#define UPSTAT_AUTOXOFF         ((__force upstat_t) (1 << 4))
#define UPSTAT_SYNC_FIFO        ((__force upstat_t) (1 << 5))

        int                     hw_stopped;             /* sw-assisted CTS flow state */
        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 */
        unsigned int            minor;
        resource_size_t         mapbase;                /* for ioremap */
        resource_size_t         mapsize;
        struct device           *dev;                   /* parent device */
        unsigned char           hub6;                   /* this should be in the 8250 driver */
        unsigned char           suspended;
        unsigned char           unused[2];
        const char              *name;                  /* port name */
        struct attribute_group  *attr_group;            /* port specific attributes */
        const struct attribute_group **tty_groups;      /* all attributes (serial core use only) */
        struct serial_rs485     rs485;
        struct serial_iso7816   iso7816;
        void                    *private_data;          /* generic platform data pointer */
};

其中部分参数含义如下:

  • iobase:I/O端口寄存器基地址,物理地址;
  • membase:I/O端口寄存器基地址,虚拟地址;
  • irq:中断号,一般存放的是接收中断编号;
  • irqflags:中断标志;
  • uartclk:串口时钟;
  • fifosize:FIFO缓冲区大小;
  • regshift:寄存器位移;
  • iotype:I/O访问方式;
  • icount:串口信息计数器;
  • state:指向struct uart_state;
  • type:端口类型;
  • ops:串口端口的操作函数;
  • line:端口索引,通过drv->state + uport->line可以获取到当前UART对应的uart_state;
  • mapbase:I/O内存物理基地址;
  • dev:设备模型中的设备,一般存放的是platform device中device;

2.5 struct uart_ops

uart_ops定义了UART硬件相关相关的操作集,芯片厂家需要进行硬件寄存器级的适配其中各个操作,该结构定义在include/linux/serial_core.h:

/*
 * This structure describes all the operations that can be done on the
 * physical hardware.  See Documentation/serial/driver.rst for details.
 */
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            (*throttle)(struct uart_port *);
        void            (*unthrottle)(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 *, struct ktermios *);
        void            (*pm)(struct uart_port *, unsigned int state,
                              unsigned int oldstate);

        /*
         * 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
        int             (*poll_init)(struct uart_port *);
        void            (*poll_put_char)(struct uart_port *, unsigned char);
        int             (*poll_get_char)(struct uart_port *);
#endif
};

其中部分参数含义如下:

  • tx_empty:串口的Tx FIFO是否为空;
  • set_mctrl:设置串口modem控制;
  • get_mctrl:获取串口modem控制;
  • stop_tx:停止串口数据发送;
  • start_tx:开始串口数据发送;
  • send_xchar:发送一个字符;
  • stop_rx:停止串口数据接收;
  • enable_ms:使能modem的状态信号;
  • break_ctl:设置break信号;
  • startup:启动串口,应用程序打开串口的设备文件时,该函数被调用;
  • shutdown:关闭串口,应用程序关闭串口的设备文件时,该函数被调用;
  • set_termios:设置串口参数;
  • set_ldisc:设置线路规程;
  • pm:串口电源管理;
  • request_port:申请必要的IO端口/IO内存资源,必要时还可以重新映射串口端口;
  • config_port:指向串口所需的自动配置;
  • verify_port:核实新串口的信息;

2.6 关系图

我们在来看一下uart_driver、uart_state、uart_port之间的关系,如下图所示:

三、UART驱动API

linux内核提供了一组函数用于操作uart_driver和uart_port。

3.1 注册UART驱动

uart_register_driver用于注册UART设备,函数定义在drivers/tty/serial/serial_core.c:

/**
 *      uart_register_driver - register a driver with the uart core layer
 *      @drv: low level driver structure
 *
 *      Register a uart driver with the core driver.  We in turn register
 *      with the tty layer, and initialise the core driver per-port state.
 *
 *      We have a proc file in /proc/tty/driver which is named after the
 *      normal driver.
 *
 *      drv->port should be NULL, and the per-port structures should be
 *      registered using uart_add_one_port after this call has succeeded.
 */
int uart_register_driver(struct uart_driver *drv)
{
        struct tty_driver *normal;
        int i, retval = -ENOMEM;

        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 = kcalloc(drv->nr, sizeof(struct uart_state), GFP_KERNEL);  // 申请内存,支持几个串口,就申请几个uart_state结构体
        if (!drv->state)
                goto out;

        normal = alloc_tty_driver(drv->nr);    // 分配tty驱动
        if (!normal)
                goto out_kfree;

        drv->tty_driver = normal;
     // 初始化tty驱动成员  
        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);  // 初始化tty driver操作集
        /*
         * Initialise the UART state(s).
         */
        for (i = 0; i < drv->nr; i++) {
                struct uart_state *state = drv->state + i;
                struct tty_port *port = &state->port;

                tty_port_init(port);  // 初始化tty port
                port->ops = &uart_port_ops;
        }

        retval = tty_register_driver(normal);  // 注册tty驱动
        if (retval >= 0)   // 成功返回0
                return retval;

        for (i = 0; i < drv->nr; i++)
                tty_port_destroy(&drv->state[i].port);
        put_tty_driver(normal);  
out_kfree:
        kfree(drv->state);
out:
        return retval;
}

该函数的入参是struct uart_driver,该函主要:

  • 首先初始化uart_driver的成员:
    • 根据驱动支持的串口数量,动态申请内存,初始化state成员,执行一个数组,每个成员都是uart_state,用来存放驱动所支持的串口(端口)的物理信息;;
    • 根据驱动支持的串口数量,分配tty驱动,初始化tty_driver成员;
  • 初始化tty驱动:
    • 设置驱动名称driver_name;
    • 设置设备名称name;
    • 设置主设备号major;
    • 设置开始的次设备号minor_start;
    • 设置tty驱动类型为TTY_DRIVER_TYPE_SERIAL;
    • 设置tty驱动子类型为SERIAL_TYPE_NORMAL;
    • 设置初始化线路设置iit_termios;
    • 设置tty驱动标志为TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
    • 设置tty驱动私有数据为uart_driver;
    • 设置tty驱动操作集合ops为uart_ops,类型为struct tty_operations;
  • 遍历state数组,依次初始化为一个uart_state:
    • 获取成员port,类型为tty_port,调用tty_port_init初始化;
    • 设置tty port操作集为uart_port_ops;
  • 调用tty_register_driver注册tty驱动;

通过这段源码解读,我们会发现uart_driver的注册,实际上就是tty_driver的注册,都是将uart的参数传递给tty_driver,后注册字符设备、分配设备文件、将驱动注册到tty_driver链表中。

3.1.1 uart_ops

uart_ops定义在drivers/tty/serial/serial_core.c:

static const struct tty_operations uart_ops = {
        .install        = uart_install,
        .open           = uart_open,
        .close          = uart_close,
        .write          = uart_write,
        .put_char       = uart_put_char,
        .flush_chars    = uart_flush_chars,
        .write_room     = uart_write_room,
        .chars_in_buffer= uart_chars_in_buffer,
        .flush_buffer   = uart_flush_buffer,
        .ioctl          = uart_ioctl,
        .throttle       = uart_throttle,
        .unthrottle     = uart_unthrottle,
        .send_xchar     = uart_send_xchar,
        .set_termios    = uart_set_termios,
        .set_ldisc      = uart_set_ldisc,
        .stop           = uart_stop,
        .start          = uart_start,
        .hangup         = uart_hangup,
        .break_ctl      = uart_break_ctl,
        .wait_until_sent= uart_wait_until_sent,
#ifdef CONFIG_PROC_FS
        .proc_show      = uart_proc_show,
#endif
        .tiocmget       = uart_tiocmget,
        .tiocmset       = uart_tiocmset,
        .set_serial     = uart_set_info_user,
        .get_serial     = uart_get_info_user,
        .get_icount     = uart_get_icount,
#ifdef CONFIG_CONSOLE_POLL
        .poll_init      = uart_poll_init,
        .poll_get_char  = uart_poll_get_char,
        .poll_put_char  = uart_poll_put_char,
#endif
};

我们上一节说过当我们在应用层打开一个tty设备节点,比如串口设备/dev/ttySACn,就可以对串口做一些配置、读写的操作;

这里用户空间的任何open、write、read等操作,直接对应到了tty 层的注册到字符设备的file_operation,也就是tty_fops;

tty_fops成员函数执行过程中就会调用tty_operations中相应的函数。

3.1.2 uart_open

打个比如,应用层对设备节点 "/dev/ttySAC0" 设备节点调用open的时候,触发的流程如下:

  • tty_fops->tty_open():这个上一节已经介绍过了;
  •  tty->ops->open(tty, filp):其实就是调用到了 struct tty_operations uart_ops 中的 uart_open;

3.2 注册uart_port

在 uart_register_driver 调用成功后,可以说,我们已经对uart_drvier进行了注册,并且实现了这个结构体中的绝大多数成员了。

uart_state也会在register_uart_driver的过程中分配空间,但是它里面真正设置硬件相关的东西是uart_state->uart_port ,这个uart_port 是需要我们从其它地方调用 uart_add_one_port 来添加的。uart_add_one_port函数定义在drivers/tty/serial/serial_core.c:

/**
 *      uart_add_one_port - attach a driver-defined port structure
 *      @drv: pointer to the uart low level driver structure for this port
 *      @uport: uart port structure to use for this port.
 *
 *      This allows the driver to register its own uart_port structure
 *      with the core driver.  The main purpose is to allow the low
 *      level uart drivers to expand uart_port, rather than having yet
 *      more levels of structures.
 */
int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
        struct uart_state *state;
        struct tty_port *port;
        int ret = 0;
        struct device *tty_dev;
        int num_groups;

        BUG_ON(in_interrupt());

        if (uport->line >= drv->nr)
                return -EINVAL;

        state = drv->state + uport->line;  // 获取upor所描述的的uart_state
        port = &state->port;    // 获取tty_port

        mutex_lock(&port_mutex);
        mutex_lock(&port->mutex);
        if (state->uart_port) {
                ret = -EINVAL;
                goto out;
        }

        /* Link the port to the driver state table and vice versa */
        atomic_set(&state->refcount, 1);
        init_waitqueue_head(&state->remove_wait);
        state->uart_port = uport;  // 这一步才是重点
        uport->state = state;

        state->pm_state = UART_PM_STATE_UNDEFINED;
        uport->cons = drv->cons;
        uport->minor = drv->tty_driver->minor_start + uport->line;
        uport->name = kasprintf(GFP_KERNEL, "%s%d", drv->dev_name,
                                drv->tty_driver->name_base + uport->line);
        if (!uport->name) {
                ret = -ENOMEM;
                goto out;
        }

        /*
         * If this port is a console, then the spinlock is already
         * initialised.
         */
        if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) {
                spin_lock_init(&uport->lock);
                lockdep_set_class(&uport->lock, &port_lock_key);
        }
        if (uport->cons && uport->dev)
                of_console_check(uport->dev->of_node, uport->cons->name, uport->line);

        uart_configure_port(drv, state, uport);

        port->console = uart_console(uport);

        num_groups = 2;
        if (uport->attr_group)
                num_groups++;

        uport->tty_groups = kcalloc(num_groups, sizeof(*uport->tty_groups),
                                    GFP_KERNEL);
        if (!uport->tty_groups) {
                ret = -ENOMEM;
                goto out;
        }
        uport->tty_groups[0] = &tty_dev_attr_group;
        if (uport->attr_group)
                uport->tty_groups[1] = uport->attr_group;

        /*
         * Register the port whether it's detected or not.  This allows
         * setserial to be used to alter this port's parameters.
         */
        tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver,
                        uport->line, uport->dev, port, uport->tty_groups);
        if (!IS_ERR(tty_dev)) {
                device_set_wakeup_capable(tty_dev, 1);
        } else {
                dev_err(uport->dev, "Cannot register tty device on line %dn",
                       uport->line);
        }

        /*
         * Ensure UPF_DEAD is not set.
         */
        uport->flags &= ~UPF_DEAD;

 out:
        mutex_unlock(&port->mutex);
        mutex_unlock(&port_mutex);

        return ret;
}

这个函有两个参数:

  • drv:为哪个uart_driver 赋予一个uart_port;
  • uport:具体的 uart port;

一个 uart_port 代表了一个UART物理硬件,有多个UART的话,就要调用多次这个接口!

还记得之前那个uart_state结构么,它里面不就有一个 uart_port 么,没错,这里就是把这个uport 赋值给对应的这个state的uart_port成员。那么这个uart_port 又是从何而来的呢?既然是和芯片直接相关的,那么肯定是芯片厂家定义的。

比如我们 S3C2440有3个串口,那么就需要填充3个uart_port ,并且通过 uart_add_one_port 添加uart_driver->uart_state->uart_port 中去。当然 uart_driver有多个uart_state ,每个 uart_state 有一个 uart_port 。在uart_port里还有一个非常重要的成员 struct uart_ops *ops ,这个也是需要我们自己来实现的。

四、UART驱动编写步骤

经过对UART相关数据结构和API的分析,我们大概了解到,要实现一个串口驱动,我们需要定义以下数据结构:

  • uart_driver结构,一个;
  • uart_port结构,多个,取决于串口的数量;
  • uart_ops串口的操作集,可能一个,也可能多个;

同时需要调用的API有:

  • uart_register_driver,一次;
  • uart_add_one_port,多次;

我们以S3C2440 UART驱动为例,对其源码进行分析,其采用的也是platform设备驱动模型。

五、UART platform设备

5.1 串口配置

我们定位到arch/arm/mach-s3c24xx/mach-smdk2440.c文件,找到串口相关的定义:

#define UCON S3C2410_UCON_DEFAULT | S3C2410_UCON_UCLK
#define ULCON S3C2410_LCON_CS8 | S3C2410_LCON_PNONE | S3C2410_LCON_STOPB
#define UFCON S3C2410_UFCON_RXTRIG8 | S3C2410_UFCON_FIFOMODE

static struct s3c2410_uartcfg smdk2440_uartcfgs[] __initdata = {
        [0] = {
                .hwport      = 0,
                .flags       = 0,
                .ucon        = 0x3c5,
                .ulcon       = 0x03,
                .ufcon       = 0x51,
        },
        [1] = {
                .hwport      = 1,
                .flags       = 0,
                .ucon        = 0x3c5,
                .ulcon       = 0x03,
                .ufcon       = 0x51,
        },
        /* IR port */
        [2] = {
                .hwport      = 2,
                .flags       = 0,
                .ucon        = 0x3c5,
                .ulcon       = 0x43,
                .ufcon       = 0x51,
        }
};

这里定义了S3C2440 3个串口的配置,主要包括串口寄存器值的配置,以串口0为例:

  • UCON:串口控制器寄存器0x3c5;
    • [1:0]:接收模式设置为中断请求;
    • [3:2]:发送模式设置为中断请求;
    • [6]:使能产生接收错误状态中断;
    • [7]:使能Rx超时使能 ;
    • [8]:Rx中断类型设置为脉冲;
    • [9]:Tx中断类型设置为脉冲;
    • [11:10]:时钟选择PCLK,50MHz;
  • ULCON:串口线路控制寄存器0x03;
    • [1:0]:字节长度为8位;
    • [2]:1个停止位;
    • [5:3]:无奇偶校验位;
    • [6]:普通模式;
  • UFCON:串口FIFO控制寄存器0x51;
    • [0]:使能FIFO;
    • [1]:Rx FIFO复位正常;
    • [2]:Tx FIFO复位正常;
    • [5:4]:Rx FIFO触发深度16字节;
    • [7:6]:Tx FIFO触发深度16字节;

我们找到smdk2440_uartcfgs被引用的代码:

static void __init smdk2440_map_io(void)
{
        s3c24xx_init_io(smdk2440_iodesc, ARRAY_SIZE(smdk2440_iodesc));
        s3c24xx_init_uarts(smdk2440_uartcfgs, ARRAY_SIZE(smdk2440_uartcfgs));
        samsung_set_timer_source(SAMSUNG_PWM3, SAMSUNG_PWM4);
}

5.2 串口初始化

s3c24xx_init_uarts函数定义在arch/arm/plat-samsung/init.c:

void __init s3c24xx_init_uarts(struct s3c2410_uartcfg *cfg, int no)
{
        if (cpu == NULL)
                return;

        if (cpu->init_uarts == NULL && IS_ENABLED(CONFIG_SAMSUNG_ATAGS)) {
                printk(KERN_ERR "s3c24xx_init_uarts: cpu has no uart initn");
        } else
                (cpu->init_uarts)(cfg, no);
}

上面函数中的变量cpu是一个全局变量,定义如下所示:

static struct cpu_table *cpu;

那么该变量的值是什么呢?往下看:

setup_arch()
    ->paging_init()
        ->devicemaps_init()
            if (mdesc->map_io)
                mdesc->map_io();

而mdesc->map_io()函数就是smdk2440_map_io函数。

5.2.1 s3c24xx_init_io

再看s3c24xx_init_io函数的定义,定义在arch/arm/mach-s3c24xx/common.c:

void __init s3c24xx_init_io(struct map_desc *mach_desc, int size)
{
        arm_pm_idle = s3c24xx_default_idle;

        /* initialise the io descriptors we need for initialisation */
        iotable_init(mach_desc, size);
        iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc));

        if (cpu_architecture() >= CPU_ARCH_ARMv5) {
                samsung_cpu_id = s3c24xx_read_idcode_v5();
        } else {
                samsung_cpu_id = s3c24xx_read_idcode_v4();
        }

        s3c_init_cpu(samsung_cpu_id, cpu_ids, ARRAY_SIZE(cpu_ids));

        samsung_pwm_set_platdata(&s3c24xx_pwm_variant);
}
5.2.2 s3c_init_cpu

最后调用s3c_init_cpu函数,该函数的第2个参数的定义如下,位于arch/arm/mach-s3c24xx/common.c:

static struct cpu_table cpu_ids[] __initdata = {
        {
                .idcode         = 0x32410000,
                .idmask         = 0xffffffff,
                .map_io         = s3c2410_map_io,
                .init_uarts     = s3c2410_init_uarts,
                .init           = s3c2410_init,
                .name           = name_s3c2410
        },
        {
                .idcode         = 0x32410002,
                .idmask         = 0xffffffff,
                .map_io         = s3c2410_map_io,
                .init_uarts     = s3c2410_init_uarts,
                .init           = s3c2410a_init,
                .name           = name_s3c2410a
        },
        {
                .idcode         = 0x32440000,
                .idmask         = 0xffffffff,
                .map_io         = s3c2440_map_io,
                .init_uarts     = s3c244x_init_uarts,
                .init           = s3c2440_init,
                .name           = name_s3c2440
        },
        {
                .idcode         = 0x32440001,
                .idmask         = 0xffffffff,
                .map_io         = s3c2440_map_io,
                .init_uarts     = s3c244x_init_uarts,
                .init           = s3c2440_init,
                .name           = name_s3c2440a
        },
        {
                .idcode         = 0x32440aaa,
                .idmask         = 0xffffffff,
                .map_io         = s3c2442_map_io,
                .init_uarts     = s3c244x_init_uarts,
                .init           = s3c2442_init,
                .name           = name_s3c2442
        },
        {
                .idcode         = 0x32440aab,
                .idmask         = 0xffffffff,
                .map_io         = s3c2442_map_io,
                .init_uarts     = s3c244x_init_uarts,
                .init           = s3c2442_init,
                .name           = name_s3c2442b
        },
        {
                .idcode         = 0x32412001,
                .idmask         = 0xffffffff,
                .map_io         = s3c2412_map_io,
                .init_uarts     = s3c2412_init_uarts,
                .init           = s3c2412_init,
        },
        {                       /* a newer version of the s3c2412 */
                .idcode         = 0x32412003,
                .idmask         = 0xffffffff,
                .map_io         = s3c2412_map_io,
                .init_uarts     = s3c2412_init_uarts,
                .init           = s3c2412_init,
                .name           = name_s3c2412,
        },
        {                       /* a strange version of the s3c2416 */
                .idcode         = 0x32450003,
                .idmask         = 0xffffffff,
                .map_io         = s3c2416_map_io,
                .init_uarts     = s3c2416_init_uarts,
                .init           = s3c2416_init,
                .name           = name_s3c2416,
        },
        {
                .idcode         = 0x32443001,
                .idmask         = 0xffffffff,
                .map_io         = s3c2443_map_io,
                .init_uarts     = s3c2443_init_uarts,
                .init           = s3c2443_init,
                .name           = name_s3c2443,
        },
};

而s3c_init_cpu的定义是这样的:

void __init s3c_init_cpu(unsigned long idcode,
                         struct cpu_table *cputab, unsigned int cputab_size)
{
        cpu = s3c_lookup_cpu(idcode, cputab, cputab_size);

        if (cpu == NULL) {
                printk(KERN_ERR "Unknown CPU type 0x%08lxn", idcode);
                panic("Unknown S3C24XX CPU");
        }

        printk("CPU %s (id 0x%08lx)n", cpu->name, idcode);

        if (cpu->init == NULL) {
                printk(KERN_ERR "CPU %s support not enabledn", cpu->name);
                panic("Unsupported Samsung CPU");
        }

        if (cpu->map_io)
                cpu->map_io();
}

 这个函数其实也很简单,通过调用s3c_lookup_cpu函数来设置cpu的值,最后如果是S3C2440的话,那么cpu的值为:

        {
                .idcode         = 0x32440000,
                .idmask         = 0xffffffff,
                .map_io         = s3c2440_map_io,
                .init_uarts     = s3c244x_init_uarts,
                .init           = s3c2440_init,
                .name           = name_s3c2440
        },

回过头再看s3c24xx_init_uarts函数,函数里面的cpu->init_uarts函数其实就是s3c244x_init_uarts函数,其传递的参数为smdk2440_uartcfgs和ARRAY_SIZE(smdk2440_uartcfgs)。

5.2.3 s3c244x_init_uarts

s3c244x_init_uarts函数位于arch/arm/mach-s3c24xx/s3c244x.c:

/* uart initialisation */

void __init s3c244x_init_uarts(struct s3c2410_uartcfg *cfg, int no)
{
        s3c24xx_init_uartdevs("s3c2440-uart", s3c2410_uart_resources, cfg, no);
}

5.3 资源定义

我们来看一下串口资源是如何定义的,s3c2410_uart_resources位于arch/arm/mach-s3c24xx/common.c:

struct s3c24xx_uart_resources s3c2410_uart_resources[] __initdata = {
        [0] = {
                .resources      = s3c2410_uart0_resource,
                .nr_resources   = ARRAY_SIZE(s3c2410_uart0_resource),
        },
        [1] = {
                .resources      = s3c2410_uart1_resource,
                .nr_resources   = ARRAY_SIZE(s3c2410_uart1_resource),
        },
        [2] = {
                .resources      = s3c2410_uart2_resource,
                .nr_resources   = ARRAY_SIZE(s3c2410_uart2_resource),
        },
        [3] = {
                .resources      = s3c2410_uart3_resource,
                .nr_resources   = ARRAY_SIZE(s3c2410_uart3_resource),
        },
};

函数s3c24xx_init_uartdevs 的定义如下,位于arch/arm/plat-samsung/init.c:

/* s3c24xx_init_uartdevs
 *
 * copy the specified platform data and configuration into our central
 * set of devices, before the data is thrown away after the init process.
 *
 * This also fills in the array passed to the serial driver for the
 * early initialisation of the console.
*/

void __init s3c24xx_init_uartdevs(char *name,
                                  struct s3c24xx_uart_resources *res,
                                  struct s3c2410_uartcfg *cfg, int no)
{
#ifdef CONFIG_SERIAL_SAMSUNG_UARTS
        struct platform_device *platdev;
        struct s3c2410_uartcfg *cfgptr = uart_cfgs;
        struct s3c24xx_uart_resources *resp;
        int uart;

        memcpy(cfgptr, cfg, sizeof(struct s3c2410_uartcfg) * no);  // 拷贝

        for (uart = 0; uart < no; uart++, cfg++, cfgptr++) {    // cfgptr指针+1
                platdev = s3c24xx_uart_src[cfgptr->hwport];

                resp = res + cfgptr->hwport;

                s3c24xx_uart_devs[uart] = platdev;

                platdev->name = name;
                platdev->resource = resp->resources;
                platdev->num_resources = resp->nr_resources;

                platdev->dev.platform_data = cfgptr;
        }

        nr_uarts = no;
#endif
}

该函数中的uart_cfgs也是一个全局变量,看看它的定义:

#ifdef CONFIG_SERIAL_SAMSUNG_UARTS
static struct s3c2410_uartcfg uart_cfgs[CONFIG_SERIAL_SAMSUNG_UARTS];
#endif

该函数中的 s3c24xx_uart_src也是一个全局变量,看看它的定义:

/* uart devices */

static struct platform_device s3c24xx_uart_device0 = {
        .id             = 0,
};

static struct platform_device s3c24xx_uart_device1 = {
        .id             = 1,
};

static struct platform_device s3c24xx_uart_device2 = {
        .id             = 2,
};

static struct platform_device s3c24xx_uart_device3 = {
        .id             = 3,
};

struct platform_device *s3c24xx_uart_src[4] = {
        &s3c24xx_uart_device0,
        &s3c24xx_uart_device1,
        &s3c24xx_uart_device2,
        &s3c24xx_uart_device3,
};

该函数中的 s3c24xx_uart_devs 也是一个全局变量,看看它的定义:

struct platform_device *s3c24xx_uart_devs[4] = {
};

5.4 总结

我们总结一下函数的调用流程如下图所示:

在流程最后s3c24xx_init_uartdevs函数主要作用就是为变量s3c24xx_uart_device0~2赋值,其实上面这些所有的操作可以总结为以下代码:

static struct resource s3c2410_uart0_resource[] = {
    [0] = {
        .start = S3C2410_PA_UART0,
        .end   = S3C2410_PA_UART0 + 0x3fff,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = IRQ_S3CUART_RX0,
        .end   = IRQ_S3CUART_ERR0,
        .flags = IORESOURCE_IRQ,
    }
};
 
static struct resource s3c2410_uart1_resource[] = {
    [0] = {
        .start = S3C2410_PA_UART1,
        .end   = S3C2410_PA_UART1 + 0x3fff,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = IRQ_S3CUART_RX1,
        .end   = IRQ_S3CUART_ERR1,
        .flags = IORESOURCE_IRQ,
    }
};
 
static struct resource s3c2410_uart2_resource[] = {
    [0] = {
        .start = S3C2410_PA_UART2,
        .end   = S3C2410_PA_UART2 + 0x3fff,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = IRQ_S3CUART_RX2,
        .end   = IRQ_S3CUART_ERR2,
        .flags = IORESOURCE_IRQ,
    }
};
 
static struct resource s3c2410_uart3_resource[] = {
    [0] = {
        .start = S3C2443_PA_UART3,
        .end   = S3C2443_PA_UART3 + 0x3fff,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = IRQ_S3CUART_RX3,
        .end   = IRQ_S3CUART_ERR3,
        .flags = IORESOURCE_IRQ,
    },
};
 
static struct platform_device s3c24xx_uart_device0 = {
    .id                = 0,
    .name            = "s3c2440-uart",
    .resource         = s3c2410_uart0_resource,
    .num_resources     = ARRAY_SIZE(s3c2410_uart0_resource),
    .dev            = {
                        .platform_data = uart_cfgs,    // 取得是smdk2440_uartcfgs数组第0个元素
                      }
};
 
static struct platform_device s3c24xx_uart_device1 = {
    .id                = 1,
    .name            = "s3c2440-uart",
    .resource         = s3c2410_uart1_resource,
    .num_resources     = ARRAY_SIZE(s3c2410_uart1_resource),
    .dev            = {
                        .platform_data = uart_cfgs,  // 取得是smdk2440_uartcfgs数组第1个元素
                      }
};
 
static struct platform_device s3c24xx_uart_device2 = {
    .id                = 2,
    .name            = "s3c2440-uart",
    .resource         = s3c2410_uart2_resource,
    .num_resources     = ARRAY_SIZE(s3c2410_uart2_resource),
    .dev            = {
                        .platform_data = uart_cfgs,  // 取得是smdk2440_uartcfgs数组第2个元素
                      }
};
 
struct platform_device *s3c24xx_uart_devs[4] = {
    &s3c24xx_uart_device0,
    &s3c24xx_uart_device1,
    &s3c24xx_uart_device2,
};

既然已经为S3C2440的三个串口定义了三个platform device,那平台设备是在何时注册的呢?

s3c24xx_uart_devs是通过s3c_arch_init函数以模块的方式注册到内核的,函数位于arch/arm/plat-samsung/init.c

static int __init s3c_arch_init(void)
{
        int ret;

        /* init is only needed for ATAGS based platforms */
        if (!IS_ENABLED(CONFIG_ATAGS) ||
            (!soc_is_s3c24xx() && !soc_is_s3c64xx()))
                return 0;

        // do the correct init for cpu

        if (cpu == NULL) {
                /* Not needed when booting with device tree. */
                if (of_have_populated_dt())
                        return 0;
                panic("s3c_arch_init: NULL cpun");
        }

        ret = (cpu->init)();
        if (ret != 0)
                return ret;
#if IS_ENABLED(CONFIG_SAMSUNG_ATAGS)
        ret = platform_add_devices(s3c24xx_uart_devs, nr_uarts);
#endif
        return ret;
}

arch_initcall(s3c_arch_init);

六、UART platform驱动

6.1 驱动定义

串口驱动的定义位于drivers/tty/serial/samsung.c:

static const struct platform_device_id s3c24xx_serial_driver_ids[] = {
        {
                .name           = "s3c2410-uart",
                .driver_data    = S3C2410_SERIAL_DRV_DATA,
        }, {
                .name           = "s3c2412-uart",
                .driver_data    = S3C2412_SERIAL_DRV_DATA,
        }, {
                .name           = "s3c2440-uart",                         // 匹配这里
                .driver_data    = S3C2440_SERIAL_DRV_DATA,
        }, {
                .name           = "s3c6400-uart",
                .driver_data    = S3C6400_SERIAL_DRV_DATA,
        }, 
......
{ }, }; MODULE_DEVICE_TABLE(platform, s3c24xx_serial_driver_ids); #ifdef CONFIG_OF static const struct of_device_id s3c24xx_uart_dt_match[] = { // 设备树 { .compatible = "samsung,s3c2410-uart", .data = (void *)S3C2410_SERIAL_DRV_DATA }, { .compatible = "samsung,s3c2412-uart", .data = (void *)S3C2412_SERIAL_DRV_DATA }, { .compatible = "samsung,s3c2440-uart", .data = (void *)S3C2440_SERIAL_DRV_DATA }, { .compatible = "samsung,s3c6400-uart", .data = (void *)S3C6400_SERIAL_DRV_DATA }, { .compatible = "samsung,s5pv210-uart", .data = (void *)S5PV210_SERIAL_DRV_DATA }, { .compatible = "samsung,exynos4210-uart", .data = (void *)EXYNOS4210_SERIAL_DRV_DATA }, { .compatible = "samsung,exynos5433-uart", .data = (void *)EXYNOS5433_SERIAL_DRV_DATA }, {}, }; MODULE_DEVICE_TABLE(of, s3c24xx_uart_dt_match); #endif static struct platform_driver samsung_serial_driver = { .probe = s3c24xx_serial_probe, .remove = s3c24xx_serial_remove, .id_table = s3c24xx_serial_driver_ids, .driver = { .name = "samsung-uart", .pm = SERIAL_SAMSUNG_PM_OPS, .of_match_table = of_match_ptr(s3c24xx_uart_dt_match), }, }; module_platform_driver(samsung_serial_driver);

我们知道当platform device和platform driver匹配的话,将会调用platform驱动的probe函数,也就是s3c24xx_serial_probe。

6.2 s3c24xx_serial_probe

s3c24xx_serial_probe函数定义位于drivers/tty/serial/samsung.c:

static int s3c24xx_serial_probe(struct platform_device *pdev)
{
        struct device_node *np = pdev->dev.of_node;
        struct s3c24xx_uart_port *ourport;
        int index = probe_index;     // 全局变量 0
        int ret;

        if (np) {
                ret = of_alias_get_id(np, "serial");
                if (ret >= 0)
                        index = ret;
        }

        dbg("s3c24xx_serial_probe(%p) %dn", pdev, index);

        if (index >= ARRAY_SIZE(s3c24xx_serial_ports)) {
                dev_err(&pdev->dev, "serial%d out of rangen", index);
                return -EINVAL;
        }
        ourport = &s3c24xx_serial_ports[index];  // 获取第index个s3c24xx_uart_port

        ourport->drv_data = s3c24xx_get_driver_data(pdev);   // 设置为S3C2440_SERIAL_DRV_DATA
        if (!ourport->drv_data) {
                dev_err(&pdev->dev, "could not find driver datan");
                return -ENODEV;
        }

        ourport->baudclk = ERR_PTR(-EINVAL);
        ourport->info = ourport->drv_data->info;
        ourport->cfg = (dev_get_platdata(&pdev->dev)) ?   // 设置为uart_cfgs
                        dev_get_platdata(&pdev->dev) :
                        ourport->drv_data->def_cfg;

        if (np)
                of_property_read_u32(np,
                        "samsung,uart-fifosize", &ourport->port.fifosize);

        if (ourport->drv_data->fifosize[index])
                ourport->port.fifosize = ourport->drv_data->fifosize[index];
        else if (ourport->info->fifosize)
                ourport->port.fifosize = ourport->info->fifosize;

        /*
         * DMA transfers must be aligned at least to cache line size,
         * so find minimal transfer size suitable for DMA mode
         */
        ourport->min_dma_size = max_t(int, ourport->port.fifosize,
                                    dma_get_cache_alignment());

        dbg("%s: initialising port %p...n", __func__, ourport);

        ret = s3c24xx_serial_init_port(ourport, pdev);  // 获取串口硬件资源,比如寄存器基地址、中断编号,时钟等,并初始化uart_port成员;最后进行UCON、UFCON寄存器配置;
        if (ret < 0)
                return ret;

        if (!s3c24xx_uart_drv.state) {  // 如果未初始化过uart_state,则说明uart_driver未注册过
                ret = uart_register_driver(&s3c24xx_uart_drv);  // 注册串口驱动
                if (ret < 0) {
                        pr_err("Failed to register Samsung UART drivern");
                        return ret;
                }
        }

        dbg("%s: adding portn", __func__);
        uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);  // 注册uart_port
        platform_set_drvdata(pdev, &ourport->port);

        /*
         * Deactivate the clock enabled in s3c24xx_serial_init_port here,
         * so that a potential re-enablement through the pm-callback overlaps
         * and keeps the clock enabled in this case.
         */
        clk_disable_unprepare(ourport->clk);  // 将clk_disable和clk_unprepare组合起来
        if (!IS_ERR(ourport->baudclk))
                clk_disable_unprepare(ourport->baudclk);

        ret = s3c24xx_serial_cpufreq_register(ourport);   // cpufreq_register_notifier(&port->freq_transition,CPUFREQ_TRANSITION_NOTIFIER),
// port->freq_transition.notifier_call = s3c24xx_serial_cpufreq_transition; 个人理解这里就是注册一个listener,用于监听CPU时钟频率变化,当时钟频率发生改变
// 调用s3c24xx_serial_cpufreq_transition->s3c24xx_serial_set_termios,重新计算波特率分频寄存器UBRDIV的值,并写入该寄存器
if (ret < 0) dev_err(&pdev->dev, "failed to add cpufreq notifiern"); probe_index++; return 0; }

其中S3C2440_SERIAL_DRV_DATA被定义为了:

#if defined(CONFIG_CPU_S3C2440) || defined(CONFIG_CPU_S3C2416) || 
        defined(CONFIG_CPU_S3C2443) || defined(CONFIG_CPU_S3C2442)
static struct s3c24xx_serial_drv_data s3c2440_serial_drv_data = {
        .info = &(struct s3c24xx_uart_info) {
                .name           = "Samsung S3C2440 UART",
                .type           = PORT_S3C2440,
                .fifosize       = 64,
                .has_divslot    = 1,
                .rx_fifomask    = S3C2440_UFSTAT_RXMASK,
                .rx_fifoshift   = S3C2440_UFSTAT_RXSHIFT,
                .rx_fifofull    = S3C2440_UFSTAT_RXFULL,
                .tx_fifofull    = S3C2440_UFSTAT_TXFULL,
                .tx_fifomask    = S3C2440_UFSTAT_TXMASK,
                .tx_fifoshift   = S3C2440_UFSTAT_TXSHIFT,
                .def_clk_sel    = S3C2410_UCON_CLKSEL2,  // 1<<2
                .num_clks       = 4,
                .clksel_mask    = S3C2412_UCON_CLKMASK,
                .clksel_shift   = S3C2412_UCON_CLKSHIFT,
        },
        .def_cfg = &(struct s3c2410_uartcfg) {
                .ucon           = S3C2410_UCON_DEFAULT,
                .ufcon          = S3C2410_UFCON_DEFAULT,
        },
};
#define S3C2440_SERIAL_DRV_DATA ((kernel_ulong_t)&s3c2440_serial_drv_data)
#else
#define S3C2440_SERIAL_DRV_DATA (kernel_ulong_t)NULL
#endif

s3c24xx_serial_probe函数的前半部分代码主要是对UART设备进行一些初始化工作,最终的目的是为UART设备分配一个uart_port结构体,以UART0为例,其初始化完之后的数据结构如下图:

注意:由于S3C2440有三个UART,所以最终会得到三个struct s3c24xx_uart_port,三个struct uart_port,三个struct s3c2410_uartcfg,他们内容也是各不一样的。

s3c24xx_serial_probe函数的后半部分代码就是我们之前介绍的:

  • 调用uart_register_driver(&s3c24xx_uart_drv)注册uart_driver;
  • 调用uart_add_one_port(&s3c24xx_uart_drv, &ourport->port)注册uart_port,这里的ourport->port就是上半段代码初始化得到的uart_port;
6.2.1 s3c24xx_serial_ports

三星厂商定义了自己的s3c24xx_uart_port结构,用来存储的是与UART串口硬件相关的信息,这个结构体的数据后面会用来填充uart_port:

struct s3c24xx_uart_port {
    unsigned char            rx_claimed;
    unsigned char            tx_claimed;
    unsigned int            pm_level;
    unsigned long            baudclk_rate;   // 波特率
    unsigned int            min_dma_size;
 
    unsigned int            rx_irq;    // 接收中断号
    unsigned int            tx_irq;    // 发送中断号
 
    unsigned int            tx_in_progress;
    unsigned int            tx_mode;       // 发送模式
    unsigned int            rx_mode;       // 接收模式
 
    struct s3c24xx_uart_info    *info;     // 会设置为&s3c2440_serial_drv_data .info
    struct clk            *clk;            // uart时钟
    struct clk            *baudclk;        // 波特率时钟
    struct uart_port        port;       // serial core中的数据结构
    struct s3c24xx_serial_drv_data    *drv_data; // 驱动数据 指向s3c2440_serial_drv_data 
 
    /* reference to platform data */
    struct s3c2410_uartcfg        *cfg;  // 配置信息,主要保存寄存器的配置值 会设置为uart_cfgs
 
    struct s3c24xx_uart_dma        *dma;
 
#ifdef CONFIG_ARM_S3C24XX_CPUFREQ
    struct notifier_block        freq_transition;
#endif
};

并且为每个串口初始化一个s3c24xx_uart_port,保存在s3c24xx_serial_ports数组:

static struct s3c24xx_uart_port
s3c24xx_serial_ports[CONFIG_SERIAL_SAMSUNG_UARTS] = {
        [0] = {
                .port = {
                        .lock           = __PORT_LOCK_UNLOCKED(0),
                        .iotype         = UPIO_MEM,
                        .uartclk        = 0,
                        .fifosize       = 16,
                        .ops            = &s3c24xx_serial_ops,
                        .flags          = UPF_BOOT_AUTOCONF,
                        .line           = 0,
                }
        },
        [1] = {
                .port = {
                        .lock           = __PORT_LOCK_UNLOCKED(1),
                        .iotype         = UPIO_MEM,
                        .uartclk        = 0,
                        .fifosize       = 16,                    // FIFO缓冲区大小 
                        .ops            = &s3c24xx_serial_ops,   // 操作集
                        .flags          = UPF_BOOT_AUTOCONF,
                        .line           = 1,
                }
        },
#if CONFIG_SERIAL_SAMSUNG_UARTS > 2

        [2] = {
                .port = {
                        .lock           = __PORT_LOCK_UNLOCKED(2),
                        .iotype         = UPIO_MEM,
                        .uartclk        = 0,
                        .fifosize       = 16,
                        .ops            = &s3c24xx_serial_ops,
                        .flags          = UPF_BOOT_AUTOCONF,
                        .line           = 2,
                }
        },
#endif
#if CONFIG_SERIAL_SAMSUNG_UARTS > 3
        [3] = {
                .port = {
                        .lock           = __PORT_LOCK_UNLOCKED(3),
                        .iotype         = UPIO_MEM,
                        .uartclk        = 0,
                        .fifosize       = 16,
                        .ops            = &s3c24xx_serial_ops,
                        .flags          = UPF_BOOT_AUTOCONF,
                        .line           = 3,
                }
        }
#endif
};

这里我们重点关注uart_ops *ops 结构,在这里被赋值成为了 s3c24xx_serial_ops。

6.2.2 s3c24xx_get_driver_data
static inline struct s3c24xx_serial_drv_data *s3c24xx_get_driver_data(
                        struct platform_device *pdev)
{
#ifdef CONFIG_OF
        if (pdev->dev.of_node) {
                const struct of_device_id *match;
                match = of_match_node(s3c24xx_uart_dt_match, pdev->dev.of_node);
                return (struct s3c24xx_serial_drv_data *)match->data;
        }
#endif
        return (struct s3c24xx_serial_drv_data *)
                        platform_get_device_id(pdev)->driver_data;  // 获取s3c24xx_serial_driver_ids数组元素中的成员driver_data,S3C2440_SERIAL_DRV_DATA
}
6.2.3 s3c24xx_serial_init_port
/* s3c24xx_serial_init_port
 *
 * initialise a single serial port from the platform device given
 */

static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport,
                                    struct platform_device *platdev)
{
        struct uart_port *port = &ourport->port;
        struct s3c2410_uartcfg *cfg = ourport->cfg;
        struct resource *res;
        int ret;

        dbg("s3c24xx_serial_init_port: port=%p, platdev=%pn", port, platdev);

        if (platdev == NULL)
                return -ENODEV;

        if (port->mapbase != 0)
                return -EINVAL;

        /* setup info for port */
        port->dev       = &platdev->dev;

        /* Startup sequence is different for s3c64xx and higher SoC's */
        if (s3c24xx_serial_has_interrupt_mask(port))
                s3c24xx_serial_ops.startup = s3c64xx_serial_startup;

        port->uartclk = 1;

        if (cfg->uart_flags & UPF_CONS_FLOW) {
                dbg("s3c24xx_serial_init_port: enabling flow controln");
                port->flags |= UPF_CONS_FLOW;
        }

        /* sort our the physical and virtual addresses for each UART */

        res = platform_get_resource(platdev, IORESOURCE_MEM, 0);  // 获取IO内存资源,即UART控制器寄存器内存资源
        if (res == NULL) {
                dev_err(port->dev, "failed to find memory resource for uartn");
                return -EINVAL;
        }

        dbg("resource %pR)n", res);

        port->membase = devm_ioremap(port->dev, res->start, resource_size(res));   // 存储为虚拟地址
        if (!port->membase) {
                dev_err(port->dev, "failed to remap controller addressn");
                return -EBUSY;
        }

        port->mapbase = res->start;  // 以串口0位为例 物理起始地址为0x50000000
        ret = platform_get_irq(platdev, 0);   // 获取第0个中断资源
        if (ret < 0)
                port->irq = 0;
        else {
                port->irq = ret;   //设置接收中断编号 任然以串口0为例:主中断源为IRQ_UART0(编号44),子中断为IRQ_S3CUART_RX0(编号74),IRQ_S3CUART_TX0(编号75)、IRQ_S3CUART_ERR0(编号77)
                ourport->rx_irq = ret;  // IRQ_S3CUART_RX0(编号74)
                ourport->tx_irq = ret + 1;  // IRQ_S3CUART_TX0(编号75)
        }

        ret = platform_get_irq(platdev, 1);  // 获取第1个中断资源  
        if (ret > 0)  // 不会进入
                ourport->tx_irq = ret;
        /*
         * DMA is currently supported only on DT platforms, if DMA properties
         * are specified.
         */
        if (platdev->dev.of_node && of_find_property(platdev->dev.of_node,
                                                     "dmas", NULL)) {
                ourport->dma = devm_kzalloc(port->dev,
                                            sizeof(*ourport->dma),
                                            GFP_KERNEL); 
                if (!ourport->dma) {
                        ret = -ENOMEM;
                        goto err;
                }
        }

        ourport->clk    = clk_get(&platdev->dev, "uart");   // 获取uart时钟,这个和后面介绍的clk_uart_baud2指向的是一个时钟id
        if (IS_ERR(ourport->clk)) {
                pr_err("%s: Controller clock not foundn",
                                dev_name(&platdev->dev));
                ret = PTR_ERR(ourport->clk);
                goto err;
        }

        ret = clk_prepare_enable(ourport->clk);  // 时钟使能
        if (ret) {
                pr_err("uart: clock failed to prepare+enable: %dn", ret);
                clk_put(ourport->clk);
                goto err;
        }

        ret = s3c24xx_serial_enable_baudclk(ourport);  // 配置波特率
        if (ret)
                pr_warn("uart: failed to enable baudclkn");

        /* Keep all interrupts masked and cleared */
        if (s3c24xx_serial_has_interrupt_mask(port)) {
                wr_regl(port, S3C64XX_UINTM, 0xf);
                wr_regl(port, S3C64XX_UINTP, 0xf);
                wr_regl(port, S3C64XX_UINTSP, 0xf);
        }

        dbg("port: map=%pa, mem=%p, irq=%d (%d,%d), clock=%un",
            &port->mapbase, port->membase, port->irq,
            ourport->rx_irq, ourport->tx_irq, port->uartclk);

        /* reset the fifos (and setup the uart) */
        s3c24xx_serial_resetport(port, cfg);   // 寄存器配置 配置UCON、UFCON

        return 0;

err:
        port->mapbase = 0;
        return ret;
}

该函数内部流程比较简单,主要就是获取UART硬件资源,比如控制器基地址、中断编号;然后使能UART时钟,设置波特率;

这里唯一需要留意的函数就是s3c24xx_serial_enable_baudclk,该函数定义位于drivers/tty/serial/samsung.c:

static int s3c24xx_serial_enable_baudclk(struct s3c24xx_uart_port *ourport)
{
        struct device *dev = ourport->port.dev;
        struct s3c24xx_uart_info *info = ourport->info;
        char clk_name[MAX_CLK_NAME_LENGTH];
        unsigned int clk_sel;
        struct clk *clk;
        int clk_num;
        int ret;

        clk_sel = ourport->cfg->clk_sel ? : info->def_clk_sel;
        for (clk_num = 0; clk_num < info->num_clks; clk_num++) {
                if (!(clk_sel & (1 << clk_num)))  // 1<<2 & (1<< n ) n=2时,才会跳过
                        continue;

                sprintf(clk_name, "clk_uart_baud%d", clk_num);  // clk_uart_baud2
                clk = clk_get(dev, clk_name);
                if (IS_ERR(clk))
                        continue;

                ret = clk_prepare_enable(clk);  // 使能时钟
                if (ret) {
                        clk_put(clk);
                        continue;
                }

                ourport->baudclk = clk;  // 设置波特率时钟
                ourport->baudclk_rate = clk_get_rate(clk);  // 获取时钟频率,这里实际上获取到的是PCLK时钟频率50MHz
                s3c24xx_serial_setsource(&ourport->port, clk_num);

                return 0;
        }

        return -EINVAL;
}

这些串口时钟在drivers/clk/samsung/clk-s3c2410.c文件有定义:

static struct samsung_clock_alias s3c244x_common_aliases[] __initdata = {
        ALIAS(PCLK_UART0, "s3c2440-uart.0", "uart"),  // 第一个参数为时钟id,第二个参数为device设备名称,第三个参数为时钟别名
        ALIAS(PCLK_UART1, "s3c2440-uart.1", "uart"),
        ALIAS(PCLK_UART2, "s3c2440-uart.2", "uart"),
        ALIAS(PCLK_UART0, "s3c2440-uart.0", "clk_uart_baud2"),
        ALIAS(PCLK_UART1, "s3c2440-uart.1", "clk_uart_baud2"),
        ALIAS(PCLK_UART2, "s3c2440-uart.2", "clk_uart_baud2"),
        ALIAS(HCLK_CAM, NULL, "camif"),
        ALIAS(CAMIF, NULL, "camif-upll"),
};

我们以时钟别名为clk_uart_baud2的PCLK_UART0为例,其定义为:

static struct samsung_gate_clock s3c2410_common_gates[] __initdata = {
        GATE(PCLK_SPI, "spi", "pclk", CLKCON, 18, 0, 0),
        GATE(PCLK_I2S, "i2s", "pclk", CLKCON, 17, 0, 0),
        GATE(PCLK_I2C, "i2c", "pclk", CLKCON, 16, 0, 0),
        GATE(PCLK_ADC, "adc", "pclk", CLKCON, 15, 0, 0),
        GATE(PCLK_RTC, "rtc", "pclk", CLKCON, 14, 0, 0),
        GATE(PCLK_GPIO, "gpio", "pclk", CLKCON, 13, CLK_IGNORE_UNUSED, 0),
        GATE(PCLK_UART2, "uart2", "pclk", CLKCON, 12, 0, 0),
        GATE(PCLK_UART1, "uart1", "pclk", CLKCON, 11, 0, 0),
        GATE(PCLK_UART0, "uart0", "pclk", CLKCON, 10, 0, 0),
        GATE(PCLK_SDI, "sdi", "pclk", CLKCON, 9, 0, 0),
        GATE(PCLK_PWM, "pwm", "pclk", CLKCON, 8, 0, 0),
        GATE(HCLK_USBD, "usb-device", "hclk", CLKCON, 7, 0, 0),
        GATE(HCLK_USBH, "usb-host", "hclk", CLKCON, 6, 0, 0),
        GATE(HCLK_LCD, "lcd", "hclk", CLKCON, 5, 0, 0),
        GATE(HCLK_NAND, "nand", "hclk", CLKCON, 4, 0, 0),
};

我们以GATE(PCLK_UART0, "uart0", "pclk", CLKCON, 10, 0, 0),为例:

  • id表示为平台为时钟特定分配的id,这里被设置为了PCLK_UART0;
  • name表示时钟的名称,这里设置为uart0;
  • parent_name为父时钟的名称,这里设置为为pclk;
  • offset表示控制时钟开关的寄存器地址,这里设置为CLKCON;
  • bit_idx表示控制时钟开关bit位,这里设置为10;CLKCON第10位用于控制进入UART0模块的PCLK,1使能;

6.3 s3c24xx_uart_drv

我们在介绍uart_driver结构体的时候说过,每一款芯片的UART都需要去实现并定义个属于他自己的uart_driver结构,S3C2440当然也不例外,在 drivers/tty/serial/samsung.c文件定义有:

static struct uart_driver s3c24xx_uart_drv = {
        .owner          = THIS_MODULE,
        .driver_name    = "s3c2410_serial",
        .nr             = CONFIG_SERIAL_SAMSUNG_UARTS,  // 串口数量定义为4个,实际S3C2440只有3个
        .cons           = S3C24XX_SERIAL_CONSOLE,
        .dev_name       = S3C24XX_SERIAL_NAME,     // 设备名称ttySAC
        .major          = S3C24XX_SERIAL_MAJOR,    // 主设备号 204 
        .minor          = S3C24XX_SERIAL_MINOR,    // 次设备号 64
};

我们知道在tty_register_driver函数会注册driver->num(这里实际上是3个)个struct device设备:

  • 设备class为tty_calss(名称为tty) ;
  • 设备名称为driver->name+编号,会在文件系统下创建设备节点文件/dev/ttySAC0、/dev/ttySAC1、/dev/ttySAC2;
6.3.1 S3C24XX_SERIAL_CONSOLE

cons成员被设置为了S3C24XX_SERIAL_CONSOLE,关于控制台的注册我们在linux内核调试-printk中介绍:

static struct console s3c24xx_serial_console = {
        .name           = S3C24XX_SERIAL_NAME,        // 控制台名称 ttySAC
        .device         = uart_console_device,        // 控制台设备
        .flags          = CON_PRINTBUFFER,            // 标志位
        .index          = -1,                         // 索引值
        .write          = s3c24xx_serial_console_write,  // 串口输出 
        .setup          = s3c24xx_serial_console_setup,  // 设置串口波特率、发送、接收等功能 
        .data           = &s3c24xx_uart_drv,             // 串口驱动uart_driver
};


#define S3C24XX_SERIAL_CONSOLE &s3c24xx_serial_console

6.4 s3c24xx_serial_ops

在分析s3c24xx_serial_probe中我们已经将代表UART串口硬件的数据结构uart_port的ops成员设置为了&s3c24xx_serial_ops,这是struct uart_ops类型,定义了一组 UART 相关的底层的操作集,位于drivers/tty/serial/samsung.c:

static struct uart_ops s3c24xx_serial_ops = {
        .pm             = s3c24xx_serial_pm,
        .tx_empty       = s3c24xx_serial_tx_empty,
        .get_mctrl      = s3c24xx_serial_get_mctrl,
        .set_mctrl      = s3c24xx_serial_set_mctrl,
        .stop_tx        = s3c24xx_serial_stop_tx,
        .start_tx       = s3c24xx_serial_start_tx,    // 向tty设备节点写入数据会调用 
        .stop_rx        = s3c24xx_serial_stop_rx,
        .break_ctl      = s3c24xx_serial_break_ctl,
        .startup        = s3c24xx_serial_startup,     // 打开tty设备节点会调用
        .shutdown       = s3c24xx_serial_shutdown,
        .set_termios    = s3c24xx_serial_set_termios,    // 会根据启动参数的波特率,计算波特率分频寄存器UBRDIV的值,并写入寄存器
        .type           = s3c24xx_serial_type,
        .release_port   = s3c24xx_serial_release_port,
        .request_port   = s3c24xx_serial_request_port,
        .config_port    = s3c24xx_serial_config_port,
        .verify_port    = s3c24xx_serial_verify_port,
#if defined(CONFIG_SERIAL_SAMSUNG_CONSOLE) && defined(CONFIG_CONSOLE_POLL)
        .poll_get_char = s3c24xx_serial_get_poll_char,
        .poll_put_char = s3c24xx_serial_put_poll_char,
#endif
};
6.4.1 uart_open

在上一篇博客中,我们分析了应用程序打开tty设备节点的函数调用流程,从tty_open到最后的uart_open函数,uart_open函数定义在drivers/tty/serial/serial_core.c:

/*
 * Calls to uart_open are serialised by the tty_lock in
 *   drivers/tty/tty_io.c:tty_open()
 * Note that if this fails, then uart_close() _will_ be called.
 *
 * In time, we want to scrap the "opening nonpresent ports"
 * behaviour and implement an alternative way for setserial
 * to set base addresses/ports/types.  This will allow us to
 * get rid of a certain amount of extra tests.
 */
static int uart_open(struct tty_struct *tty, struct file *filp)
{
        struct uart_state *state = tty->driver_data;
        int retval;

        retval = tty_port_open(&state->port, tty, filp);
        if (retval > 0)
                retval = 0;

        return retval;
}

函数tty_port_open定义在drivers/tty/tty_port.c,内容如下:

/**
 * tty_port_open
 *
 * Caller holds tty lock.
 *
 * NB: may drop and reacquire tty lock (in tty_port_block_til_ready()) so
 * tty and tty_port may have changed state (eg., may be hung up now)
 */
int tty_port_open(struct tty_port *port, struct tty_struct *tty,
                                                        struct file *filp)
{
        spin_lock_irq(&port->lock);
        ++port->count;
        spin_unlock_irq(&port->lock);
        tty_port_tty_set(port, tty);

        /*
         * Do the device-specific open only if the hardware isn't
         * already initialized. Serialize open and shutdown using the
         * port mutex.
         */

        mutex_lock(&port->mutex);

        if (!tty_port_initialized(port)) {  // tty port初始化
                clear_bit(TTY_IO_ERROR, &tty->flags);
                if (port->ops->activate) {
                        int retval = port->ops->activate(port, tty);  //  uport->ops->startup(uport)
                        if (retval) {
                                mutex_unlock(&port->mutex);
                                return retval;
                        }
                }
                tty_port_set_initialized(port, 1);
        }
        mutex_unlock(&port->mutex);
        return tty_port_block_til_ready(port, tty, filp);
}

函数最终调用了uart_ops 结构的startup,即s3c24xx_serial_startup,该函数主要就是一些初始化工作,比如申请UART接收/发送中断,并设置中断处理函数

static int s3c24xx_serial_startup(struct uart_port *port)
{
        struct s3c24xx_uart_port *ourport = to_ourport(port);
        int ret;

        dbg("s3c24xx_serial_startup: port=%p (%08llx,%p)n",
            port, (unsigned long long)port->mapbase, port->membase);

        rx_enabled(port) = 1;

        ret = request_irq(ourport->rx_irq, s3c24xx_serial_rx_chars, 0,  // 申请中断  接收中断
                          s3c24xx_serial_portname(port), ourport);

        if (ret != 0) {
                dev_err(port->dev, "cannot get irq %dn", ourport->rx_irq);
                return ret;
        }

        ourport->rx_claimed = 1;

        dbg("requesting tx irq...n");

        tx_enabled(port) = 1;

        ret = request_irq(ourport->tx_irq, s3c24xx_serial_tx_chars, 0,  // 申请中断  发送中断
                          s3c24xx_serial_portname(port), ourport);

        if (ret) {
                dev_err(port->dev, "cannot get irq %dn", ourport->tx_irq);
                goto err;
        }

        ourport->tx_claimed = 1;

        dbg("s3c24xx_serial_startup okn");

        /* the port reset code should have done the correct
         * register setup for the port controls */

        return ret;

err:
        s3c24xx_serial_shutdown(port);
        return ret;
}
6.4.2 uart_write

同理,我们党对tty设备节点进行写入的时候,uart_write函数被调用,uart_write函数定义在drivers/tty/serial/serial_core.c:

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) {
                WARN_ON(1);
                return -EL3HLT;
        }

        port = uart_port_lock(state, flags);
        circ = &state->xmit;
        if (!circ->buf) {
                uart_port_unlock(port, flags);
                return 0;
        }

        while (port) {
                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;
        }

        __uart_start(tty);
        uart_port_unlock(port, flags);
        return ret;
}

在 uart_write 中:

  • 首先获取到了这个对应串口的 uart_state 结构;
  • 获取state->xmit 的环形buffer;
  • check当前环形buffer的剩余量c,并确定将数据 copy 到 state->xmit 的环形 buffer 中;
  • 调用__uart_start启动发送;

__uart_start函数:

static void __uart_start(struct tty_struct *tty)
{
        struct uart_state *state = tty->driver_data;
        struct uart_port *port = state->uart_port;

        if (port && !uart_tx_stopped(port))
                port->ops->start_tx(port);
}

如果环形buffer不为空,那么调用到了port->ops->start_tx,也就是uart_ops结构的start_tx,即s3c24xx_serial_start_tx:

static void s3c24xx_serial_start_tx(struct uart_port *port)
{
        struct s3c24xx_uart_port *ourport = to_ourport(port);
        struct circ_buf *xmit = &port->state->xmit;

        if (!tx_enabled(port)) {
                if (port->flags & UPF_CONS_FLOW)
                        s3c24xx_serial_rx_disable(port);

                tx_enabled(port) = 1;
                if (!ourport->dma || !ourport->dma->tx_chan)
                        s3c24xx_serial_start_tx_pio(ourport);
        }

        if (ourport->dma && ourport->dma->tx_chan) {
                if (!uart_circ_empty(xmit) && !ourport->tx_in_progress)
                        s3c24xx_serial_start_next_tx(ourport);
        }
}

这里实际上就是与UART串口寄存器相关的代码了,就不深究了。

6.4.3 s3c24xx_serial_rx_chars

s3c24xx_serial_rx_chars函数为UART接收中断处理函数,当UART有数据接收到后,触发这个函数:

static irqreturn_t s3c24xx_serial_rx_chars(int irq, void *dev_id)
{
        struct s3c24xx_uart_port *ourport = dev_id;

        if (ourport->dma && ourport->dma->rx_chan)
                return s3c24xx_serial_rx_chars_dma(dev_id);
        return s3c24xx_serial_rx_chars_pio(dev_id);
}

这个函数最终会调用tty_schedule_flip将数据搬至线路规程层,由于这个函数调用栈比较深,就不继续看下去了。

七、测试

我们给MIni2440开发板上电,内核启动后,我们首先查看/sys/class/tty目录,在该目录下我们可以看到注册的串口设备:

[root@zy:/]# ls /sys/class/tty/ttySAC* -l
lrwxrwxrwx    1 0        0                0 Jan  1 00:00 /sys/class/tty/ttySAC0 -> ../../devices/platform/s3c2440-uart.0/tty/ttySAC0
lrwxrwxrwx    1 0        0                0 Jan  1 00:00 /sys/class/tty/ttySAC1 -> ../../devices/platform/s3c2440-uart.1/tty/ttySAC1
lrwxrwxrwx    1 0        0                0 Jan  1 00:00 /sys/class/tty/ttySAC2 -> ../../devices/platform/s3c2440-uart.2/tty/ttySAC2

然后我们看一下字符设备信息/dev/ttySAC0,可以看到主设备号为204,次设备号为64:

[root@zy:/]# ls -l /dev/ttySAC0
crw-rw----    1 0        0         204,  64 Jan  1 00:00 /dev/ttySAC0

设备信息/dev/ttySAC1,可以看到主设备号为204,次设备号为65:

[root@zy:/]# ls -l /dev/ttySAC1
crw-rw----    1 0        0         204,  65 Jan  1 00:00 /dev/ttySAC1

设备信息/dev/ttySAC2,可以看到主设备号为204,次设备号为66:

[root@zy:/]# ls -l /dev/ttySAC2
crw-rw----    1 0        0         204,  66 Jan  1 00:00 /dev/ttySAC2

由于我们在内核启动命令中指定了:

console=ttySAC0,115200

也就是说我们将控制台设置为了ttySAC0终端,所以当我们向/dev/ttySAC0写入内容,也就是向控制台上写入数据,所以会在控制台上回显:

[root@zy:/]# echo "ls -l /dev" > /dev/ttySAC0
ls -l /dev

参考文章

[1]S3C2440 Linux UART 串口驱动-----1

[2]Linux UART 驱动 Part-1 (底层对接)

[3]Linux UART 驱动 Part-2 (tty 层流程)

[4]Linux串口驱动分析及移植

[5]Linux系统TTY串口驱动实例详解

[6]s3c2440 UART设备结构体的定义

内容来源于网络如有侵权请私信删除

文章来源: 博客园

原文链接: https://www.cnblogs.com/zyly/p/17185739.html

你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!

相关课程