uart tty console控制台 print相关另外一篇
1 概述
芯片内部有很多引脚,这些引脚可以复用为GPIO功能,也可以接到I2C UART等功能。
通过Pinctrl子系统来选择引脚的功能(mux function)、配置引脚:
平台用了一路uart做debug串口输出用(日常抓串口日志),其他模块(有双向通信需求)比如蓝牙、GPS可能也会用到。
串口全称叫做串行接口,通常也叫做 COM 接口,串行接口指的是数据一个一个的顺序传输,通信线路简单。使用两条线即可实现双向通信,一条用于发送,一条用于接收。串口通信距离远,但是速度相对会低,串口是一种很常用的工业接口。UART 全称是 UniversalAsynchronous Receiver/Trasmitter,也就是异步串行收发器。
全双工:同时收发,发送器向接收器发数据的同时,接收器也可以向发送器发送数据。
UART 作为串口的一种,其工作原理也是将数据一位一位的进行传输,发送和接收各用一 条线,因此通过 UART 接口与外界相连最少只需要三条线:TXD(发送)、RXD(接收)和 GND(地 线)(没有时钟线)。
时序介绍
空闲位:数据线在空闲状态的时候为逻辑“1”状态,也就是高电平,表示没有数据线空闲, 没有数据传输。
停止位:数据传输完成标志位,停止位的位数可以选择 1 位、1.5 位或 2 位高电平,一般都 选择 1 位停止位。
起始位:当要传输数据的时候先传输一个逻辑“0”,也就是将数据线拉低,表示开始数据 传输。
因为收发双方是异步通信,意思就是双方没有一个基准时钟,各自维护自己的时钟,那么即使双方时钟误差极小,那么数据量一大(即一次发送的持续时长过长),数据量就错乱了,导致后续的全部数据都错乱,而发送少的数据位就经历一遍这个过程,就相当于重新开始计数,就可以减少这个误差带来的问题。
数据位:数据位就是实际要传输的数据,数据位数可选择 5~8 位,我们一般都是按照字节 传输数据的,一个字节 8 位,因此数据位通常是 8 位的。低位在前,先传输,高位最后传输。
奇偶校验位:这是对数据中“1”的个数进行奇偶校验用的,可以不使用奇偶校验功能。
波特率:波特率就是 UART 数据传输的速率,也就是每秒传输的数据位数,一般选择 9600、 19200、115200(由于起始和停止位占了两个比特,约115200/10=11520字节,约11KB每秒) 等。
uart的电平标准
UART只规定了收发的时序,即“先发起始位,再发数据位,校验位,最后发停止位”;只规定了高低电平,但是并没有规定高电平指的是多少伏,低电平指的是多少伏。
UART 一般的接口电平有 TTL 和 RS-232,RS-422,RS-485,有对应的电平转换芯片。
2 Qualcomm uart pinctrl配置
软件(UART, SPI, I2C, I3C)支持文档
QUP
高通统一的外设(Qualcomm Universal Peripheral,QUP)QUP v3是一个可编程模块,支持多种串行接口,如UART、SPI、I2C和I3C。它支持从系统中的多个硬件实体进行访问。每个实体都有自己的执行环境(EE)、一个单独的地址空间和一条中断线。
uart设备树节点
引脚有自身相关寄存器,用来设置复用功能、拉高拉低等配置
uart控制器另外的有另外的寄存器,用来配置波特率、中断、奇偶校验位、fifo、dma等
/kernel_platform/qcom/proprietary/devicetree/qcom/pineapple-qupv3.dtsi
基地址0x89c000也是对应的,是uart寄存器的基地址。
qup2的第8组串行接口的基地址 中断号 对应gpio pin脚
关于(UART)的设备树配置
scripts/dtc/include-prefixes/arm64/vendor/qcom/pineapple.dtsi
Uart 对应pinctrl子节点
/include-prefixes/arm64/vendor/qcom/pineapple-pinctrl.dtsi
&tlmm {
qupv3_se15_2uart_pins: qupv3_se15_2uart_pins {
qupv3_se15_2uart_tx_active: qupv3_se15_2uart_tx_active {
mux {
pins = "gpio30";
function = "qup2_se7_l2";
};
config {
pins = "gpio30";
drive-strength = <2>;
bias-disable;
};
};
qup2_se7_l2和qup2_se7_l3在datasheet里对应位置
qup共有16组硬件串行接口,每个串行接口最多有7个pin脚,gpio30对应qup2_se7_l2指的是第二组第8个串行接口可以用作uart的L2引脚(uart_tx),这里uart只用了gpio30和gpio31作为收发,有四个引脚是因为也可以配置为spi(spi需要四个引脚)
Uart console驱动文件
compatible 属性 qcom,geni-debug-uart ,在驱动文件(kernel_platform/msm-kernel/drivers/tty/serial/qcom_geni_serial.c)中对应
本质上是一个 platform 驱动,设备树所使用的匹配表
static const struct of_device_id qcom_geni_serial_match_table[] = { { .compatible = "qcom,geni-debug-uart", }, { .compatible = "qcom,geni-uart", }, {} };
上面完成的是注册了一个uart 串口设备,在文件系统里以 /dev/ttyMSM0呈现
3 UART主要数据结构介绍
其实uart就是收发的接口,初始化好收发pin脚,设置好中断 波特率 电平时序 收发中断函数就可以用了。linux将debug uart封装为了tty设备,方面上层用户调用。所以数据结构中有许多关于tty掺杂在一起。
首先要了解驱动框架涉及到的数据结构
Linux UART驱动框架如下图所示,UART在用户空间会生成名为/dev/ttyS*的设备(ttyS名称是驱动给出的,可能因驱动而异),应用程序通过读写tty设备就可以进行UART通信。Linux内核实现了tty层和serial core,serial core会调用tty层的接口,注册tty driver,同时提供了底层uart的抽象:
定义struct uart_driver、struct uart_port、struct uart_ops等结构来描述底层uart驱动;
提供相应接口 uart_register_driver、uart_add_one_port 等。
uart软件框架如下
struct uart_driver
每一款SoC的UART都需要去实现并定义个属于它自己uart_driver结构,uart_driver包含了串口设备名,串口驱动名,主次设备号,串口控制台(可选))等信息,还封装了tty_driver,定义在msm-kernel/include/linux/serial_core.h (底层串口驱动无需关心tty_driver)
struct uart_driver {
struct module *owner; /*拥有该uart_driver的模块,一般为THIS_MODULE*/
const char *driver_name; /*驱动串口名,串口设备名以驱动名为基础*/
const char *dev_name; /*串口设备名*/
int major; /*主设备号*/
int minor; /*次设备号*/
int nr; /*该uart_driver支持的串口数该驱动支持的串口数量;比如,某某芯片的介绍:”多达3个UART接口“,那么这个nr指的就是这个3*/
struct console *cons; /*其对应的console,若该uart_driver支持serial console,
*否则为NULL*/
/*
* these are private; the low level driver should not
* touch these; they should be initialised to NULL
*/
struct uart_state *state; /*下层,串口驱动层;指向一个数组,对应了芯片的每一个UART(比如 uart_state[0] 对应了芯片的 UART0以此类推),需要初始化为NULL;*/
struct tty_driver *tty_driver; /*tty相关,上层,tty驱动层;需要初始化为NULL;*/
ANDROID_KABI_RESERVE(1);
};
struct uart_state
每一个uart端口对应着一个uart_state,该结构体将uart_port与对应的circ_buf联系起来。uart_state有两个成员在底层串口驱动会用到:xmit和port。用户空间程序通过串口发送数据时,上层驱动将用户数据保存在xmit(串口待发送数据环形缓冲区);而串口发送中断处理函数就是通过xmit获取到用户数据并将它们发送出去。串口接收中断处理函数需要通过port将接收到的数据传递给线路规程层。
struct uart_port
uart_port结构体里是关于与UART串口硬件相关的信息,需要芯片厂家定义自己的 uart_port 结构并填充它,该结构定义在msm-kernel/include/linux/serial_core.h
uart_port用于描述串口端口的I/O端口或I/O内存地址、FIFO大小、端口类型、串口时钟等信息。实际上,一个uart_port实现对应一个物理串口设备。
struct uart_port {
spinlock_t lock; /* port lock */
unsigned long iobase; /* in/out[bwl] I/O端口寄存器基地址,物理地址;*/
unsigned char __iomem *membase; /* read/write[bwl] */I/O端口寄存器基地址,虚拟地址;
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,
const 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 ktermios *termios,
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 */FIFO缓冲区大小;
unsigned char x_char; /* xon/xoff char */
unsigned char regshift; /* reg offset shift */寄存器位移;
unsigned char iotype; /* io access style */I/O访问方式;
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_state;
struct uart_icount icount; /* statistics */串口信息计数器;
struct console *cons; /* struct console, if any */
/* flags must be updated while holding port mutex */
upf_t flags;
/*
* 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..ASYNCB_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) BIT_ULL(19))
/* Port has hardware-assisted h/w flow control */
#define UPF_AUTO_CTS ((__force upf_t) BIT_ULL(20))
#define UPF_AUTO_RTS ((__force upf_t) BIT_ULL(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) BIT_ULL(22))
#define UPF_CONS_FLOW ((__force upf_t) BIT_ULL(23))
#define UPF_SHARE_IRQ ((__force upf_t) BIT_ULL(24))
#define UPF_EXAR_EFR ((__force upf_t) BIT_ULL(25))
#define UPF_BUG_THRE ((__force upf_t) BIT_ULL(26))
/* The exact UART type is known and should not be probed. */
#define UPF_FIXED_TYPE ((__force upf_t) BIT_ULL(27))
#define UPF_BOOT_AUTOCONF ((__force upf_t) BIT_ULL(28))
#define UPF_FIXED_PORT ((__force upf_t) BIT_ULL(29))
#define UPF_DEAD ((__force upf_t) BIT_ULL(30))
#define UPF_IOREMAP ((__force upf_t) BIT_ULL(31))
#define UPF_FULL_PROBE ((__force upf_t) BIT_ULL(32))
#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 frame_time; /* frame timing in ns */
unsigned int type; /* port type */端口类型;
const struct uart_ops *ops;//串口端口的操作函数;
unsigned int custom_divisor;
unsigned int line; /* port index */端口索引,通过drv->state + uport->line可以获取到当前UART对应的uart_state;
unsigned int minor;
resource_size_t mapbase; /* for ioremap */I/O内存物理基地址;
resource_size_t mapsize;
struct device *dev; /* parent device */设备模型中的设备,一般存放的是platform device中device;
unsigned long sysrq; /* sysrq timeout */
unsigned int sysrq_ch; /* char for sysrq */
unsigned char has_sysrq;
unsigned char sysrq_seq; /* index in sysrq_toggle_seq */
unsigned char hub6; /* this should be in the 8250 driver */
unsigned char suspended;
unsigned char console_reinit;
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_rs485 rs485_supported; /* Supported mask for serial_rs485 */
struct gpio_desc *rs485_term_gpio; /* enable RS485 bus termination */
struct serial_iso7816 iso7816;
void *private_data; /* generic platform data pointer */
ANDROID_KABI_RESERVE(1);
ANDROID_KABI_RESERVE(2);
};
uart_port的成员需要在驱动中添加实现,例如uport->membase
struct uart_ops
uart_ops定义了UART硬件相关相关的操作集,芯片厂家需要进行硬件寄存器级的适配其中各个操作,该结构定义在msm-kernel/include/linux/serial_core.h
struct uart_ops {
unsigned int (*tx_empty)(struct uart_port *);串口的Tx FIFO是否为空;
void (*set_mctrl)(struct uart_port *, unsigned int mctrl);设置串口modem控制;
unsigned int (*get_mctrl)(struct uart_port *);获取串口modem控制;
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 (*start_rx)(struct uart_port *);开始串口数据接收;;
void (*enable_ms)(struct uart_port *);使能modem的状态信号;
void (*break_ctl)(struct uart_port *, int ctl);设置break信号;
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,
const struct ktermios *old);设置串口参数;
void (*set_ldisc)(struct uart_port *, struct ktermios *);设置线路规程;
void (*pm)(struct uart_port *, unsigned int state,串口电源管理;
unsigned int oldstate);
const char *(*type)(struct uart_port *);
void (*release_port)(struct uart_port *);
int (*request_port)(struct uart_port *);申请必要的IO端口/IO内存资源,必要时还可以重新映射串口端口;
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
ANDROID_KABI_RESERVE(1);
ANDROID_KABI_RESERVE(2);
};
qcom_geni_uart_pops 中的函数基本都是和8650 的 UART 寄存器打交道的
uart_driver、uart_state、uart_port之间的关系
如果有多个uart的话,分别注册实现各自的数据结构 操作函数等,如下图所示:
4 UART驱动API
linux内核提供了一组函数用于操作uart_driver和uart_port。
4.1,uart_driver设备驱动注册过程
uart_register_driver用于注册UART设备,函数定义在msm-kernel/drivers/tty/serial/qcom_geni_serial.c
首先我们找到驱动的入口函数module_init(qcom_geni_serial_init),在函数qcom_geni_serial_init中调用uart_register_driver向内核注册了一个驱动.
uart_register_driver
msm-kernel/drivers/tty/serial/serial_core.c
注册过程主要做了以下操作:该函数的入参是struct uart_driver:
首先初始化uart_driver的成员:
根据驱动支持的串口数量,动态申请内存,初始化state成员,用来存放驱动所支持的串口(端口)的物理信息;
根据驱动支持的串口数量,分配tty驱动,初始化tty_driver成员;
初始化tty驱动:
遍历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链表中。与用户空间打交道的工作完全交给tty_driver。
uart_register_driver再调用tty_register_driver,从而注册了ttyMAM0设备
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;
/*分配一个可注册的tty_driver,并告知设备数据量*/
normal = tty_alloc_driver(drv->nr, TTY_DRIVER_REAL_RAW |// 分配tty驱动
TTY_DRIVER_DYNAMIC_DEV);
if (IS_ERR(normal)) {
retval = PTR_ERR(normal);
goto out_kfree;
}
drv->tty_driver = normal;
// 初始化tty驱动成员
normal->driver_name = drv->driver_name;//设置驱动名称driver_name;
normal->name = drv->dev_name;//设置设备名称name;
normal->major = drv->major;//设置主设备号major;
normal->minor_start = drv->minor;//设置开始的次设备号minor_start;
normal->type = TTY_DRIVER_TYPE_SERIAL;//设置tty驱动类型为TTY_DRIVER_TYPE_SERIAL;
normal->subtype = SERIAL_TYPE_NORMAL;//设置tty驱动子类型为SERIAL_TYPE_NORMAL;
normal->init_termios = tty_std_termios;//设置初始化线路设置iit_termios;
normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;//
normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
normal->driver_state = drv;
tty_set_operations(normal, &uart_ops);//调用tty_set_operations将uart_ops这一个tty设备的操作函数ops集设置到了tty驱动中//设置tty驱动操作集合ops为uart_ops,类型为struct tty_operations;//操作tty设备的函数
/*
* Initialise the UART state(s).
*/
for (i = 0; i < drv->nr; i++) {//遍历state数组,依次初始化为一个uart_state:
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);/*注册struct tty_driver,向内核注册了tty驱动*/// 注册tty驱动
if (retval >= 0)
return retval;
for (i = 0; i < drv->nr; i++)
tty_port_destroy(&drv->state[i].port);
tty_driver_kref_put(normal);
out_kfree:
kfree(drv->state);
out:
return retval;
}
EXPORT_SYMBOL(uart_register_driver);
tty_register_driver注册
uart_register_driver里包含了tty_register_driver.
int tty_register_driver(struct tty_driver *driver)/*注册struct tty_driver,最终要注册的实体*/
{
int error;
int i;
dev_t dev;
struct device *d;
if (!driver->major) { /* 如果没有主设备号则申请 */
error = alloc_chrdev_region(&dev, driver->minor_start,
driver->num, driver->name);
if (!error) {
driver->major = MAJOR(dev);
driver->minor_start = MINOR(dev);
}
} else {
dev = MKDEV(driver->major, driver->minor_start);
error = register_chrdev_region(dev, driver->num, driver->name);
}/*将char_device_struct变量注册到内核*/
if (error < 0)
goto err;
if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {
error = tty_cdev_add(driver, dev, 0, driver->num);/* 创建添加字符设备,使用 tty_fops */
if (error)
goto err_unreg_char;
}
mutex_lock(&tty_mutex);
list_add(&driver->tty_drivers, &tty_drivers);/* 将该 driver->tty_drivers 添加到全局链表 tty_drivers */
mutex_unlock(&tty_mutex);
if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
for (i = 0; i < driver->num; i++) {
d = tty_register_device(driver, i, NULL);
if (IS_ERR(d)) {
error = PTR_ERR(d);
goto err_unreg_devs;
}
}
}
proc_tty_register_driver(driver); /* proc 文件系统注册driver */
driver->flags |= TTY_DRIVER_INSTALLED;
return 0;
err_unreg_devs:
for (i--; i >= 0; i--)
tty_unregister_device(driver, i);
mutex_lock(&tty_mutex);
list_del(&driver->tty_drivers);
mutex_unlock(&tty_mutex);
err_unreg_char:
unregister_chrdev_region(dev, driver->num);
err:
return error;
}
EXPORT_SYMBOL(tty_register_driver);
tty_operations uart_ops
驱动里有好几个ops操作函数集,这里不是操作底层串口的,是操作tty设备的。
uart_ops定义在msm-kernel/drivers/tty/serial/serial_core.c
在应用层打开一个tty设备节点,比如串口设备/dev/ttyMSM0,就可以对串口做一些配置、读写的操作;
这里用户空间的任何open、write、read等操作,直接对应到了tty 层的注册到字符设备的file_operation,也就是tty_fops;
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, // 当termios设置被改变时由tty核心调用
.set_ldisc = uart_set_ldisc,// 设置线路规程函数
.stop = uart_stop,
.start = uart_start,
.hangup = uart_hangup,// 挂起函数,当驱动挂起tty设备时调用
.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,// 获得当前tty的线路规程的设置
.tiocmset = uart_tiocmset,// 设置当前tty线路规程的设置
.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_fops成员函数执行过程中就会调用tty_operations中相应的函数。注册 uart_driver 实际上是注册 tty_driver,因此与用户空间打交道的工作完全交给了 tty_driver ,
打个比如,应用层对设备节点 "/dev/ttyMAM0" 设备节点调用open的时候,触发的流程如下:
tty_fops->tty_open():;
tty->ops->open(tty, filp):其实就是调用到了 struct tty_operations uart_ops 中的 uart_open;
4.2,uart_add_one_port(添加uart_port)
在 uart_register_driver 调用成功后,可以说,我们已经对uart_drvier进行了注册,并且实现了这个结构体中的绝大多数成员了。
uart_state也会在register_uart_driver的过程中分配空间,但是它里面真正设置硬件相关的东西是uart_state->uart_port ,这个uart_port 是probe函数调用 uart_add_one_port 来添加的。uart_add_one_port函数定义在msm-kernel/drivers/tty/serial/serial_core.c
uart_add_one_port此接口用于注册一个uart port 到uart driver上,通过注册,uart driver就可以访问对应的uart port,进行数据收发。该接口在uart driver中的probe函数(设备树与驱动中的compatible匹配以后)调用,必须保证晚于uart_register_drver的注册过程。 uart driver在调用接口前,要手动设置uart_port的操作uart_ops,使得通过调用uart_add_one_port接口后驱动完成硬件的操作接口注册。uart添加port流程如图
这个函有两个参数:
drv:为哪个uart_driver 赋予一个uart_port;
uport:具体的 uart port;
一个 uart_port 代表了一个UART物理硬件,有多个UART的话,就要调用多次这个接口!
之前那个uart_state结构有一个 uart_port ,这里就是把这个uport 赋值给对应的这个state的uart_port成员。
假如我们 8650有3个串口,那么就需要填充3个uart_port ,并且通过 uart_add_one_port 添加uart_driver->uart_state->uart_port 中去。
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;
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;// 这一步才是重点,为uart_driver 赋予一个uart_port
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 in use as a console then the spinlock is already
* initialised.
*/
if (!uart_console_enabled(uport))
uart_port_spin_lock_init(uport);
if (uport->cons && uport->dev)
of_console_check(uport->dev->of_node, uport->cons->name, uport->line);
tty_port_link_device(port, drv->tty_driver, 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;
}
EXPORT_SYMBOL(uart_add_one_port);
5 UART platform设备
要实现一个串口驱动,我们需要定义以下数据结构:
uart_driver结构,一个;
uart_port结构,多个,取决于串口的数量;
uart_ops串口的操作集,可能一个,也可能多个;
同时需要调用的API有:
uart_register_driver,一次;
uart_add_one_port,多次;
以8650 UART驱动为例,对其源码进行分析,其采用的也是platform设备驱动模型。
Uart 设备树platform device
Uart的寄存器以及具体的gpio30 31的引脚是两类寄存器,一个是配置uart具体功能的,一个是用来配置引脚复用的。
Uart 设备树platform driver
就是注册了一个platform驱动,然后当驱动platform driver和设备platform device匹配之后,调用驱动里面的probe函数,就是static int qcom_geni_serial_probe(struct platform_device *pdev)函数函数的前半部分代码主要是对UART设备进行一些初始化工作,最终的目的是为UART设备分配一个uart_port结构体,
函数内部首先是申请了一个qcom_geni_serial_port 结构体,这个qcom_geni_serial_port 结构体里面还有个uart_port成员,显示解析了设备树获取了寄存器信息,然后设置添加这个uart_port结构体。
static int qcom_geni_serial_probe(struct platform_device *pdev)
{
int ret = 0;
int line;
struct qcom_geni_serial_port *port;
struct uart_port *uport;
struct resource *res;
int irq;
bool console = false;
struct uart_driver *drv;
if (of_device_is_compatible(pdev->dev.of_node, "qcom,geni-debug-uart"))
console = true;
if (console) {
drv = &qcom_geni_console_driver;
line = of_alias_get_id(pdev->dev.of_node, "serial");
} else {
drv = &qcom_geni_uart_driver;
line = of_alias_get_id(pdev->dev.of_node, "serial");
if (line == -ENODEV) /* compat with non-standard aliases */
line = of_alias_get_id(pdev->dev.of_node, "hsuart");
}
port = get_port_from_line(line, console);//获取uport 操作uart寄存器的ops接口函数也是在这里添加的
if (IS_ERR(port)) {
dev_err(&pdev->dev, "Invalid line %d\n", line);
return PTR_ERR(port);
}
uport = &port->uport;
/* Don't allow 2 drivers to access the same port */
if (uport->private_data)
return -ENODEV;
uport->dev = &pdev->dev;
port->se.dev = &pdev->dev;
port->se.wrapper = dev_get_drvdata(pdev->dev.parent);
port->se.clk = devm_clk_get(&pdev->dev, "se");
if (IS_ERR(port->se.clk)) {
ret = PTR_ERR(port->se.clk);
dev_err(&pdev->dev, "Err getting SE Core clk %d\n", ret);
return ret;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//解析了设备树获取了寄存器信息
if (!res)
return -EINVAL;
uport->mapbase = res->start;
port->tx_fifo_depth = DEF_FIFO_DEPTH_WORDS;
port->rx_fifo_depth = DEF_FIFO_DEPTH_WORDS;
port->tx_fifo_width = DEF_FIFO_WIDTH_BITS;
if (!console) {
port->rx_fifo = devm_kcalloc(uport->dev,
port->rx_fifo_depth, sizeof(u32), GFP_KERNEL);
if (!port->rx_fifo)
return -ENOMEM;
}
ret = geni_icc_get(&port->se, NULL);
if (ret)
return ret;
port->se.icc_paths[GENI_TO_CORE].avg_bw = GENI_DEFAULT_BW;
port->se.icc_paths[CPU_TO_GENI].avg_bw = GENI_DEFAULT_BW;
/* Set BW for register access */
ret = geni_icc_set_bw(&port->se);
if (ret)
return ret;
port->name = devm_kasprintf(uport->dev, GFP_KERNEL,
"qcom_geni_serial_%s%d",
uart_console(uport) ? "console" : "uart", uport->line);
if (!port->name)
return -ENOMEM;
irq = platform_get_irq(pdev, 0);//获取中断,接收数据需要用到接收中断
if (irq < 0)
return irq;
uport->irq = irq;
uport->has_sysrq = IS_ENABLED(CONFIG_SERIAL_QCOM_GENI_CONSOLE);
if (!console)
port->wakeup_irq = platform_get_irq_optional(pdev, 1);
if (of_property_read_bool(pdev->dev.of_node, "rx-tx-swap"))
port->rx_tx_swap = true;
if (of_property_read_bool(pdev->dev.of_node, "cts-rts-swap"))
port->cts_rts_swap = true;
ret = devm_pm_opp_set_clkname(&pdev->dev, "se");
if (ret)
return ret;
/* OPP table is optional */
ret = devm_pm_opp_of_add_table(&pdev->dev);
if (ret && ret != -ENODEV) {
dev_err(&pdev->dev, "invalid OPP table in device tree\n");
return ret;
}
port->private_data.drv = drv;
uport->private_data = &port->private_data;
platform_set_drvdata(pdev, port);
port->handle_rx = console ? handle_rx_console : handle_rx_uart;
irq_set_status_flags(uport->irq, IRQ_NOAUTOEN);
ret = devm_request_irq(uport->dev, uport->irq, qcom_geni_serial_isr,
IRQF_TRIGGER_HIGH, port->name, uport);
if (ret) {
dev_err(uport->dev, "Failed to get IRQ ret %d\n", ret);
return ret;
}
ret = uart_add_one_port(drv, uport);//注册uart_port,这里的ourport->port就是上半段代码初始化得到的uart_port;
if (ret)
return ret;
if (port->wakeup_irq > 0) {
device_init_wakeup(&pdev->dev, true);
ret = dev_pm_set_dedicated_wake_irq(&pdev->dev,
port->wakeup_irq);
if (ret) {
device_init_wakeup(&pdev->dev, false);
uart_remove_one_port(drv, uport);
return ret;
}
}
return 0;
}
struct qcom_geni_serial_port {
struct uart_port uport;//先初始化 然后再uart_add_one_port
struct geni_se se;
const char *name;
u32 tx_fifo_depth;
u32 tx_fifo_width;
u32 rx_fifo_depth;
bool setup;
int (*handle_rx)(struct uart_port *uport, u32 bytes, bool drop);
unsigned int baud;
void *rx_fifo;
u32 loopback;
bool brk;
unsigned int tx_remaining;
int wakeup_irq;
bool rx_tx_swap;
bool cts_rts_swap;
struct qcom_geni_private_data private_data;
};
然后uart_port结构体里面的ops操作结构体对应的是uart_ops结构体,这个uart_ops结构体里面对应的就是一些8650自己的一些操作函数了,这就具体到底层硬件操作函数了
probe函数中port = get_port_from_line(line, console);-----qcom_geni_console_port----qcom_geni_console_pops
具体的操作uart寄存器的接口函数
重点看一下struct uart_ops定义,定义了UART硬件能完成的操作。UART控制器驱动会定义uart_ops结构并实现对应的函数功能,当然并不是所有函数都需要实现。
位置 msm-kernel/drivers/tty/serial/qcom_geni_serial.c
static const struct uart_ops qcom_geni_uart_pops = {
.tx_empty = qcom_geni_serial_tx_empty,//查询TX FIFO是否为空,为空则返回1,否则返回0
.stop_tx = qcom_geni_serial_stop_tx,//停止发送
.start_tx = qcom_geni_serial_start_tx,//启动发送
.stop_rx = qcom_geni_serial_stop_rx,//停止接收
.set_termios = qcom_geni_serial_set_termios,//设置UART属性,比如波特率、数据位数、停止位数、奇偶校验、流控等。
.startup = qcom_geni_serial_startup,//开启UART
.request_port = qcom_geni_serial_request_port,
.config_port = qcom_geni_serial_config_port,
.shutdown = qcom_geni_serial_shutdown,//关闭UART
.type = qcom_geni_serial_get_type,
.set_mctrl = qcom_geni_serial_set_mctrl,
.get_mctrl = qcom_geni_serial_get_mctrl,
.pm = qcom_geni_serial_pm,
};
Linux上串口的常规操作工具
Linux上,除了一些串口工具比如minicom, cutecom可以操作串口外,也可以用如下命令行工具进行基本的操作。
6 uart底层发送接收流程
主要看一下具体是怎么操作寄存器发送接收数据。
数据底层发送流程
static int uart_write(struct tty_struct *tty,
const unsigned char *buf, int count)
{
struct uart_state *state = tty->driver_data;//首先获取到了这个对应串口的 uart_state 结构;
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;//获取用户state->xmit 的buffer;
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);//将tty设备用户下发的数据拷贝到buf中,check当前环形buffer的剩余量c,并确定将数据 copy 到 state->xmit 的环形 buffer 中;
circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1);//发送对应的是head,buf中的数据写入循环缓冲中,并修改了head的位置
buf += c;
count -= c;
ret += c;
}
__uart_start(tty);//调用底层串口发送数据,start_tx函数只是用来打开发送中断,而真正进行发送操作的却是在中断处理函数中
uart_port_unlock(port, flags);
return ret;
}
drivers/tty/serial/qcom_geni_serial.c
start_tx函数只是用来打开发送中断,而真正进行发送操作的却是在中断处理函数中
static void qcom_geni_serial_start_tx(struct uart_port *uport)
{
u32 irq_en;
u32 status;
status = readl(uport->membase + SE_GENI_STATUS);
if (status & M_GENI_CMD_ACTIVE)
return;
if (!qcom_geni_serial_tx_empty(uport))//判断循环缓冲是否为空,或者串口不允许发送,则把中断关闭
return;
irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN);
irq_en |= M_TX_FIFO_WATERMARK_EN | M_CMD_DONE_EN;
writel(DEF_TX_WM, uport->membase + SE_GENI_TX_WATERMARK_REG);
writel(irq_en, uport->membase + SE_GENI_M_IRQ_EN);//应该是触发发送中断的地方
}
uart中断处理函数
具体通过发送中断把数据通过串口发出去
uart中断处理函数(static irqreturn_t qcom_geni_serial_isr(int isr, void *dev))在static int qcom_geni_serial_probe(struct platform_device *pdev)进行了注册
static irqreturn_t qcom_geni_serial_isr(int isr, void *dev)
{
u32 m_irq_en;
u32 m_irq_status;
u32 s_irq_status;
u32 geni_status;
struct uart_port *uport = dev;
bool drop_rx = false;
struct tty_port *tport = &uport->state->port;
struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
if (uport->suspended)
return IRQ_NONE;
spin_lock(&uport->lock);
m_irq_status = readl(uport->membase + SE_GENI_M_IRQ_STATUS);
s_irq_status = readl(uport->membase + SE_GENI_S_IRQ_STATUS);
geni_status = readl(uport->membase + SE_GENI_STATUS);
m_irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN);
writel(m_irq_status, uport->membase + SE_GENI_M_IRQ_CLEAR);
writel(s_irq_status, uport->membase + SE_GENI_S_IRQ_CLEAR);
if (WARN_ON(m_irq_status & M_ILLEGAL_CMD_EN))
goto out_unlock;
if (s_irq_status & S_RX_FIFO_WR_ERR_EN) {
uport->icount.overrun++;
tty_insert_flip_char(tport, 0, TTY_OVERRUN);
}
if (m_irq_status & m_irq_en & (M_TX_FIFO_WATERMARK_EN | M_CMD_DONE_EN))
qcom_geni_serial_handle_tx(uport, m_irq_status & M_CMD_DONE_EN,//数据发送中断函数
geni_status & M_GENI_CMD_ACTIVE);
if (s_irq_status & S_GP_IRQ_0_EN || s_irq_status & S_GP_IRQ_1_EN) {
if (s_irq_status & S_GP_IRQ_0_EN)
uport->icount.parity++;
drop_rx = true;
} else if (s_irq_status & S_GP_IRQ_2_EN ||
s_irq_status & S_GP_IRQ_3_EN) {
uport->icount.brk++;
port->brk = true;
}
if (s_irq_status & S_RX_FIFO_WATERMARK_EN ||
s_irq_status & S_RX_FIFO_LAST_EN)
qcom_geni_serial_handle_rx(uport, drop_rx);//数据接收中断函数
out_unlock:
uart_unlock_and_check_sysrq(uport);
return IRQ_HANDLED;
}
循环缓冲circ_buf是linux内核定义的一种数据结构,收入n个数据,head=head+n,发送n个数据,tail=tail+n,相当于一个循环队列,先进先出。
write函数中的数据并不是直接写入寄存器中,而是先写入循环缓冲。具体实现是在uart_write函数中。
为了处理器的工作效率,发送出的数据也不是直接到达相应的硬件,而是存入发送FIFO中,当处理器闲置时在进行发送。
一次发送一个字节的数据
static void qcom_geni_serial_handle_tx(struct uart_port *uport, bool done,bool active)
{
struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
struct circ_buf *xmit = &uport->state->xmit;
size_t avail;
size_t remaining;
size_t pending;
int i;
u32 status;
u32 irq_en;
unsigned int chunk;
int tail;
status = readl(uport->membase + SE_GENI_TX_FIFO_STATUS);
/* Complete the current tx command before taking newly added data 先把当前的发送完*/
if (active)
pending = port->tx_remaining;
else
pending = uart_circ_chars_pending(xmit);
/* All data has been transmitted and acknowledged as received */
if (!pending && !status && done) {
qcom_geni_serial_stop_tx(uport);//数据发送完毕,停止发送
goto out_write_wakeup;
}
avail = port->tx_fifo_depth - (status & TX_FIFO_WC);
avail *= BYTES_PER_FIFO_WORD;
tail = xmit->tail;
chunk = min(avail, pending);
if (!chunk)
goto out_write_wakeup;
if (!port->tx_remaining) {
qcom_geni_serial_setup_tx(uport, pending);
port->tx_remaining = pending;
irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN);
if (!(irq_en & M_TX_FIFO_WATERMARK_EN))
writel(irq_en | M_TX_FIFO_WATERMARK_EN,
uport->membase + SE_GENI_M_IRQ_EN);
}
remaining = chunk;
//处理数据发送的地方
for (i = 0; i < chunk; ) {
unsigned int tx_bytes;
u8 buf[sizeof(u32)];
int c;
memset(buf, 0, sizeof(buf));
tx_bytes = min_t(size_t, remaining, BYTES_PER_FIFO_WORD);
for (c = 0; c < tx_bytes ; c++) {
buf[c] = xmit->buf[tail++];
tail &= UART_XMIT_SIZE - 1;
}
iowrite32_rep(uport->membase + SE_GENI_TX_FIFOn, buf, 1);//将数据写入发送寄存器或fifo
i += tx_bytes;
uport->icount.tx += tx_bytes;
remaining -= tx_bytes;
port->tx_remaining -= tx_bytes;
}
xmit->tail = tail;
/*
* The tx fifo watermark is level triggered and latched. Though we had
* cleared it in qcom_geni_serial_isr it will have already reasserted
* so we must clear it again here after our writes.
*/
writel(M_TX_FIFO_WATERMARK_EN,
uport->membase + SE_GENI_M_IRQ_CLEAR);
out_write_wakeup:
if (!port->tx_remaining) {
irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN);
if (irq_en & M_TX_FIFO_WATERMARK_EN)
writel(irq_en & ~M_TX_FIFO_WATERMARK_EN,
uport->membase + SE_GENI_M_IRQ_EN);
}
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
uart_write_wakeup(uport);
}
msm-kernel/include/linux/soc/qcom/geni-se.h
数据底层接收
static void qcom_geni_serial_handle_rx(struct uart_port *uport, bool drop)
{
u32 status;
u32 word_cnt;
u32 last_word_byte_cnt;
u32 last_word_partial;
u32 total_bytes;
struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
status = readl(uport->membase + SE_GENI_RX_FIFO_STATUS);
word_cnt = status & RX_FIFO_WC_MSK;
last_word_partial = status & RX_LAST;
last_word_byte_cnt = (status & RX_LAST_BYTE_VALID_MSK) >>
RX_LAST_BYTE_VALID_SHFT;
if (!word_cnt)
return;
total_bytes = BYTES_PER_FIFO_WORD * (word_cnt - 1);
if (last_word_partial && last_word_byte_cnt)
total_bytes += last_word_byte_cnt;
else
total_bytes += BYTES_PER_FIFO_WORD;
port->handle_rx(uport, total_bytes, drop);
}
static int handle_rx_console(struct uart_port *uport, u32 bytes, bool drop)
{
u32 i;
unsigned char buf[sizeof(u32)];
struct tty_port *tport;
struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
tport = &uport->state->port;
for (i = 0; i < bytes; ) {
int c;
int chunk = min_t(int, bytes - i, BYTES_PER_FIFO_WORD);
ioread32_rep(uport->membase + SE_GENI_RX_FIFOn, buf, 1);
i += chunk;
if (drop)
continue;
for (c = 0; c < chunk; c++) {
int sysrq;
uport->icount.rx++;
if (port->brk && buf[c] == 0) {//判断是否接收完了
port->brk = false;
if (uart_handle_break(uport))
continue;
}
sysrq = uart_prepare_sysrq_char(uport, buf[c]);
if (!sysrq)
tty_insert_flip_char(tport, buf[c], TTY_NORMAL);
}
}
if (!drop)
tty_flip_buffer_push(tport);
return 0;
}
7 uart在linux文件系统的一些节点信息
一些uart具体设置信息可以在文件节点中查看。
下面是tty drivers,四列依次为:
driver_name
设备节点前缀
主设备号
次设备号范围 类型
/dev/ttyMSM为我们抓串口log用的uart
Console tty下一篇培训准备
tty终端设备 console控制台 print 等uart相关的,后面另外做一份培训。
参考链接
基于Linux的tty架构及UART驱动详解 https://zhuanlan.zhihu.com/p/355833608
linux驱动移植-uart设备驱动 https://www.cnblogs.com/zyly/p/17185739.html
tty初探 — uart驱动框架分析 https://zhuanlan.zhihu.com/p/68816998
Linux内核UART串口子系统驱动框架详解
https://blog.csdn.net/u013171226/article/details/133343779
内核的打印信息从哪个设备上显示出来,可以通过内核的cmdline来指定,
Linux下tty串口驱动数据的发送、接收过程源码实例详解
https://blog.csdn.net/Luckiers/article/details/123586094
tty初探 — uart驱动框架分析
https://zhuanlan.zhihu.com/p/68816998
https://www.cnblogs.com/51qianrushi/p/4324845.html
Linux 驱动开发 三十九:Linux 中断(一)
https://blog.csdn.net/OnlyLove_/article/details/122639787
https://zhuanlan.zhihu.com/p/355833608
基于Linux的tty架构及UART驱动详解 一般
https://blog.csdn.net/weixin_44618297/article/details/131881635?spm=1001.2101.3001.6650.3&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-3-131881635-blog-131405213.235%5Ev43%5Epc_blog_bottom_relevance_base1&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-3-131881635-blog-131405213.235%5Ev43%5Epc_blog_bottom_relevance_base1&utm_relevant_index=6
linux下 UART串口相关
https://blog.csdn.net/qq_43416206/article/details/131405213
Linux下应用层操作UART的四种方式 都是串口设备注册完以后
tty初探 — uart驱动框架分析
https://zhuanlan.zhihu.com/p/68816998
Linux下tty串口驱动数据的发送、接收过程源码实例详解
https://blog.csdn.net/Luckiers/article/details/123586094