技术标签: Linux内核 c语言 linux nxp arm 驱动开发
平台:NXP imx6ull 内核版本:4.1.15
Linux TTY驱动程序代码位于/drivers/tty下面。TTY整体框架大致分为TTY应用层、TTY文件层、TTY线路规程层、TTY驱动层、TTY设备驱动层。
TTY应用层负责应用逻辑
TTY文件层(操作函数集file_operations)负责文件接口
TTY线路规程(操作函数集tty_operations)负责串行通信协议处理,包括特定协议的封装与解封
TTY驱动层对各种TTY设备进行分类和抽象(struct uart_driver)
TTY设备驱动层(操作函数集struct uart_ops)实现具体的TTY设备(芯片或者控制器)驱动,即设备配置与数据收发。
(具体的TTY设备有串口、USB串口、VT设备、PTY设备等,最常见的是串口,即UART。)
UART驱动没有主机端和设备端之分,只有控制器驱动。imx6ull的UART驱动已由厂家写好,位于/drivers/tty/serial/imx.c文件中。剩下的工作主要是在设备树中配置串口节点信息,当UART驱动和设备树匹配成功后,相应的UART被驱动起来,在/dev/目录下生成ttymxcX(x=0…n)文件。
(1)struct uart_driver结构体本身并不包含底层UART硬件的操作方法,其是所有串口设备驱动的抽象和封装。起到了连接硬件设备驱动和TTY驱动的作用。注册了struct uart_driver后还不能使用UART设备,还需要关联具体的UART设备。
// 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; /* 该uart_driver支持的串口个数(最大) */
struct console *cons; /* 控制台 其对应的console.若该uart_driver支持serial console,否则为NULL */ */
struct uart_state *state; // 下层,串口驱动层 uart_state则代表下层,uart_state会在register_uart_driver 的过程中分配空间
struct tty_driver *tty_driver; /* tty相关 */ tty_driver是tty驱动的结构体,它会在register_uart_driver中的过程中赋值。
};
// 每个串口驱动都需要定义一个 uart_driver,加载驱动的时候通过 uart_register_driver 函数向系统注册这个 uart_driver
int uart_register_driver(struct uart_driver *drv) drv: 要注册的 uart_driver
// 注销驱动的时候也需要注销掉前面注册的 uart_driver,需要用到 uart_unregister_driver 函数
void uart_unregister_driver(struct uart_driver *drv) drv: 要注册的 uart_driver
// 在注册 driver 时,会根据 uart_driver->nr 来申请 nr 个 uart_state 空间,用来存放驱动所支持的串口(端口)的物理信息。
struct uart_state {
struct tty_port port;
enum uart_pm_state pm_state;
struct circ_buf xmit; // 缓冲区,发送数据和接收数据的存储空间
struct uart_port *uart_port; // 对应于一个实际的串口设备
};
(2)一个串口芯片上往往有多个串行端口(serial ports,对应于一个物理上的串口),这些串行端口具备相同的操作机制。Linux内核将这些串行端口用struct uart_port结构体描述。struct uart_port用于描述一个UART端口的中断、I/O内存地址、FIFO大小、端口类型等信息。
// include/linux/serial_core.h
struct uart_port {
spinlock_t lock; /* 自旋锁 */
unsigned long iobase; /* io端口基地址(物理) */
unsigned char __iomem *membase; /* io内存基地址(虚拟) */
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_mctrl)(struct uart_port *, unsigned int);
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);
unsigned int irq; /* 中断号 */
unsigned long irqflags; /* 中断标志 */
unsigned int uartclk; /* 串口时钟 */
unsigned int fifosize; /* 串口缓冲区大小 */
unsigned char x_char; /* xon/xoff握手字节 */
unsigned char regshift; /* 寄存器位移 */
unsigned char iotype; /* IO访问方式 */
unsigned char unused1;
#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) /* 8b MMIO access */
#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 */
unsigned int read_status_mask; /* 读状态验码 */
unsigned int ignore_status_mask; /* 忽略状态掩码 */
struct uart_state *state; /* 指向父设备的状态 */
struct uart_icount icount; /* 串口信息计数器 */
struct console *cons; /* struct console, if any */
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)
unsigned long sysrq; /* sysrq timeout */
#endif
/* flags must be updated while holding port mutex */
upf_t flags;
#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_NO_TXEN_TEST ((__force upf_t) (1 << 15))
#define UPF_MAGIC_MULTIPLIER ((__force upf_t) ASYNC_MAGIC_MULTIPLIER /* 16 */ )
/* 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))
int hw_stopped; /* sw-assisted CTS flow state */
unsigned int mctrl; /* 当前的Moden 设置 */
unsigned int timeout; /* character-based timeout */
unsigned int type; /* 端口类型 */
const struct uart_ops *ops; /* 此结构体很重要,uart硬件的操作函数 */
unsigned int custom_divisor;
unsigned int line; /* 端口索引 */
unsigned int minor;
resource_size_t mapbase; /* io内存物理基地址 */
resource_size_t mapsize;
struct device *dev; /* 父设备 */
unsigned char hub6; /* this should be in the 8250 driver */
unsigned char suspended;
unsigned char irq_wake;
unsigned char unused[2];
struct attribute_group *attr_group; /* port specific attributes */
const struct attribute_group **tty_groups; /* all attributes (serial core use only) */
struct serial_rs485 rs485;
void *private_data; /* generic platform data pointer */
};
每个 UART 都有一个 uart_port,uart_port 和 uart_driver 结合起来是通过uart_add_one_port 函数
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)
drv:此 port 对应的 uart_driver
uport: 要添加到 uart_driver 中的 port
返回值: 0,成功;负值,失败
卸载 UART 驱动的时候也需要将 uart_port 从相应的 uart_driver 中移除,需要用到uart_remove_one_port 函数
int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)
drv:要卸载的 port 所对应的 uart_driver
uport: 要卸载的 uart_port
返回值: 0,成功;负值,失败
(3)Linux 系统收发数据最终调用的都是 ops 中的函数。 ops 是 uart_ops类型的结构体指针变量。uart硬件操作函数集合,底层硬件驱动必须实现这个结构体
// 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控制,xyz
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 (*enable_ms)(struct uart_port *); // 使能modem的状态信号
void (*break_ctl)(struct uart_port *, int ctl); // 设置break信号
int (*startup)(struct uart_port *); // 使能串口,用户调用open使最终会调用此函数
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);
const char *(*type)(struct uart_port *); // 判断串口类型是否为amba
void (*release_port)(struct uart_port *); // 释放端口使用的内存
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
};
uart3: serial@021ec000 {
compatible = "fsl,imx6ul-uart",
"fsl,imx6q-uart", "fsl,imx21-uart";
reg = <0x021ec000 0x4000>;
interrupts = <GIC_SPI 28 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_UART3_IPG>,
<&clks IMX6UL_CLK_UART3_SERIAL>;
clock-names = "ipg", "per";
dmas = <&sdma 29 4 0>, <&sdma 30 4 0>;
dma-names = "rx", "tx";
status = "disabled";
};
pinctrl_uart3: uart3grp {
fsl,pins = <
MX6UL_PAD_UART3_TX_DATA__UART3_DCE_TX 0x1b0b1
MX6UL_PAD_UART3_RX_DATA__UART3_DCE_RX 0x1b0b1
>;
};
&uart3 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart3>;
status = "okay";
};
// 用于和设备树匹配的表,和设备树中的兼容属性一致
static const struct of_device_id imx_uart_dt_ids[] = {
{
.compatible = "fsl,imx6q-uart", .data = &imx_uart_devdata[IMX6Q_UART], },
{
.compatible = "fsl,imx1-uart", .data = &imx_uart_devdata[IMX1_UART], },
{
.compatible = "fsl,imx21-uart", .data = &imx_uart_devdata[IMX21_UART], },
{
/* sentinel */ }
};
// 平台驱动结构体
static struct platform_driver serial_imx_driver = {
.probe = serial_imx_probe,
.remove = serial_imx_remove,
.suspend = serial_imx_suspend,
.resume = serial_imx_resume,
.id_table = imx_uart_devtype,
.driver = {
.name = "imx-uart",
.of_match_table = imx_uart_dt_ids,
},
};
// 驱动具体定义的uart_driver结构体,成员state指向的内存在uart_register_driver函数中根据uart port数量进行分配
static struct uart_driver imx_reg = {
.owner = THIS_MODULE,
.driver_name = DRIVER_NAME, #define DRIVER_NAME "IMX-uart"
.dev_name = DEV_NAME, #define DEV_NAME "ttymxc"
.major = SERIAL_IMX_MAJOR, #define SERIAL_IMX_MAJOR 207
.minor = MINOR_START, #define MINOR_START 16
.nr = ARRAY_SIZE(imx_ports),
.cons = IMX_CONSOLE,
};
#define UART_NR 8
static struct imx_port *imx_ports[UART_NR];
// 模块初始化函数
static int __init imx_serial_init(void)
{
int ret = uart_register_driver(&imx_reg); // 调用uart_register_driver函数向TTY层注册uart_driver
if (ret)
return ret;
ret = platform_driver_register(&serial_imx_driver); // UART本质上是一个platform 驱动,注册,自动匹配和调用probe函数
if (ret != 0)
uart_unregister_driver(&imx_reg);
return ret;
}
// 模块退出函数
static void __exit imx_serial_exit(void) // 驱动出口函数
{
// 注销平台驱动
platform_driver_unregister(&serial_imx_driver); //调用 uart_unregister_driver 函数注销掉前面注册的 uart_driver
// 注销uart_driver结构体
uart_unregister_driver(&imx_reg);
}
module_init(imx_serial_init);
module_exit(imx_serial_exit);
tty核心层(上层)底层驱动层和tty层之间的联系需要从register_uart_driver中连接,tty_driver是在uart_driver注册过程中构建的。
// drivers/tty/serial/serial_core.c
uart_register_driver(struct uart_driver *drv)
-> struct tty_driver *normal;
// 根据driver支持的最大设备数,申请n个 uart_state 空间,每一个 uart_state 都有一个uart_port,支持多少个串口,就开辟多少块空间
-> drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
/* tty层:分配一个 tty_driver ,并将drv->tty_driver 指向它 */
-> normal = alloc_tty_driver(drv->nr);
-> drv->tty_driver = normal; // tty_driver 赋值给uart_driver结构体中的tty_driver成员
/* 对 tty_driver 进行设置 */
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; // uart_driver 赋值给tty_driver结构体中的driver_state成员
-> tty_set_operations(normal, &uart_ops); // 设置tty驱动层的处理函数 将tty驱动的操作函数和uart的操作函数关联起来 将tty_driver的操作集统一设为了uart_ops.这样就使得从用户空间下来的操作可以找到正确的serial_core的操作函数
-> struct uart_state *state = drv->state + i;
-> struct tty_port *port = &state->port; // driver->state->tty_port
-> tty_port_init // 初始化tty_port
-> port->ops = &uart_port_ops; // 操作tty上层函数
-> tty_register_driver(normal) // 注册tty驱动
static const struct tty_operations uart_ops = {
.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_fops = &uart_proc_fops,
#endif
.tiocmget = uart_tiocmget, // 获得当前tty的线路规程的设置
.tiocmset = uart_tiocmset, // 设置当前tty线路规程的设置
.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层
static const struct tty_port_operations uart_port_ops = {
.activate = uart_port_activate,
.shutdown = uart_port_shutdown,
.carrier_raised = uart_carrier_raised,
.dtr_rts = uart_dtr_rts,
};
用户空间函数分析
// drivers/tty/tty_io.c
// 在uart_register_driver()函数中我们调用了每个tty类型的驱动注册时都会调用的tty_register_driver函数
tty_register_driver(struct tty_driver *driver)
-> if (!driver->major) //设备号未知的情况
-> error = alloc_chrdev_region(&dev, driver->minor_start, driver->num, driver->name);
-> else //设备号已知的情况
dev = MKDEV(driver->major, driver->minor_start);
error = register_chrdev_region(dev, driver->num, driver->name);
-> tty_cdev_add(driver, dev, 0, driver->num);
-> cdev_init(&driver->cdevs[index], &tty_fops); /* 创建字符设备,使用 tty_fops index=0为传入得参数*/
-> driver->cdevs[index].owner = driver->owner;
-> cdev_add(&driver->cdevs[index], dev, count);
-> list_add(&driver->tty_drivers, &tty_drivers); /* 将该 driver->tty_drivers 添加到全局链表 tty_drivers */
-> tty_register_device(driver, i, NULL);
-> proc_tty_register_driver(driver); /* proc 文件系统注册driver */
static const struct file_operations tty_fops = {
.llseek = no_llseek,
.read = tty_read,
.write = tty_write,
.poll = tty_poll,
.unlocked_ioctl = tty_ioctl,
.compat_ioctl = tty_compat_ioctl,
.open = tty_open,
.release = tty_release,
.fasync = tty_fasync,
};
自动匹配调用probe函数(serial_imx_probe):初始化 uart_port,然后将其添加到对应的 uart_driver 中
// drivers/tty/serial/imx.c
serial_imx_probe
-> struct imx_port *sport; // 定义一个imx_port类型的结构体指针变量sport (后面有定义)
-> devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL); // 为sport申请内存
-> serial_imx_probe_dt(sport, pdev) // 获取设备树信息
-> of_match_device(imx_uart_dt_ids, &pdev->dev); // 将设备树匹配表与设备进行匹配,获取设备对应的设备树匹配表
-> ret = of_alias_get_id(np, "serial"); // 获取uart别名id,与aliases设备树节点有关
-> sport->port.line = ret; // 将uart别名id保存到uart_port结构体line成员中
-> of_get_property(np, "fsl,uart-has-rtscts", NULL) // 获取fsl,uart-has-rtscts属性,有就设置sport->have_rtscts = 1;
-> of_get_property(np, "fsl,dte-mode", NULL) // 获取fsl,dte-mode属性,有就设置sport->dte_mode = 1;
-> sport->devdata = of_id->data; // 设置设备结构体的驱动数据成员,指向设备树匹配表中的date成员
-> platform_get_resource(pdev, IORESOURCE_MEM, 0); // 从设备树中获取uart外设寄存器首地址,即设备树中的reg属性 #define IORESOURCE_MEM 0x00000200
-> devm_ioremap_resource(&pdev->dev, res); // 得到寄存器首地址以后对其进行内存映射,得到对应的虚拟地址
-> rxirq = platform_get_irq(pdev, 0); // 获取接收中断(中断索引号为0),设备树中配置了此中断
txirq = platform_get_irq(pdev, 1); // 获取发送中断(中断索引号为1),设备树中没有配置此中断
rtsirq = platform_get_irq(pdev, 2); // 获取rts中断(中断索引号为2),设备树中没有配置此中断
------------------- 设置uart_port结构体 --------------------------
sport->port.dev = &pdev->dev;
sport->port.mapbase = res->start; // 设置映射前的基地址
sport->port.membase = base; // 设置映射后的基地址
sport->port.type = PORT_IMX,
sport->port.iotype = UPIO_MEM; // IO类型为IO内存
sport->port.irq = rxirq; // 设置接收中断
sport->port.fifosize = 32; // 设置fifo大小
sport->port.ops = &imx_pops; // 设置uart硬件操作函数集合
sport->port.rs485_config = imx_rs485_config; // 设置rs485配置函数
sport->port.rs485.flags =
SER_RS485_RTS_ON_SEND | SER_RS485_RX_DURING_TX;
sport->port.flags = UPF_BOOT_AUTOCONF;
------------------------------------------------------------------------------------------
init_timer(&sport->timer); // 初始化定时器
sport->timer.function = imx_timeout; // 设置定时器超时函数
sport->timer.data = (unsigned long)sport; // 设置定时器超时函数的参数
sport->clk_ipg = devm_clk_get(&pdev->dev, "ipg"); // 获取ipg时钟
sport->clk_per = devm_clk_get(&pdev->dev, "per"); // 获取per时钟
sport->port.uartclk = clk_get_rate(sport->clk_per); // uart的时钟频率
-> devm_request_irq(&pdev->dev, rxirq, imx_int, 0, dev_name(&pdev->dev), sport); // 请求接收中断,中断服务函数为imx_int
-> imx_ports[sport->port.line] = sport; // 保存设备结构体指针,数组索引为uart别名id
// uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
-> uart_add_one_port(&imx_reg, &sport->port) // 向 uart_driver 添加 uart_port drivers/tty/serial/serial_core.c
-> if (uport->line >= drv->nr) // 检查uart端口数量是否超过了uart_driver中规定的数量
-> state = drv->state + uport->line; // 根据串口编号找到具体串口的state地址
-> state->uart_port = uport; // 关联state中的port和具体的port
-> uport->state = state; // 关联具体port中的state和uart_driver中的state
-> uart_configure_port(drv, state, uport); // 配置串口
-> port->ops->config_port(port, flags); // 调用imx_config_port配置串口
-> port.type = PORT_IMX // 设置port类型为Motorola i.MX SoC
-> uart_report_port(drv, port); // 打印串口配置信息
-> port->ops->set_mctrl(port, port->mctrl & TIOCM_DTR); // 调用imx_set_mctrl函数设置串口 设置串口模式
// 注册tty设备 device *tty_port_register_device_attr(struct tty_port *port, struct tty_driver *driver, unsigned index, struct device *device, void *drvdata, const struct attribute_group **attr_grp)
-> struct tty_port *port; port = &state->port;
-> tty_port_register_device_attr(port, drv->tty_driver, uport->line, uport->dev, port, uport->tty_groups);
-> tty_port_link_device(port, driver, index);
-> driver->ports[index] = port;
// 封装设备结构体,包含uart_port
struct imx_port {
struct uart_port port;
struct timer_list timer;
unsigned int old_status;
unsigned int have_rtscts:1;
unsigned int dte_mode:1;
unsigned int irda_inv_rx:1;
unsigned int irda_inv_tx:1;
unsigned short trcv_delay; /* transceiver delay */
struct clk *clk_ipg;
struct clk *clk_per;
const struct imx_uart_data *devdata;
/* DMA fields */
unsigned int dma_is_inited:1;
unsigned int dma_is_enabled:1;
unsigned int dma_is_rxing:1;
unsigned int dma_is_txing:1;
struct dma_chan *dma_chan_rx, *dma_chan_tx;
struct scatterlist tx_sgl[2];
struct imx_dma_rxbuf rx_buf;
unsigned int tx_bytes;
unsigned int dma_tx_nents;
struct delayed_work tsk_dma_tx;
wait_queue_head_t dma_wait;
unsigned int saved_reg[10];
#define DMA_TX_IS_WORKING 1
unsigned long flags;
};
// 最底层的驱动函数集合
static struct uart_ops imx_pops = {
.tx_empty = imx_tx_empty,
.set_mctrl = imx_set_mctrl,
.get_mctrl = imx_get_mctrl,
.stop_tx = imx_stop_tx,
.start_tx = imx_start_tx,
.stop_rx = imx_stop_rx,
.enable_ms = imx_enable_ms,
.break_ctl = imx_break_ctl,
.startup = imx_startup,
.shutdown = imx_shutdown,
.flush_buffer = imx_flush_buffer,
.set_termios = imx_set_termios,
.type = imx_type,
.config_port = imx_config_port,
.verify_port = imx_verify_port,
#if defined(CONFIG_CONSOLE_POLL)
.poll_init = imx_poll_init,
.poll_get_char = imx_poll_get_char,
.poll_put_char = imx_poll_put_char,
#endif
};
小结
可参考整理的TTY-UART结构体关系图
文章浏览阅读1k次,点赞6次,收藏28次。哈喽,大家好,我是爱学习的莫提。从大二就开始接触并使用IDEA这款软件,现在它同样是我工作中的主力开发工具。对于Java开发人员来说,掌握这款软件的基本使用也已经是必备的工作技能。这款软件相比于Eclipse来说也是有非常多的优点,更加的高效,也更加的稳定。IDEA的插件市场也是非常的丰富!吸引了非常多的开发者从Eclipse来过渡到IDEA,那么正所谓「工欲善其事,必先利其器」,经过我这几年的使用,以及工作之后和同事的一些交流分享。我总结出了很多可以让我们的使用体验和开发效率得到很大程度提升的。_鲁班大叔 maven插件
文章浏览阅读623次。Oracle SQL Loader 的详细语法 SQL*LOADER 是 ORACLE 的数据加载工具,通常用来将操作系统文件迁移到 ORACLE 数据库中。 S_sqlldr941
文章浏览阅读2.9k次,点赞6次,收藏10次。大家好,我是无际。最近有一些做机械的朋友找到我,说嵌入式好不好学。深度聊完以后,我抓到了他们的几个吐槽点,行业过于传统,工资低,没前途,工作环境恶劣。看到身边那些做了10年的才1万块出头,心都凉了。如果不是看不到希望,谁也不会轻易冒险转行,毕竟付出的代价太大啦。我做了嵌入式单片机开发10年,除了技术以外,我还领悟到了很多经验。一、嵌入式和机械哪个好?首先要告诉你的是,不管任何技术都好,本身只是工具,有没有前途其实最大的因素是你怎么用这个工具去创造价值。做嵌入式开发也一样,_机械转嵌入式那个方向好
文章浏览阅读7.3k次,点赞3次,收藏5次。RN中文网关于原生模块(Android)的介绍可以看到,RN前端与原生模块之间通信,主要有三种方法:1)使用回调函数Callback,它提供了一个函数来把返回值传回给JavaScript。2)使用Promise来实现。3)原生模块向JavaScript发送事件。关于使用回调,这是最简单的一种通信,这里可以看看官网的实现,今天要讲的是滴三种由原生模块向JavaScript发送事件。(1)首先,你需要_react native promise 原生通信
文章浏览阅读1.5w次。解决Python.h No such file or directory 1.可以先查看一下含python-devel的包 yum search python | grep python-devel2.64位安装python-devel.x86_64,32位安装python-devel.i686,我这里安装: sudo yum install python-deve..._centos7 fatal error: python.h: no such file or directory
文章浏览阅读905次。Python里面有内置(Built-in)的平方根函数:sqrt(),可以方便计算正数的平方根。那么,如果要自己定义一个sqrt函数,该怎么解决呢?解决思路:1. 大于等于1的正数n的方根,范围肯定在0~n之间;小于1的正数n的方根,范围肯定在0~1之间2. 用二分法(Bisection method, Binary search)从中间开始找n的方根。3. 对于大于等于1的正数n,先假设n/2是..._python怎么定义平方根筛选函数
文章浏览阅读471次。IDA打开在sub_140001380发现主函数然后在字符串里发现有东西双击跟进发现a0有东西,估计是个迷宫题。看代码` char v9; // dlCHAR *v10; // rcxv2 = a2;if ( *(_DWORD *)(a2 + ‘0’) >= 0 ){v3 = *(_QWORD *)(a2 + 24);v4 = *(_QWORD *)(a2 + 56) >> 3;if ( (_DWORD)v4 ){v5 = dword_1400030E4;_do { *(_dword *)(((unsigned int)v7 & 0xfffffffc) + v4) = 0; v4 += 4; } while
文章浏览阅读5.8k次,点赞4次,收藏33次。原文地址:https://blog.csdn.net/to_baidu/article/details/53649796步骤一:登录界面设计首先在VS中设计一个简单的登录界面,点击【资源视图】,然后右键【Dialog】,选择【添加资源】——【Dialog】——【新建】,这样就创建了一个新的空白对话框。可以按自己的需要更改窗口的ID,并根据自己需要将窗口设计成登录的方式,例如我的设计如下: ..._mfc制作密码登录页面
文章浏览阅读243次。说到事物的四大特性原子性、一致性、隔离性、持久性,懂的人很多,但是稍微涉及细节,四大特性在数据库中的实现原理是怎么实现的?几乎很少有人能够答上来。所以这里着重讨论下mysql中的实现原理。问题一:Mysql怎么保证一致性的?OK,这个问题分为两个层面来说。从数据库层面,数据库通过原子性、隔离性、持久性来保证一致性。也就是说ACID四大特性之中,C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保证一致性,数据库提供的手段。数据库必须要实现AID三大特性,才有可能实现一致性_acid原理
文章浏览阅读1.5k次。服务搭建:基于docker-compose搭建单机版skywalking:docker-compose.yml文件内容version: '3.5'services: elasticsearch: image: elasticsearch:${ES_TAG} container_name: elasticsearch restart: always ports: - 9200:9200 environment: dis_springboot集成skywalking docker
文章浏览阅读1.4w次,点赞8次,收藏10次。、其中的 过程解释如下_为什么叉乘是平行四边形面积
文章浏览阅读849次。Ubuntu安装QQ方法1下载:链 接:http://pan.baidu.com/s/1pJ9D1OZ提取密码: lbq02解压:7zxwineTM.7z解压出来了,有一个安装脚本tm2013_install.sh,和两个隐藏目录.wine和.local,你只需要运行tm2013_install.sh脚本就可以完成安装wine和配置tm2013了。_ubuntuqq安葬添加信任