解决方案

Linux串口驱动分析及移植

seo靠我 2023-09-23 15:50:51

1 概述

在Linux中,常碰到“控制台”、“终端”、“console”、“tty”等术语,也会经常使用一些设备文件:/dev/console、/dev/ttyS0、/dev/ttyUSB0等。tty是SEO靠我Teletype的缩写,Teletype是最早出现的一种终端设备,Linux通常使用tty来表示“终端”,终端设备的种类有很多,比如串行终端、键盘和显示器、通过网络实现的终端等。

UART与USART都SEO靠我是单片机上的串口通信, UART(universal asynchronous receiver and transmitter)通用异步收/发器,USART(universal synchronouSEO靠我s asynchronous receiver and transmitter)通用同步/异步收/发器。从名字上可以看出,USART在UART基础上增加了同步功能,即USART是UART的增强型。当我SEO靠我们使用USART在异步通信的时候,它与UART没有什么区别,但是用在同步通信的时候,区别就很明显了:同步通信需要时钟来触发数据传输,也就是说USART相对UART的区别之一就是能提供主动时钟。

串口属于SEO靠我终端设备,它的驱动程序并不仅仅是简单的初始化硬件、接收/发送数据,在基本硬件操作的基础上,还增加了很多软件功能,是一个多层次的驱动程序。

2 串口驱动程序层次结构

串口驱动程序层次结构如图2.1所示。简单SEO靠我来说,串口驱动程序层次结构可以分为两层,下层为串口驱动层,它直接与硬件相接触,需要填充一个 struct uart_ops 的结构体。上层为tty层,包括tty核心层及线路规程,它们各自都有一个 opSEO靠我s 结构体,用户空间可以通过tty注册的字符设备节点来访问串口设备。如图2.1所示,涉及到了4个 ops 结构体,层层进行跳转。

    下面以Microchip SAMA5D4处理器的UART控制器来进行代码SEO靠我分析。

2.1 串口驱动注册-uart_register_driver

路径: \linux-at91\drivers\tty\serial\atmel_serial.c。对于Atmel平台,是这样来注册SEO靠我串口驱动的,首先分配一个struct uart_driver 简单填充,并调用uart_register_driver 注册到内核中去。

struct uart_driver 中,只是填充了一些名字、设SEO靠我备号等信息,这些都是不涉及底层硬件访问的。

下面看一下完整的 uart_driver 结构。

在struct uart_driver atmel_uart结构体中,有两个成员未被赋值,分别是tty_driSEO靠我ver和uart_state。对于tty_driver,代表的是上层,它会在 uart_ register_driver 的过程中赋值。而uart_state ,则代表下层,uart_state也会在SEO靠我uart_ register_driver 的过程中分配空间,但是它里面真正设置硬件相关的东西是 uart_state->uart_port ,这个uart_port 是需要从其它地方调用 uart_SEO靠我add_one_port 来添加的。

 下层(串口驱动层)

首先,认识几个结构体。

1) 结构体:uart_state

路径:\linux-at91\linux-at91\linux-at91\incluSEO靠我de\linux\serial_core.h/** This is the state information which is persistent across opens.* 打开时下的状态信息SEO靠我*/ struct uart_state {struct tty_port port;enum uart_pm_state pm_state;struct circ_buf xmit;SEO靠我atomic_t refcount;wait_queue_head_t remove_wait;struct uart_port *uart_port; // 对应于一个串口设备 };SEO靠我

    在注册 driver 时,会根据 uart_driver->nr 来申请 nr 个 uart_state 空间,用来存放驱动所支持的串口(端口)物理信息。

2) 结构体:uart_port路径:\linSEO靠我ux-at91\linux-at91\linux-at91\include\linux\serial_core.h

struct uart_port {spinlock_t lock; /* port SEO靠我lock */unsigned long iobase; /* in/out[bwl] io端口基地址(物理) */unsigned char __iomem *membase; /* read/wrSEO靠我ite[bwl] */unsigned int (*serial_in)(struct uart_port *, int);void (*serial_out)(struct uart_port *,SEO靠我 int, int);void (*set_termios)(struct uart_port *,struct ktermios *new,struct ktermios *old);unsigneSEO靠我d int (*get_mctrl)(struct uart_port *);void (*set_mctrl)(struct uart_port *, unsigned int);int (*staSEO靠我rtup)(struct uart_port *port);void (*shutdown)(struct uart_port *port);void (*throttle)(struct uart_SEO靠我port *port);void (*unthrottle)(struct uart_port *port);int (*handle_irq)(struct uart_port *);void (*SEO靠我pm)(struct uart_port *, unsigned int state,unsigned int old);void (*handle_break)(struct uart_port *SEO靠我);int (*rs485_config)(struct uart_port *,struct serial_rs485 *rs485);unsigned int irq; /* irq numberSEO靠我 */unsigned long irqflags; /* irq flags */unsigned int uartclk; /* base uart clock */unsigned int fiSEO靠我fosize; /* tx fifo size */unsigned char x_char; /* xon/xoff char */unsigned char regshift; /* reg ofSEO靠我fset shift */unsigned char iotype; /* io access style */unsigned char unused1;#define UPIO_PORT (SERSEO靠我IAL_IO_PORT) /* 8b I/O port access */ #define UPIO_HUB6 (SERIAL_IO_HUB6) /* Hub6 ISA card */SEO靠我 #define UPIO_MEM (SERIAL_IO_MEM) /* driver-specific */ #define UPIO_MEM32 (SERIAL_ISEO靠我O_MEM32) /* 32b little endian */ #define UPIO_AU (SERIAL_IO_AU) /* Au1x00 and RT288x type IOSEO靠我 */ #define UPIO_TSI (SERIAL_IO_TSI) /* Tsi108/109 type IO */ #define UPIO_MEM32BE (SEO靠我SERIAL_IO_MEM32BE) /* 32b big endian */ #define UPIO_MEM16 (SERIAL_IO_MEM16) /* 16b little eSEO靠我ndian */unsigned int read_status_mask; /* driver specific */unsigned int ignore_status_mask; /* drivSEO靠我er specific */struct uart_state *state; /* pointer to parent state */struct uart_icount icount; /* sSEO靠我tatistics */struct console *cons; /* struct console, if any */ #if defined(CONFIG_SERIAL_CORSEO靠我E_CONSOLE) || defined(SUPPORT_SYSRQ)unsigned long sysrq; /* sysrq timeout */ #endif。。。 SEO靠我 。。。int hw_stopped; /* sw-assisted CTS flow state */unsigned int mctrl; /* current modem ctrl settSEO靠我ings */unsigned int timeout; /* character-based timeout */unsigned int type; /* port type */const stSEO靠我ruct uart_ops *ops;unsigned int custom_divisor;unsigned int line; /* port index */unsigned int minorSEO靠我;resource_size_t mapbase; /* for ioremap */resource_size_t mapsize;struct device *dev; /* parent devSEO靠我ice */unsigned char hub6; /* this should be in the 8250 driver */unsigned char suspended;unsigned chSEO靠我ar irq_wake;unsigned char unused[2];struct attribute_group *attr_group; /* port specific attributes SEO靠我*/const struct attribute_group **tty_groups; /* all attributes (serial core use only) */struct seriaSEO靠我l_rs485 rs485;void *private_data; /* generic platform data pointer */ };

struct uart_port,这个结SEO靠我构体对应于一个串口设备,如果平台有3个串口那么就需要填充3个uart_port ,并且通过 uart_add_one_port 添加到 uart_driver->uart_state->uart_poSEO靠我rt 中去。当然 uart_driver 有多个 uart_state ,每个 uart_state 有一个 uart_port。在 uart_port 里还有一个非常重要的成员 struct uarSEO靠我t_ops *ops ,一般芯片厂家都写好了,只需要稍作修改。

    下面是struct uart_ops 结构体。

 上层(tty核心层)tty 核心层要从 uart_register_ driver 来看SEO靠我起,因为 tty_driver 是在注册过程中构建的,顺便了解注册过程。

/*** uart_register_driver - register a driver with the uart coreSEO靠我 layer* @drv: low level driver structure** Register a uart driver with the core driver. We in turn rSEO靠我egister* with the tty layer, and initialise the core driver per-port state.** We have a proc file inSEO靠我 /proc/tty/driver which is named after the* normal driver.** drv->port should be NULL, and the per-pSEO靠我ort structures should be* registered using uart_add_one_port after this call has succeeded.*/ SEO靠我 int uart_register_driver(struct uart_driver *drv) {struct tty_driver *normal;int i, retvalSEO靠我;BUG_ON(drv->state);/** Maybe we should be using a slab cache for this, especially if* we have a larSEO靠我ge number of ports to handle.*/ /* 根据driver支持的最大设备数,申请n个 uart_state 空间,每一个 uart_state 都有一个uaSEO靠我rt_port */drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);if (!drv->state)gotoSEO靠我 out; /* tty层:分配一个 tty_driver ,并将drv->tty_driver 指向它 */normal = alloc_tty_driver(drv->nr);ifSEO靠我 (!normal)goto out_kfree;drv->tty_driver = normal; /* 对 tty_driver 进行设置 */normal->driver_namSEO靠我e = drv->driver_name;normal->name = drv->dev_name;normal->major = drv->major;normal->minor_start = dSEO靠我rv->minor;normal->type = TTY_DRIVER_TYPE_SERIAL;normal->subtype = SERIAL_TYPE_NORMAL;normal->init_teSEO靠我rmios = tty_std_termios;normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;normal->SEO靠我init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;normal->flags = TTY_DRIVER_REAL_RAW | TSEO靠我TY_DRIVER_DYNAMIC_DEV;normal->driver_state = drv;tty_set_operations(normal, &uart_ops);/** InitialisSEO靠我e the UART state(s).*/for (i = 0; i < drv->nr; i++) {struct uart_state *state = drv->state + i;strucSEO靠我t tty_port *port = &state->port;tty_port_init(port);port->ops = &uart_port_ops;//port->close_delay =SEO靠我 HZ / 2; /* .5 seconds *///port->closing_wait = 30 * HZ;/* 30 seconds */}/* tty层:注册 driver->tty_drivSEO靠我er */retval = tty_register_driver(normal);if (retval >= 0)return retval;for (i = 0; i < drv->nr; i++SEO靠我)tty_port_destroy(&drv->state[i].port);put_tty_driver(normal); out_kfree:kfree(drv->state); SEO靠我 out:return -ENOMEM; }

uart_register_ driver 注册过程干了哪些事

1)根据driver支持的最大设备数,申请n个 uart_staSEO靠我te 空间,每一个 uart_state 都有一个 uart_port ;

     2)分配一个 tty_driver ,并将drv->tty_driver 指向它;

3)对 tty_driver 进行设置,其中SEO靠我包括默认波特率、校验方式等,还有一个重要的ops ,uart_ops ,它是tty核心与串口驱动通信的接口;

     4)初始化每一个 uart_state ;

     5)注册 tty_driver 。

注册 uart_SEO靠我driver 实际上是注册 tty_driver,因此与用户空间打交道的工作完全交给了 tty_driver ,而且这一部分都是内核实现好的,基本不需要修改,了解一下工作原理即可。

这里介绍下uart_SEO靠我ops结构体。

static const struct tty_operations uart_ops = {.open = uart_open,.close = uart_close,.write =SEO靠我 uart_write,.put_char = uart_put_char,.flush_chars = uart_flush_chars,.write_room = uart_write_room,SEO靠我.chars_in_buffer= uart_chars_in_buffer,.flush_buffer = uart_flush_buffer,.ioctl = uart_ioctl,.throttSEO靠我le = uart_throttle,.unthrottle = uart_unthrottle,.send_xchar = uart_send_xchar,.set_termios = uart_sSEO靠我et_termios,.set_ldisc = uart_set_ldisc,.stop = uart_stop,.start = uart_start,.hangup = uart_hangup,.SEO靠我break_ctl = uart_break_ctl,.wait_until_sent= uart_wait_until_sent, #ifdef CONFIG_PROC_FS.proSEO靠我c_fops = &uart_proc_fops, #endif.tiocmget = uart_tiocmget,.tiocmset = uart_tiocmset,.get_icoSEO靠我unt = uart_get_icount, #ifdef CONFIG_CONSOLE_POLL.poll_init = uart_poll_init,.poll_get_char SEO靠我= uart_poll_get_char,.poll_put_char = uart_poll_put_char, #endif };

这个是 tty 核心层的 ops SEO靠我,简单一看,后面分析调用关系时,我们再来看具体的里边的函数。

下面来看 tty_driver 的注册,tty_register_driver。/** Called by a tty driver to SEO靠我register itself.*/ int tty_register_driver(struct tty_driver *driver) {int error;intSEO靠我 i;dev_t dev;struct device *d;/* 如果没有主设备号则申请 */if (!driver->major) {error = alloc_chrdev_region(&devSEO靠我, driver->minor_start,driver->num, driver->name);if (!error) {driver->major = MAJOR(dev);driver->minSEO靠我or_start = MINOR(dev);}} else {dev = MKDEV(driver->major, driver->minor_start);error = register_chrdSEO靠我ev_region(dev, driver->num, driver->name);}if (error < 0)goto err;/* 创建字符设备 ,使用 tty_fops */if (driveSEO靠我r->flags & TTY_DRIVER_DYNAMIC_ALLOC) {error = tty_cdev_add(driver, dev, 0, driver->num);if (error)goSEO靠我to err_unreg_char;}mutex_lock(&tty_mutex);/* 将该 driver->tty_drivers 添加到全局链表 tty_drivers */list_add(&SEO靠我driver->tty_drivers, &tty_drivers);mutex_unlock(&tty_mutex);if (!(driver->flags & TTY_DRIVER_DYNAMICSEO靠我_DEV)) {for (i = 0; i < driver->num; i++) {d = tty_register_device(driver, i, NULL);if (IS_ERR(d)) {SEO靠我error = PTR_ERR(d);goto err_unreg_devs;}}}/* proc 文件系统注册driver */proc_tty_register_driver(driver);drSEO靠我iver->flags |= TTY_DRIVER_INSTALLED;return 0;err_unreg_devs:for (i--; i >= 0; i--)tty_unregister_devSEO靠我ice(driver, i);mutex_lock(&tty_mutex);list_del(&driver->tty_drivers);mutex_unlock(&tty_mutex);err_unSEO靠我reg_char:unregister_chrdev_region(dev, driver->num); err:return error; }

tty_registerSEO靠我_driver注册过程干了哪些事

1)为线路规程和termios分配空间,并使 tty_driver 相应的成员指向它们;

2)注册字符设备,名字是 uart_driver->name 我们这里是“ttSEO靠我yS”,文件操作函数集是 tty_fops。可查看tty_cdev_add 函数的定义;

3)将该 uart_driver->tty_drivers 添加到全局链表 tty_drivers ;

4)向 pSEO靠我roc 文件系统添加 driver ,这个暂时不了解。

下面是结构体 tty_fops

    至此,文章起初的结构图中的4个ops已经出现了3个,另一个关于线路规程的在哪?下面来看一下调用关系。

 调用关系

ttSEO靠我y_driver注册了一个字符设备(/dev/ttyS0),从它的 tty_fops 入手,可以得知用户空间是如何访问到最底层的硬件操作函数。以 open、read、write 为例,用户空间 opeSEO靠我n 时将调用到 uart_port.ops.startup ,在用户空间 write 则调用 uart_port.ops.start_tx。这些内核都已经实现好,在驱动开发过程中几乎不涉及这些代码的修SEO靠我改移植工作。图2.2为串口读写函数调用关系。

2.2 平台驱动注册-platform _driver_register

目前Linux2.6版本以后的ARM 结构使用设备树(DTS,Device TreeSEO靠我 Source)来分管设备,DeviceTree是一种描述硬件的数据结构,为什么要引入DTS?这是因为在Linux2.6中,ARM架构的板极硬件细节过多地被硬编码在arch/arm/plat-xxx和SEO靠我arch/arm/mach-xxx,比如板上的platform设备、resource、i2c_board_info、spi_board_info以及各种硬件的platform_data,这些板级细节代SEO靠我码对内核来讲只不过是垃圾代码。而采用DeviceTree后,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余编码。

platform_driver的入口函数,仍采用SEO靠我platform_driver_register注册。platform_driver_register用来枚举名称为 “atmel_usart” 的平台设备,只要内核中有相同名称的平台设备,platfSEO靠我orm_driver_register函数就会调用atmel_serial_driver 中的atmel_serial_probe函数来枚举它。

    函数:platform_driver_registerSEO靠我构体:atmel_serial_driver看结构体参数 atmel_serial_driver 的定义,该结构体一个很重要的成员是 .of_match_table = of_match_ptr(atSEO靠我mel_serial_dt_ids), of_match_ptr函数用于将atmel_serial_dt_ids 中.compatible 属性与.dts 文件中描述的设备节点.compatible SEO靠我属性进行匹配,且不区分大小写,当完成配对之后,就调用atmel_serial_probe函数完成驱动注册的最后工作。

    结构体:atmel_serial_dt_ids  设备树

Dtsi和dts同为设备树文SEO靠我件,dtsi为被包含文件,可被dts或者dtsi文件包含。

设备树文件sama5d4.dtsi中对串口设备节点的描述。路径:\linux-at91\arch\arm\boot\dts\sama5d4.dSEO靠我tsi。

    sama5d4.dtsi中描述了7个串口设备节点,2个uart,5个usart。

(别名)

(节点描述)描述了节点的一些属性。

(引脚定义)

设备树文件at91-sama5d4_xplained.dtSEO靠我s中对串口设备节点的描述。路径:\linux-at91\arch\arm\boot\dts\at91-sama5d4_xplained.dts。

3 串口移植

3.1 查看板卡的串口引脚

阅读A5处理器资料SEO靠我,查看UART章节和USART章节对串口的相关描述,根据硬件原理图确定串口的引脚定义。

3.2 修改情况

1) 设备树文件,到sama5d4.dtsi 文件下去搜索 USART ,对应找到相应的串口节点以SEO靠我及串口的引脚定义部分。sama5d4.dtsi 文件不需要进行修改。

 节点描述

 引脚定义

2) 设备树文件,查看 at91-sama5d4_xplained.dts 文件中的串口USART,在此文件SEO靠我下对节点进行相应参数修改。

—原来的串口节点信息如下:原始状态使能了uart0但我们没有接其引脚使用,使能了usart3的收发功能,使能了usart4但不具备收发功能,没有使能usart2。

—修改后的串SEO靠我口节点信息如下:

3) 驱动文件,在\linux-at91\drivers\tty\serial\atmel_serial.c文件中,查找static int __init atmel_serial_iSEO靠我nit(void)函数,找到uart_register_driver()函数,转到该函数定义,函数定义在 serial_core.c 文件中,文件中修改波特率,。原始波特率9600 ,可按自己的需求修SEO靠我改波特率,我们这里将其改为115200。

    到此,串口驱动移植完成,重新编译驱动文件,包括设备树文件和驱动文件,烧录至开发板,测试串口是否可以正常使用。

4 串口收发功能测试

将编译成功的内核镜像和设备树文件SEO靠我编译到开发板中,然后使用超级终端接开发板查看打印的日志。

    串口收发功能测试步骤如下:

    1) 查看tty设备。

(1)编译运行内核,如果UART驱动加载成功会在/dev目录下产生相应UART设备节点; 名称为SEO靠我:ttySx。这里会产生 ttyS0, ttyS1, ttyS2, ttyS5(没有使用)。

ttyS0对应USART3,为DEGB口;

ttyS1对应 USART4;

ttyS2对应 USART2;

ttySEO靠我S5 对应UART0;

    (2)运行命令: $ cat /proc/tty/driver/atmel_serial ,可以查看显示设备节点详细信息,其中通过地址可以对照设备节点;

root@sama5d4-SEO靠我xplained:/proc/tty/driver# cat atmel_serial

serinfo:1.0 driver revision:

0: uart:ATMEL_SERIAL mmio:0xFSEO靠我C00C000 irq:34 tx:2176533 rx:2142522 RTS|CTS|DTR|DSR|CD|RI

1: uart:ATMEL_SERIAL mmio:0xFC010000 irq:3SEO靠我5 tx:1253 rx:68 brk:68 CTS|DSR|CD|RI

2: uart:ATMEL_SERIAL mmio:0xFC008000 irq:33 tx:6 rx:0 DSR|CD|RI

SEO靠我果UART设备节点未产生,可在其相应驱动程序xx_probe函数中添加打印,查看xx_probe函数是否被调用,进一步查找原因。

    2) 通过跳冒选择串口。

下图中,两个黑色串口共用一个芯片。左边USARTSEO靠我3和USART4共用一个串口,通过跳冒来区分,右边USART2单独使用一个串口。

跳线冒J3用于设置选择DEBG- USART3和串口4,电路图如下图4.2。

具体的跳线设置如下表4.1和表4.2所示。

SEO靠我4.1 DEGU模式跳线状态

3) 如果成功产生了UART设备节点,下面进行串口收发测试;

方法1:可通过软件回环测试确认UART驱动程序功能是否正常。编写测试程序,将串口2、3引脚短接。读写数据。如果管SEO靠我脚信号测试通过,则串口功能基本调试成功。此方法的优点是无需上位机串口助手的配合,在串口模块到位之前提前完成接口调试工作。

方法2:

Echo 命令回显测试;

在超级终端下进入 /dev 目录下,以测试USASEO靠我RT2为例,输入命令: echo test > ttyS2.在串口助手中查看收到的信息,这一步测试串口的发。在超级终端下输入命令: cat ttyS2, 在串口助手中发送clear,在超级终端下查看收SEO靠我到信息,这一步查看串口的收。由于USART2不是调试口,无法打印系统信息,需要使用SSH连接开发板。具体的测试如下图所示。

方法3: 使用系统集成的串口调试工具 busybox microcom

1) bSEO靠我usybox microcom工具的使用;

命令(busybox microcom)使用方法很简单:

Usage: microcom [-d DELAY] [-t TIMEOUT] [-s SPEED] SEO靠我[-X] TTY

参数如下:

-d 表示延时时间,一般我都不设置。

-t 表示超时时间,超多少时间就自动退出。单位为ms

-s 表示传输速度,波特率的意思,这个根据自己的情况而定。

-X 不加

最后指定你的串口设SEO靠我备。如 /dev/ttyO0 , 这是TI的串口设备节点

2)测试方式如下:

 将要测试串口与pc端连接,在pc端开启串口调试工具,波特率设定跟等下microcom设定一样。

 在产品端运行如下命令:

mSEO靠我icrocom -s 115200 /dev/ttyS2

 在超级终端命令行输出信息,在PC是否有正确显示。反之也测试一下。

“SEO靠我”的新闻页面文章、图片、音频、视频等稿件均为自媒体人、第三方机构发布或转载。如稿件涉及版权等问题,请与 我们联系删除或处理,客服邮箱:html5sh@163.com,稿件内容仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同 其观点或证实其内容的真实性。

网站备案号:浙ICP备17034767号-2