(***)Linux UART串口介绍(底层驱动相关)

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

LINUX串口驱动分析——发送数据

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

相关推荐

  1. CAN 四: CAN相关HAL库驱动介绍

    2024-04-25 07:46:05       62 阅读
  2. 深入理解vue相关底层原理

    2024-04-25 07:46:05       46 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-25 07:46:05       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-25 07:46:05       106 阅读
  3. 在Django里面运行非项目文件

    2024-04-25 07:46:05       87 阅读
  4. Python语言-面向对象

    2024-04-25 07:46:05       96 阅读

热门阅读

  1. 基于单片机的机械臂运行轨迹在线控制系统设计

    2024-04-25 07:46:05       33 阅读
  2. 面试——数据库中的锁升级(Lock Escalation)机制

    2024-04-25 07:46:05       37 阅读
  3. Android - OkHttp 访问 https 的怪问题

    2024-04-25 07:46:05       40 阅读
  4. MyBatis与Hibernate的区别

    2024-04-25 07:46:05       40 阅读
  5. C脚本实现Wincc单按钮启动/停止

    2024-04-25 07:46:05       74 阅读
  6. 表单插件——jquery.form.js

    2024-04-25 07:46:05       30 阅读