linux驱动移植-uart设备驱动
----------------------------------------------------------------------------------------------------------------------------
内核版本: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 %d\n", 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 init\n"); } 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%08lx\n", 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 enabled\n", 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 cpu\n"); } 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) %d\n", pdev, index); if (index >= ARRAY_SIZE(s3c24xx_serial_ports)) { dev_err(&pdev->dev, "serial%d out of range\n", 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 data\n"); 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 driver\n"); return ret; } } dbg("%s: adding port\n", __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 notifier\n"); 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=%p\n", 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 control\n"); 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 uart\n"); 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 address\n"); 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 found\n", 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: %d\n", ret); clk_put(ourport->clk); goto err; } ret = s3c24xx_serial_enable_baudclk(ourport); // 配置波特率 if (ret) pr_warn("uart: failed to enable baudclk\n"); /* 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=%u\n", &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 %d\n", 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 %d\n", ourport->tx_irq); goto err; } ourport->tx_claimed = 1; dbg("s3c24xx_serial_startup ok\n"); /* 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 (底层对接)