[toc]
drivers/tty/serial/stm32-usart.c 涉及的寄存器及其作用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 static struct stm32_usart_info __maybe_unused stm32f4_info = { .ofs = { .isr = 0x00 , .rdr = 0x04 , .tdr = 0x04 , .brr = 0x08 , .cr1 = 0x0c , .cr2 = 0x10 , .cr3 = 0x14 , .gtpr = 0x18 , .rtor = UNDEF_REG, .rqr = UNDEF_REG, .icr = UNDEF_REG, .presc = UNDEF_REG, .hwcfgr1 = UNDEF_REG, }, .cfg = { .uart_enable_bit = 13 , .has_7bits_data = false , } }; static struct stm32_usart_info __maybe_unused stm32f7_info = { .ofs = { .cr1 = 0x00 , .cr2 = 0x04 , .cr3 = 0x08 , .brr = 0x0c , .gtpr = 0x10 , .rtor = 0x14 , .rqr = 0x18 , .isr = 0x1c , .icr = 0x20 , .rdr = 0x24 , .tdr = 0x28 , .presc = UNDEF_REG, .hwcfgr1 = UNDEF_REG, }, .cfg = { .uart_enable_bit = 0 , .has_7bits_data = true , .has_swap = true , } }; static struct stm32_usart_info __maybe_unused stm32h7_info = { .ofs = { .cr1 = 0x00 , .cr2 = 0x04 , .cr3 = 0x08 , .brr = 0x0c , .gtpr = 0x10 , .rtor = 0x14 , .rqr = 0x18 , .isr = 0x1c , .icr = 0x20 , .rdr = 0x24 , .tdr = 0x28 , .presc = 0x2c , .hwcfgr1 = 0x3f0 , }, .cfg = { .uart_enable_bit = 0 , .has_7bits_data = true , .has_swap = true , .has_wakeup = true , .has_fifo = true , } };
CR1 1 2 3 4 5 6 7 8 9 #define USART_CR1_IE_MASK (GENMASK(8, 4) | BIT(14) | BIT(26) | BIT(27)) #define USART_CR1_IDLEIE BIT(4) #define USART_CR1_RXNEIE BIT(5) #define USART_CR1_TCIE BIT(6) #define USART_CR1_TXEIE BIT(7) #define USART_CR1_PEIE BIT(8) #define USART_CR1_CMIE BIT(14) #define USART_CR1_RTOIE BIT(26) #define USART_CR1_EOBIE BIT(27)
位29 FIFOEN:FIFO模式启用。该位由软件设置和清除。 0:FIFO模式已禁用。 1:FIFO模式已启用。 该位字段仅在USART处于禁用状态(UE=0)时可写入。 注意:FIFO模式仅适用于标准UART通信、SPI主/从模式和智能卡模式,不得在IrDA和LIN模式中启用。
位28 M1:Word长度。此位必须与位12(M0)结合使用以确定Word长度。该位由软件设置或清除。 当M[1:0]为‘00’时:发送1个起始位、8个数据位和n个停止位;当M[1:0]为‘01’时:发送1个起始位、9个数据位和n个停止位;当M[1:0]为‘10’时:发送1个起始位、7个数据位和n个停止位。该位仅在USART功能关闭(UE=0)时可写入。 注:在7位数据长度模式下,智能卡模式、LIN主模式和自动波特率(0x7F和0x55帧检测)不支持。
位27 EOBIE:Bbock中断使能端。该位由软件进行置位和清除操作。 0:中断被抑制;1:当USART_ISR寄存器中的EOBF标志位被设置时,将触发USART中断。注意:若USART不支持智能卡模式,该位将被保留且必须保持复位状态。详见第2020页第48.4节USART实现说明。
位26 RTOIE:接收器超时中断使能。该位由软件设置和清除。 0:中断被抑制;1:当USART_ISR寄存器中的RTOF位被置位时,将触发USART中断。 注:如果USART不支持接收器超时功能,则该位是保留的,必须保持在复位值。第48.4节:USART实现,第2020页。
第25:21位DEAT[4:0]:驱动器使能断言时间。该5位值定义驱动器使能信号激活与起始位开始之间的时间间隔,以采样时间单位表示(1/8或1/16位时间,取决于过采样率)。该位字段仅在USART处于禁用状态(UE=0)时可写入。 注:如果未支持驱动器启用功能,则该位被保留,必须保持在复位值。请参阅第48.4节:第2020页的USART实现。
20:16 DEDT[4:0]位:驱动器使能去激活时间。该5位值定义了传输消息中最后一个停止位结束与驱动器使能信号(DE)去激活之间的时间间隔。该时间以采样时间单位表示(1/8或1/16比特时间,取决于过采样率)。 若在DEDT期间写入USART_TDR寄存器,则新数据仅在DEDT和DEAT时间均经过后才会传输。 该位字段仅在USART处于禁用状态(UE=0)时可写入。 注:如果未支持驱动器启用功能,则该位被保留,必须保持在复位值。请参阅第48.4节:第2020页的USART实现。
第15位OVER8:过采样模式0:过采样倍数为16 1:过采样倍数为8该位仅在USART禁用时(UE=0)可写入。 注意:在LIN、IrDA和Smartcard模式下,该位必须保持清零。
第14位CMIE:字符匹配中断使能。该位由软件设置和清除。 0:中断被抑制;1:当USART_ISR寄存器中的CMF位被置位时,将触发USART中断
位12 M0:Word长度。该位与位28(M1)结合使用以确定Word长度。由软件设置或清除(参见位28(M1)描述)。 该位只能在USART被禁用时写入(UE=0)。
第10位PCE(奇偶校验控制启用):该位用于启用硬件奇偶校验功能(生成与检测)。启用后,系统会在MSB位(M=1时为第9位,M=0时为第8位)插入计算出的奇偶校验位,并对接收数据进行校验。该位由软件控制,设置后PCE将在当前字节后处于激活状态(无论接收或发送过程中)。
0:奇偶校验控制关闭;1:奇偶校验控制开启。该位字段仅在USART关闭时(UE=0)可进行写入操作。
第9位PS:奇偶校验选择。当奇偶校验生成/检测功能启用时(PCE位被置位),该位用于选择奇偶校验模式(奇或偶)。该位由软件控制置位与清除。奇偶校验选择将在当前字节之后进行。 0:偶校验位,1:奇校验位。该位字段仅在USART功能关闭(UE=0)时可写入。
位8 PEIE:PE中断使能。该位由软件设置和清除。 0:中断被抑制;1:当USART_ISR寄存器中的PE=1时,USART中断被触发。
第7位TXEIE:传输数据寄存器为空。该位由软件设置和清除。 0:中断被抑制;1:当USART_ISR寄存器中的TXE=1时触发USART中断。
第6位(位6):传输完成中断使能,该位由软件控制置位与清除。 0:中断被抑制;1:当USART_ISR寄存器中的TC=1时,USART中断被触发。
第5位RXNEIE:接收数据寄存器未清空。该位由软件设置和清除。 0:中断被抑制;1:当USART_ISR寄存器中的ORE=1或RXNE=1时触发USART中断。
第4位(IDLEIE):空闲中断使能,该位由软件控制置位与清除。 0:中断被抑制;1:当USART_ISR寄存器中的IDLE=1时,USART中断被触发
位3 TE:发射器使能。该位用于启用发射器,其设置与清除由软件控制。
0:发射器关闭;1:发射器开启。
注意:传输过程中,TE位的低电平脉冲(‘0’后接‘1’)会在当前字节后发送空闲线(idle line),智能卡模式除外。
为生成空闲字符,TE位不得立即置为‘1’。
为确保所需持续时间,软件可通过轮询USART_ISR寄存器中的TEACK位进行控制。
在智能卡模式下,当TE被设置时,传输开始前会有1比特时间延迟。
位2 RE:接收器启用。该位用于启用接收器,其设置与清除由软件控制。 0:接收器已禁用1:接收器已启用并开始搜索起始位
cr1 位1 UESM:低功耗模式下启用USART。当该位被清零时,USART将无法唤醒MCU的低功耗状态。
当该位被设置时,USART可使MCU从低功耗模式中唤醒。 该位由软件设置和清除。
0:USART无法将MCU从低功耗模式唤醒。
1:USART能够唤醒MCU的低功耗模式。
注意:建议在进入低功耗模式前设置UESM位,退出时清除该位。若USART不支持从停止功能唤醒,该位将被保留并保持复位状态。详见第2020页第48.4节:USART实现。
uart_enable_bit 位0 UE:USART使能。当该位被清零时,USART预分频器和输出端会立即停止工作,所有当前操作将被清除。USART配置保持不变,但所有USART_ISR状态标志会被重置。该位由软件进行设置和清除。
0:USART预分频器及输出功能关闭,进入低功耗模式;
1:USART启用。
注意:若要在不产生线路错误的情况下切换至低功耗模式,需先重置TE位,软件需等待USART_ISR中的TC位被置位后,再重置UE位。
当UE=0时DMA请求也会被重置,因此重置UE位前必须禁用DMA通道。
在智能卡模式(SCEN=1)下,当CLKEN=1时,CK引脚始终可用,无论UE位值如何。
CR2
位23 RTOEN:接收器超时启用。该位由软件设置和清除。 0:接收器超时功能关闭。1:接收器超时功能开启。启用该功能时,若RX线路在RTOR(接收器超时寄存器)设定的持续时间内处于空闲状态(无接收信号),则USART_ISR寄存器中的RTOF标志会被置位。 注:如果USART不支持接收器超时功能,则该位是保留的,必须保持在复位值。请参阅第2020页的第48.4节:USART实现。
第15位SWAP:交换TX/RX引脚。该位由软件设置和清除。 0:TX/RX引脚按标准引脚配置使用。1:TX和RX引脚功能互换,可实现与其他UART的交叉连接。该位字段仅在USART禁用(UE=0)时可写入。
第13:12位STOP[1:0]:停止位。这些位用于设置停止位。 00:1停止位,01:0.5停止位。 10:2停止位11:1.5停止位该位字段仅在USART禁用时(UE=0)可写入。
CR3
位31:29 txftcfg[2:0]:TXFIFO阈值配置
000:TXFIFO达到深度的1/8
001:TXFIFO达到深度的1/4
010:TXFIFO达到深度的1/2
011:TXFIFO达到深度的3/4
100:TXFIFO达到深度的7/8
101:TXFIFO清空
剩余组合:保留
第28位RXFTIE:RXFIFO阈值中断使能。该位由软件进行设置和清除。 0:中断被抑制;1:当接收FIFO达到rxftcfg中设定的阈值时,USART中断被触发。
位23 TXFTIE:TXFIFO阈值中断使能。该位由软件进行设置和清除。 0:中断被抑制;1:当TXFIFO达到txftcfg中设定的阈值时,USART中断被触发。
CR3 位22 WUFIE:从低功耗模式中断使能唤醒。该位由软件设置和清除。
0:中断被抑制;
1:当USART_ISR寄存器中的WUF=1时,会触发USART中断。
注意:进入低功耗模式前必须设置WUFIE。 WUF中断仅在低功耗模式下激活。 如果USART不支持从停止功能唤醒,该位被保留,必须保持在复位值。请参阅第2020页的第48.4节:USART实现。
位21:20 WUS[1:0]:低功耗模式唤醒中断标志选择。该位字段指定触发WUF(低功耗模式唤醒标志)的事件。
00:WUF在地址匹配时激活(由ADD[7:0]和ADDM7定义);
01:保留。
10:WUF在起始位检测时激活;
11:WUF在RXNE/RXFNE时激活。该位字段仅在USART被禁用(UE=0)时可写入。 如果USART不支持从停止功能唤醒,该位被保留,必须保持在复位值。请参阅第2020页的第48.4节:USART实现。
第16位BUSY(忙碌标志):该位由硬件控制置位与复位。当RX线路通信正在进行(检测到成功起始位)时,该位处于激活状态;接收结束时(无论是否成功)则复位。0:USART处于空闲状态(无接收操作);1:接收正在进行中。
DEP第15位:驱动器使能极性选择。0:DE信号为高电平有效;1:DE信号为低电平有效。 该位只能在USART被禁用时写入(UE=0)。 注:如果未支持驱动器启用功能,则该位被保留,必须保持在复位值。请参阅第48.4节:第2020页的USART实现。
第14位DEM:驱动器启用模式。该位通过DE信号使用户能够激活外部收发器控制。0:DE功能禁用。1:DE功能启用。DE信号在RTS引脚输出。 该位只能在USART被禁用时写入(UE=0)。 注意:如果驱动程序启用功能不受支持,则该位被保留。
第13位DDRE:接收错误时禁用DMA。当发生接收错误时,DMA不会被禁用。此时会设置相应的错误标志,但保持RXNE为0以防止数据溢出。因此,DMA请求不会被触发,错误数据不会被传输(即没有DMA请求),而是传输下一个正确接收的数据(适用于智能卡模式)。 1:接收错误发生时,DMA功能将被禁用。此时会触发相应的错误标志位,并同时置位RXNE。DMA请求将被屏蔽,直至错误标志位被清除。这意味着软件必须先禁用DMA请求(将DMAR设为0),或清除RXNE/RXFNE(若启用FIFO模式),才能清除错误标志位。 该位只能在USART被禁用时写入(UE=0)。 注:接收错误包括:奇偶校验错误、帧错误或噪声错误。
位9 CTSE:CTS使能0:CTS硬件流控制禁用1:CTS模式启用,数据仅在CTS输入信号被置零时传输。若数据传输过程中CTS输入信号被激活,则传输会在停止前提前完成。若在CTS信号激活期间向数据寄存器写入数据,传输将被延迟至CTS信号被释放时进行。 该位仅在禁用USART时(UE=0)可写入。注意:若不支持硬件流控制功能,该位将被保留并保持复位状态。详见第2020页第48.4节:USART实现。
位8 RTSE:RTS使能0:RTS硬件流控制禁用1:RTS输出启用,仅在接收缓冲区有空间时请求数据。当前字符传输完成后,数据传输预计停止。当可接收数据时,RTS输出将被取消(拉低至0)。 该位只能在USART被禁用时写入(UE=0)。 注:如果硬件流量控制功能不受支持,则保留该位,且必须保持在复位值。请参阅第48.4节:第2020页的USART实现
第7位DMAT:DMA发射器使能位该位由软件设置/复位1:DMA传输模式启用0:DMA传输模式禁用
位6 DMAR:DMA接收器使能位该位由软件设置/复位1:DMA接收模式启用0:DMA接收模式禁用
位0 EIE:错误中断使能位。当发生帧错误、溢出错误、噪声标志或SPI从机欠载错误时(在USART_ISR寄存器中FE=1或ORE=1或NE=1或UDR=1),需要启用错误中断使能位以生成中断。 0:中断被抑制;1:当USART_ISR寄存器中的FE、ORE、NE或UDR状态为1时(SPI从机模式),将触发中断。
ISR
isr 位7 TXFNF:
当TXFIFO未满时,硬件会自动设置该标志位,表示USART_TDR可进行数据写入。每次向USART_TDR写入数据时,数据都会被存入TXFIFO。该标志位会持续保持激活状态,直到TXFIFO达到满载。
当TXFIFO满载时,该标志位会被清除,表明无法向USART_TDR写入数据。若USART_CR1寄存器中的txfnfie位为1,则会触发中断。0:发送FIFO满;1:发送FIFO未满。
注意:在执行清空请求期间,TXFNF标志位会保持复位状态,直至TXFIFO清空。发送清空请求(通过设置TXFRQ位)后,在向TXFIFO写入数据前需先检查TXFNF标志位(此时TXFNF和TXFE标志位会同时被激活)。 该位用于单缓冲传输。
第3位(ORE):当移位寄存器中的数据已准备好传输至USART_RDR寄存器且RXFF=1时,硬件会设置该位。通过在USART_ICR寄存器中将1写入ORECF,软件可清除该位。 当USART_CR1寄存器中的rxfneie等于1,或当USART_CR3寄存器中的EIE等于1时,就会触发中断。 0:无溢出错误;1:检测到溢出错误。注:当该位被设置时,USART_RDR寄存器内容不会丢失,但移位寄存器会被覆盖。若EIE位被设置,在多缓冲通信过程中若ORE标志被设置,将触发中断。 当USART_CR3寄存器中的位OVRDIS被设置时,该位将被永久强制置为0(不进行溢出检测)
位1 FE:帧错误。当检测到去同步、过量噪声或断字符时,硬件会设置该位。软件通过向USART_ICR寄存器中的FECF位写入1来清除该位。 在智能卡模式下传输数据时,若最大传输次数尝试均未成功(即卡片对数据帧返回NACK),则该位将被置位。 当USART_CR3寄存器中的EIE=1时,将触发中断。 0:未检测到帧错误;1:检测到帧错误或断行字符。注:该错误与USART_RDR中的字符相关。
第0位PE:奇偶校验错误。当接收器模式下发生奇偶校验错误时,硬件会设置该位。通过向USART_ICR寄存器中的PECF写入1,软件可清除该位。若USART_CR1寄存器中的PEIE=1,则会触发中断。 0:无奇偶校验错误;1:奇偶校验错误。注:该错误与USART_RDR寄存器中的字符相关。
TDR USART transmit data register
第31:9位保留,必须保持在复位值。
第8:0位TDR[8:0]:传输数据值,包含待传输的数据字符。
USART_TDR寄存器提供了内部总线与输出移位寄存器之间的并行接口(参见图570)。
当启用奇偶校验(USART_CR1寄存器中的PCE位设为1)时,MSB寄存器中存储的值(根据数据长度为第7位或第8位)将被奇偶校验位覆盖,因此该值不再生效。
注:仅当TXE/TXFNF=1时,才必须写入此寄存器。
RQR USART request register
位4 TXFRQ:传输数据清空请求。当禁用FIFO模式时,向该位写入‘1’会激活TXE标志,从而允许丢弃传输数据。
该位仅在智能卡模式下使用,此时数据因错误(NACK)未被发送且USART_ISR寄存器中的FE标志处于激活状态。
若微控制器(USART)不支持智能卡模式,则该位为保留位,必须保持复位状态。
启用FIFO模式时,TXFRQ位将被置位以清空整个FIFO。这会触发TXFE标志(即USART_ISR寄存器中的第23位,表示发送FIFO为空)。
无论是UART还是智能卡模式,都支持清空发送FIFO的操作。
需要注意的是:在FIFO模式下,系统会在清空请求期间重置TXFNF标志,直到发送FIFO完全清空,从而确保数据寄存器不会被写入任何数据。
位3 RXFRQ:接收数据清空请求。将1写入该位会清空整个接收FIFO,即清除位RXFNE。 启用此功能
RDR USART receive data register
第31:9位保留,必须保持在复位值。
位8:0 RDR[8:0]:接收数据值,包含接收到的数据字符。 RDR寄存器作为输入移位寄存器与内部总线之间的并行接口(参见图570)。 当启用奇偶校验接收时,MSB位读取的数值即为接收到的奇偶校验位。
baud rate register (BRR)
31:16位保留,必须保持在复位值。
第15:0位的BRR[15:0]表示USART波特率;
BRR[15:4]等于UART的15:4分频比;
BRR[3:0]则为UART的3:0分频比。
当OVER8=0时,BRR[3:0]等于UART的3:0分频比。
当OVER8=1时: BRR[2:0]=usartdiv[3:0]右移1位。 BRR[3]必须保持清除状态。
OVER8位: USART过采样模式——速度与稳定性的权衡OVER8(Oversampling by 8)位是STM32 USART外设中一个非常关键的配置,它用于在两种不同的过采样(Oversampling)模式之间进行选择。其核心作用是在通信的 鲁棒性(稳定性)和 最高可达波特率 之间做出权衡。
OVER8 = 0 (过采样 by 16) : 这是默认且更稳定 的模式。
OVER8 = 1 (过采样 by 8) : 这是用于实现更高波特率 的模式。
实现原理分析 要理解OVER8,首先要明白为什么UART通信需要“过采样”。
问题的根源:异步通信 : UART是一种异步通信协议,意味着发送方和接收方没有共享同一个时钟信号。接收方必须在数据流中自行找到每一位的“节拍”。
同步与采样 :
接收方通过检测“起始位”(从高电平到低电平的下降沿)来开始一帧数据的接收,并启动一个内部定时器。
为了正确读取每一位数据(是’0’还是’1’),接收方必须在每一位的中间位置 进行采样。如果在位的边缘采样,很可能因为信号的抖动或噪声而读错。
过采样的作用 :
为了精确地找到每一位的中心点并抵抗噪声,接收方会以远高于波特率的频率来对RX输入线进行采样。这个更高的频率就是“过采样率”。
过采样 by 16 (OVER8 = 0) : 接收方的内部时钟以16倍于波特率 的频率运行。在一个比特持续时间内,它会对信号进行16次采样。通常,它会选择中间的几次采样(例如第8、9、10次)进行“多数表决”,来最终确定这一位是’0’还是’1’。
优点 : 这种方法对噪声有很强的抵抗力,并且能容忍发送方和接收方之间较大的时钟频率偏差。这是最稳定、最可靠 的模式。
过采样 by 8 (OVER8 = 1) : 接收方的内部时钟以8倍于波特率 的频率运行。它在一个比特时间内只进行8次采样,并进行多数表决。
优点 : 达到相同的波特率,它所需要的内部时钟频率只有16倍过采样模式的一半。
总结 :
默认使用16倍过采样 ,以获得最佳的通信稳定性。
只有 在所需波特率过高,导致计算出的USARTDIV小于16时,才被迫 切换到8倍过采样,以牺牲部分稳定性为代价,来达到更高的通信速率。
stm32_serial_pm_ops 电源管理实现 本代码片段展示了 STM32 USART 驱动中电源管理(PM)回调函数的具体实现 。它清晰地区分了两种不同的电源管理场景:系统级睡眠 (System Sleep)和运行时电源管理 (Runtime PM),并为每种场景提供了精细化的硬件操作。其核心功能是:在系统进入低功耗状态时,安全地挂起 UART 操作,并根据设备是否配置为“唤醒源”,来决定是完全关闭外设 还是将其置于一个特殊的、能响应唤醒事件的低功耗模式 。
实现原理分析 此驱动的电源管理实现是现代嵌入式 Linux PM 框架的一个优秀范例,它结合了 TTY/Serial 核心、pinctrl 子系统、时钟管理和 DMA 操作。
区分系统睡眠与运行时 PM :
运行时 PM (runtime_suspend/resume) :
目标 : 轻量级的、频繁的功耗优化。
操作 : stm32_usart_runtime_suspend 只执行一个动作:clk_disable_unprepare(stm32port->clk)。它仅仅是关闭(gate) USART 外设的时钟。这是一种快速且低开销的操作,因为外设的寄存器状态和供电都保持不变。当 runtime_resume 被调用时,只需重新使能时钟,外设就可以立即恢复工作。
系统睡眠 (serial_suspend/resume) :
目标 : 深度睡眠,系统的大部分组件都会断电或进入最低功耗状态。
操作 : stm32_usart_serial_suspend 执行的操作要复杂得多。它不仅要处理时钟,还要处理 pinctrl 状态 和唤醒配置 。
系统睡眠的核心逻辑 (serial_suspend) :
标准挂起 : uart_suspend_port(...) 是第一步。这是调用 serial_core 提供的标准函数,它会停止 UART 的发送和接收中断,并等待发送 FIFO 排空,确保在硬件状态改变前,软件层面已经静止。
唤醒源配置 (stm32_usart_serial_en_wakeup) : a. if (device_may_wakeup(dev)): 它检查设备树中是否将此 UART 配置为一个唤醒源(wakeup-source 属性)。 b. 如果是 唤醒源,它就调用 stm32_usart_serial_en_wakeup(port, true)。这个函数会执行一系列 STM32 特有的寄存器操作(设置 UESM 和 WUFIE 位),将 USART 置于一个特殊的低功耗模式,在该模式下,它仍然能检测到 RX 线上指定的唤醒事件(如起始位)并产生唤醒中断。 c. DMA 处理 : 如果正在使用 DMA 接收,en_wakeup 还有一个关键的附加逻辑:它必须先暂停并终止 DMA 传输,并将 DMA 缓冲区中可能已有的数据冲刷(flush)到 TTY 层。这是因为在低功耗模式下,DMA 控制器通常会被关闭。
Pinctrl 状态管理 : a. if (console_suspend_enabled || !uart_console(port)): 它检查是否允许挂起控制台。 b. 如果允许,它会根据设备是否是唤醒源,来选择将 UART 的引脚(TX, RX 等)设置为 idle 状态或 sleep 状态。这些状态在设备树的 pinctrl 节点中定义,sleep 状态通常会将引脚配置为能实现最低漏电流的状态(例如,配置为模拟输入)。
对称的恢复逻辑 (serial_resume) :
stm32_usart_serial_resume 执行完全相反的操作: a. pinctrl_pm_select_default_state(dev): 首先将引脚恢复到正常的 default 功能状态。 b. stm32_usart_serial_en_wakeup(port, false): 如果之前启用了唤醒,现在就禁用它,并将 USART 恢复到正常操作模式。如果之前使用了 DMA,它还会尝试重新启动 DMA 接收。 c. uart_resume_port(...): 最后调用 serial_core 的标准函数,重新使能 UART 中断,恢复正常的数据收发。
特定场景分析:单核、无MMU的STM32H750平台
USART_CR1_UESM 和 USART_CR3_WUFIE:STM32H7 USART低功耗模式与唤醒控制
这两个寄存器位是STM32H7系列微控制器USART外设电源管理和低功耗唤醒功能的核心。UESM负责决定USART在系统进入“Stop模式”时自身的死活,而WUFIE则决定当USART成功唤醒系统时,是否伴随产生一个中断。它们共同构成了USART从深度睡眠中唤醒系统的硬件基础。
STM32H7系列微控制器为了实现极致的低功耗,设计了多种“Stop模式”。在这些模式下,大部分时钟和外设都会被关闭以节省能源。要让一个外设(如USART)在Stop模式下依然能够工作并唤醒系统,必须经过精确的配置。
USART_CR1_UESM (USART Enable in Stop Mode) :
作用 : 这是USART能够作为唤醒源的物理前提 。它位于控制寄存器1中,直接决定了当CPU内核进入Stop模式后,USART外设是否也被关闭。
UESM = 0 (默认) : 当系统进入Stop模式时,USART外设的时钟被完全门控(gated)。此时,USART处于“冻结”状态,无法进行任何操作,包括接收数据或检测线路活动。因此,它不能 作为唤醒源。
UESM = 1 : 当系统进入Stop模式时,USART外设的时钟(必须来自一个在Stop模式下仍然运行的时钟源,如HSI或LSE)保持运行。这使得USART的接收逻辑能够持续监控RX引脚,以侦测特定的唤醒事件(如检测到起始位、地址匹配或指定的RXNE事件)。只有在此模式下,USART才具备唤醒系统的能力。
USART_CR3_WUFIE (Wakeup From Stop Mode Interrupt Enable) :
作用 : 这是唤醒事件的中断使能 开关。它位于控制寄存器3中,用于控制当一个唤醒事件被成功检测到后,是否向NVIC(嵌套向量中断控制器)发出一个中断请求。
前提 : WUFIE位的设置只有在UESM位被置1时才有意义。
WUFIE = 0 : 当一个唤醒事件(例如RX引脚上出现一个下降沿)发生时,USART会向电源控制器发出一个唤醒信号,将系统从Stop模式中唤醒。系统代码会从停止的地方继续执行。但是,不会产生 USART相关的中断。程序需要通过检查USART_ISR(中断和状态寄存器)中的WUF(Wakeup Flag)标志位,来判断系统是被USART唤醒的。
WUFIE = 1 : 当唤醒事件发生时,除了唤醒系统,USART还会同时 产生一个中断请求。如果该中断在NVIC中被使能,CPU在唤醒后会立即跳转到对应的USART中断服务程序(ISR)。这允许系统对唤醒事件做出最快速的响应,例如立即开始接收数据。
总结 : UESM是“能不能唤醒”的开关,而WUFIE是“唤醒时报不报告(中断)”的开关。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 static int __maybe_unused stm32_usart_serial_en_wakeup (struct uart_port *port, bool enable) { struct stm32_port *stm32_port = to_stm32_port(port); const struct stm32_usart_offsets *ofs = &stm32_port->info->ofs; struct tty_port *tport = &port->state->port; int ret; unsigned int size = 0 ; unsigned long flags; if (!stm32_port->wakeup_src || !tty_port_initialized(tport)) return 0 ; if (enable) { stm32_usart_set_bits(port, ofs->cr1, USART_CR1_UESM); stm32_usart_set_bits(port, ofs->cr3, USART_CR3_WUFIE); mctrl_gpio_enable_irq_wake(stm32_port->gpios); if (stm32_port->rx_ch) { uart_port_lock_irqsave(port, &flags); if (!stm32_usart_rx_dma_pause(stm32_port)) size += stm32_usart_receive_chars(port, true ); stm32_usart_rx_dma_terminate(stm32_port); uart_unlock_and_check_sysrq_irqrestore(port, flags); if (size) tty_flip_buffer_push(tport); } stm32_usart_receive_chars(port, false ); } else { if (stm32_port->rx_ch) { ret = stm32_usart_rx_dma_start_or_resume(port); if (ret) return ret; } mctrl_gpio_disable_irq_wake(stm32_port->gpios); stm32_usart_clr_bits(port, ofs->cr1, USART_CR1_UESM); stm32_usart_clr_bits(port, ofs->cr3, USART_CR3_WUFIE); } return 0 ; } static int __maybe_unused stm32_usart_serial_suspend (struct device *dev) { struct uart_port *port = dev_get_drvdata(dev); int ret; uart_suspend_port(&stm32_usart_driver, port); if (device_may_wakeup(dev) || device_wakeup_path(dev)) { ret = stm32_usart_serial_en_wakeup(port, true ); if (ret) return ret; } if (console_suspend_enabled || !uart_console(port)) { if (device_may_wakeup(dev) || device_wakeup_path(dev)) pinctrl_pm_select_idle_state(dev); else pinctrl_pm_select_sleep_state(dev); } return 0 ; } static int __maybe_unused stm32_usart_serial_resume (struct device *dev) { struct uart_port *port = dev_get_drvdata(dev); int ret; pinctrl_pm_select_default_state(dev); if (device_may_wakeup(dev) || device_wakeup_path(dev)) { ret = stm32_usart_serial_en_wakeup(port, false ); if (ret) return ret; } return uart_resume_port(&stm32_usart_driver, port); } static int __maybe_unused stm32_usart_runtime_suspend (struct device *dev) { clk_disable_unprepare(stm32port->clk); return 0 ; } static int __maybe_unused stm32_usart_runtime_resume (struct device *dev) { return clk_prepare_enable(stm32port->clk); }
stm32_usart_init 驱动模块:双重注册与电源管理 本代码片段展示了 STMicroelectronics STM32 系列微控制器 USART/UART 串行端口驱动 的模块初始化、退出以及电源管理(PM)操作集的定义。其核心功能是将自己同时注册为两种不同类型的驱动 :一种是标准的 platform_driver,用于与内核的平台总线和设备树进行匹配;另一种是 uart_driver,用于向 TTY 子系统注册一个串口驱动模板。此外,它还定义了一套电源管理回调函数,以支持系统级的睡眠/唤醒和运行时的动态电源管理。
实现原理分析 此驱动的设计体现了 Linux 内核中针对特定类型设备(如串口、I2C、SPI 控制器)的**“框架驱动”与“总线驱动”相结合**的典型模式。
双重注册 (stm32_usart_init) :
问题 : 一个串口设备驱动需要与内核的两个不同子系统交互: a. 平台总线 : 它需要通过 probe 函数从设备树中获取硬件资源(寄存器地址、中断号、时钟、DMA 通道等)。这需要它是一个 platform_driver。 b. TTY/Serial 核心 : 它需要向 TTY 子系统提供一个 uart_ops 结构(包含 startup, shutdown, tx_empty 等操作),以便 TTY 核心可以控制串口硬件并创建 /dev/ttySTMX 这样的设备节点。这需要它注册为一个 uart_driver。
解决方案 : stm32_usart_init 函数连续执行了两次注册: a. uart_register_driver(&stm32_usart_driver): 将 stm32_usart_driver(这是一个 uart_driver 结构,未在此代码段中完全展示)注册到 serial_core。这相当于向 TTY 子系统声明:“我是一个可以管理 N 个 STM32 串口的驱动模板”。 b. platform_driver_register(&stm32_serial_driver): 将 stm32_serial_driver(这是一个 platform_driver 结构)注册到平台总线。
工作流程 : 当内核启动并解析设备树时,平台总线会为每个匹配 stm32_match 的 USART 设备节点创建一个 platform_device。这个 platform_device 会触发 stm32_usart_serial_probe 函数的调用。在 probe 函数内部,驱动会获取硬件资源,然后调用 uart_add_one_port(),这个函数会从之前注册的 stm32_usart_driver 模板中“克隆”出一个新的串口端口实例,并将其与当前的 platform_device 关联起来。
电源管理 (stm32_serial_pm_ops) :
驱动通过 .driver.pm 字段将其电源管理操作集 stm32_serial_pm_ops 注册到设备模型。
系统睡眠 (SYSTEM_SLEEP_PM_OPS) : stm32_usart_serial_suspend 和 resume 会在整个系统进入/退出低功耗状态(如 suspend-to-RAM)时被调用。它们通常负责保存/恢复 USART 的寄存器状态。
运行时电源管理 (RUNTIME_PM_OPS) : stm32_usart_runtime_suspend 和 resume 用于更细粒度的动态电源管理。当串口设备在一段时间内没有被使用(没有打开的文件描述符,发送缓冲区为空)时,TTY 核心或 serial_core 可以请求设备进入运行时挂起状态。stm32_usart_runtime_suspend 可能会执行关闭 USART 的时钟门控(clock gating)等操作来节省功耗。当设备再次被需要时,resume 函数会被自动调用以重新开启时钟。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 static const struct dev_pm_ops stm32_serial_pm_ops = { SET_RUNTIME_PM_OPS(stm32_usart_runtime_suspend, stm32_usart_runtime_resume, NULL ) SET_SYSTEM_SLEEP_PM_OPS(stm32_usart_serial_suspend, stm32_usart_serial_resume) }; static struct platform_driver stm32_serial_driver = { .probe = stm32_usart_serial_probe, .remove = stm32_usart_serial_remove, .driver = { .name = DRIVER_NAME, .pm = &stm32_serial_pm_ops, .of_match_table = of_match_ptr(stm32_match), }, }; static int __init stm32_usart_init (void ) { static char banner[] __initdata = "STM32 USART driver initialized" ; int ret; pr_info("%s\n" , banner); ret = uart_register_driver(&stm32_usart_driver); if (ret) return ret; ret = platform_driver_register(&stm32_serial_driver); if (ret) uart_unregister_driver(&stm32_usart_driver); return ret; } static void __exit stm32_usart_exit (void ) { platform_driver_unregister(&stm32_serial_driver); uart_unregister_driver(&stm32_usart_driver); } module_init(stm32_usart_init); module_exit(stm32_usart_exit); MODULE_ALIAS("platform:" DRIVER_NAME); MODULE_DESCRIPTION("STMicroelectronics STM32 serial port driver" ); MODULE_LICENSE("GPL v2" );
stm32_usart_console: STM32串口的内核早期输出与控制台实现本代码片段展示了Linux内核中一个典型的平台设备驱动如何实现内核控制台 功能。其核心是通过注册一个struct console实例,将内核的早期打印信息(printk)和紧急信息(如发生oops或panic时)直接路由到STM32的物理USART硬件。这段代码实现了控制台的初始化(波特率、数据位等配置)和最重要的字符输出功能(stm32_usart_console_write),确保了在完整的TTY子系统和用户空间程序准备就绪之前,内核就具备了基本的日志输出能力。
实现原理分析 内核控制台是一个独立于标准TTY驱动的、更底层的机制。它通过一个简单的函数指针接口,允许printk直接与硬件驱动交互。
控制台注册 (stm32_console) :
驱动定义了一个静态的struct console变量stm32_console。
.name: 定义了控制台的名称,如ttySTM。
.write: 这是最重要的成员 。它指向stm32_usart_console_write函数,printk最终会调用这个函数来输出字符串。
.setup: 指向stm32_usart_console_setup,用于解析内核启动参数(如console=ttySTM0,115200n8)并对控制台进行早期初始化。
.flags = CON_PRINTBUFFER: 这个标志告诉printk系统,此控制台是可靠的,可以将内核日志缓冲区的内容打印出来。
这个console结构体最终被关联到uart_driver的.cons成员上,在UART驱动注册时,内核的打印系统会发现并注册这个新的控制台。
控制台写入 (stm32_usart_console_write) :
此函数是printk输出的最终目的地。它接收一个字符串和长度。
同步与安全 :
它首先通过uart_port_lock_irqsave获取端口的自旋锁并禁用中断。这是至关重要的,因为printk可能在任何上下文(包括中断中)被调用,必须防止重入和并发访问硬件。
在oops_in_progress(内核发生严重错误)的特殊情况下,它会使用trylock尝试获取锁。如果获取不到(例如,oops发生在持有该锁的代码中),它不会死锁,而是会放弃输出,这是一种安全回退机制。
硬件临时配置 :
它会保存控制寄存器CR1的当前值,然后强制禁用所有USART中断(~USART_CR1_IE_MASK),并强制使能发送器(USART_CR1_TE)。这是一个非常健壮的设计:无论当前串口处于何种状态(例如,可能被TTY层配置为仅接收),控制台写入都能临时、强制地打开发送功能来输出紧急信息。
轮询式输出 : 它调用uart_console_write,这是一个通用辅助函数,它会循环遍历字符串中的每一个字符,并调用stm32_usart_console_putchar来逐个发送。
恢复状态 : 输出完成后,它会将CR1寄存器恢复到之前保存的状态,确保控制台的紧急输出不会永久性地破坏串口的正常TTY配置。
单字符输出 (stm32_usart_console_putchar) :
这是真正与硬件交互的函数。它实现了阻塞式轮询发送 。
readl_relaxed_poll_timeout_atomic: 这是一个内核提供的轮询辅助宏。它会反复读取状态寄存器ISR,直到TXE(Transmit Data Register Empty,发送数据寄存器空)标志位被硬件置位,或者超时发生。TXE=1表示硬件已经将上一个字符发送出去,现在可以接收下一个字符了。
writel_relaxed: 当TXE有效后,此函数将字符写入TDR(Transmit Data Register),硬件会自动开始发送该字符。
这种轮询方式效率不高,但在早期控制台和紧急情况下是唯一可靠的方法,因为它不依赖于中断。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 static void __maybe_unused stm32_usart_console_putchar (struct uart_port *port, unsigned char ch) { struct stm32_port *stm32_port = to_stm32_port(port); const struct stm32_usart_offsets *ofs = &stm32_port->info->ofs; u32 isr; int ret; ret = readl_relaxed_poll_timeout_atomic(port->membase + ofs->isr, isr, (isr & USART_SR_TXE), 100 , STM32_USART_TIMEOUT_USEC); if (ret != 0 ) { dev_err(port->dev, "在UART TX发送数据时出错:%d\n" , ret); return ; } writel_relaxed(ch, port->membase + ofs->tdr); } #ifdef CONFIG_SERIAL_STM32_CONSOLE static void stm32_usart_console_write (struct console *co, const char *s, unsigned int cnt) { struct uart_port *port = &stm32_ports[co->index].port; struct stm32_port *stm32_port = to_stm32_port(port); const struct stm32_usart_offsets *ofs = &stm32_port->info->ofs; const struct stm32_usart_config *cfg = &stm32_port->info->cfg; unsigned long flags; u32 old_cr1, new_cr1; int locked = 1 ; if (oops_in_progress) locked = uart_port_trylock_irqsave(port, &flags); else uart_port_lock_irqsave(port, &flags); old_cr1 = readl_relaxed(port->membase + ofs->cr1); new_cr1 = old_cr1 & ~USART_CR1_IE_MASK; new_cr1 |= USART_CR1_TE | BIT(cfg->uart_enable_bit); writel_relaxed(new_cr1, port->membase + ofs->cr1); uart_console_write(port, s, cnt, stm32_usart_console_putchar); writel_relaxed(old_cr1, port->membase + ofs->cr1); if (locked) uart_port_unlock_irqrestore(port, flags); } static int stm32_usart_console_setup (struct console *co, char *options) { struct stm32_port *stm32port ; int baud = 9600 ; int bits = 8 ; int parity = 'n' ; int flow = 'n' ; if (co->index >= STM32_MAX_PORTS) return -ENODEV; stm32port = &stm32_ports[co->index]; if (stm32port->port.mapbase == 0 || !stm32port->port.membase) return -ENXIO; if (options) uart_parse_options(options, &baud, &parity, &bits, &flow); return uart_set_options(&stm32port->port, co, baud, parity, bits, flow); } static struct console stm32_console = { .name = STM32_SERIAL_NAME, .device = uart_console_device, .write = stm32_usart_console_write, .setup = stm32_usart_console_setup, .flags = CON_PRINTBUFFER, .index = -1 , .data = &stm32_usart_driver, }; #define STM32_SERIAL_CONSOLE (&stm32_console) static struct uart_driver stm32_usart_driver = { .driver_name = DRIVER_NAME, .dev_name = STM32_SERIAL_NAME, .major = 0 , .minor = 0 , .nr = STM32_MAX_PORTS, .cons = STM32_SERIAL_CONSOLE, }; #endif
stm32_usart_init_port & stm32_usart_of_get_port: STM32串口驱动的设备树解析与核心初始化本代码片段展示了STM32串口平台驱动在probe阶段的核心初始化函数stm32_usart_init_port,以及用于从设备树(Device Tree)中解析端口号和基本属性的辅助函数stm32_usart_of_get_port。其核心功能是将设备树中描述的硬件资源(如寄存器地址、中断号、时钟、GPIO、FIFO阈值等),转化为内核uart_port结构体中的标准成员 ,为后续将此端口注册到UART核心层做好万全准备。
实现原理分析 这是Linux平台驱动与设备树紧密结合的典型实现。驱动代码本身是通用的,而具体的硬件配置则由设备树文件(.dts)提供,实现了软件与硬件描述的分离。
端口识别与分配 (stm32_usart_of_get_port) :
职责 : 确定当前探测的设备(如usart1)对应于驱动内部哪个端口实例(如stm32_ports[0])。
of_alias_get_id(np, "serial"): 这是关键。它会在设备树的aliases节点中查找类似serial0 = &usart1;的条目。如果当前设备的节点是usart1,此函数就会返回0。这是一种将不稳定的设备节点路径映射到稳定、从0开始的线路号(line number)的标准方法。
属性解析 : 在找到端口号后,它会解析一些基本的布尔属性,如uart-has-rcts,来判断硬件是否支持原生的RTS/CTS流控。
核心初始化 (stm32_usart_init_port) :
职责 : 这是一个多步骤的初始化序列,负责填充uart_port和stm32_port结构体。
资源映射 :
platform_get_irq: 从设备树中获取中断号。
devm_platform_get_and_ioremap_resource: 这是一个“设备资源管理”的便利函数。它会自动从设备树的reg属性中获取寄存器的物理地址和大小,并通过ioremap将其映射到内核的虚拟地址空间,然后将虚拟地址存入port->membase。devm_前缀意味着在驱动卸载时,这块内存映射会被自动释放,简化了错误处理和清理代码。
devm_clk_get: 从设备树的clocks属性中获取指向时钟控制器的句柄。
时钟管理 :
clk_prepare_enable: 在读取时钟频率之前,必须先使能时钟。
clk_get_rate: 获取USART外设的实际输入时钟频率,并存入port->uartclk。这个值是后续计算波特率的基础 。
clk_disable_unprepare: 在错误处理路径中,会关闭时钟以节省功耗。
FIFO配置 (stm32_usart_get_ftcfg) :
FIFO大小检测 : 它首先尝试从硬件配置寄存器HWCFGR1中读取FIFO的实际大小。对于没有这个寄存器的旧型号(如H7),它会使用一个预定义的宏STM32H7_USART_FIFO_SIZE。
阈值解析 : 它会从设备树中读取用户自定义的rx-threshold和tx-threshold属性。如果用户没有定义,则默认使用FIFO大小的一半。
阈值量化 : USART硬件的FIFO阈值寄存器(如CR3的RXFTCFG位)只能接受几个离散的值(如1/8, 1/4, 1/2…满)。stm32_usart_get_ftcfg的职责就是将用户给出的字节数,量化 到硬件支持的最接近且不小于该值的档位,并返回对应的寄存器编码(0到5)。这个过程通过查表stm32h7_usart_fifo_thresh_cfg来完成。
RS-485支持 : 它为uart_port的RS-485相关成员赋值,声明了驱动支持的RS-485功能,并将配置回调函数指向stm32_usart_config_rs485。
特定场景分析:单核、无MMU的STM32H750平台 硬件交互
设备树 (DTS) : 对于STM32H750上的usart1,设备树中会有类似如下的条目:1 2 3 4 5 6 7 8 9 10 11 12 usart1: serial@40010000 { compatible = "st,stm32h7-usart" ; reg = <0x40010000 0x400 > ; interrupts = <37 > ; clocks = <&rcc_c1_pclk2 > ; dmas = <&dma1_stream0 0x10 > , <&dma1_stream1 0x11 > ; dma-names = "rx" , "tx" ; pinctrl-0 = <&usart1_pins_a > ; pinctrl-names = "default" ; uart-has-rtscts ; rx-threshold = <12 > ; };
stm32_usart_init_port函数就是逐一解析这些属性,并将它们转化为内核对象。
FIFO阈值 : 假设FIFO大小为16字节(STM32H7_USART_FIFO_SIZE)。用户设置rx-threshold = <12>。stm32_usart_get_ftcfg会计算:
档位0: 16 * 1/8 = 2
档位1: 16 * 1/4 = 4
档位2: 16 * 1/2 = 8
档位3: 16 * 3/4 = 12
函数发现档位3的值(12)大于等于用户请求的12字节,于是循环中断,返回索引3。这个值3将被保存到stm32port->rxftcfg中,并在后续配置USART_CR3寄存器的RXFTCFG位域时被使用。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 struct stm32_usart_thresh_ratio { int mul; int div; }; static const struct stm32_usart_thresh_ratio stm32h7_usart_fifo_thresh_cfg [] = { {1 , 8 }, {1 , 4 }, {1 , 2 }, {3 , 4 }, {7 , 8 }, {1 , 1 } }; static int stm32_usart_get_thresh_value (u32 fifo_size, int index) { return fifo_size * stm32h7_usart_fifo_thresh_cfg[index].mul / stm32h7_usart_fifo_thresh_cfg[index].div; } static int stm32_usart_get_ftcfg (struct platform_device *pdev, struct stm32_port *stm32port, const char *p, int *ftcfg) { const struct stm32_usart_offsets *ofs = &stm32port->info->ofs; u32 bytes, i, cfg8; int fifo_size; if (WARN_ON(ofs->hwcfgr1 == UNDEF_REG)) return 1 ; cfg8 = FIELD_GET(USART_HWCFGR1_CFG8, readl_relaxed(stm32port->port.membase + ofs->hwcfgr1)); fifo_size = cfg8 ? 1 << cfg8 : STM32H7_USART_FIFO_SIZE; if (of_property_read_u32(pdev->dev.of_node, p, &bytes)) bytes = fifo_size / 2 ; for (i = 0 ; i < ARRAY_SIZE(stm32h7_usart_fifo_thresh_cfg); i++) { if (stm32_usart_get_thresh_value(fifo_size, i) >= bytes) break ; } if (i >= ARRAY_SIZE(stm32h7_usart_fifo_thresh_cfg)) i = ARRAY_SIZE(stm32h7_usart_fifo_thresh_cfg) - 1 ; *ftcfg = i; return fifo_size; } static void stm32_usart_deinit_port (struct stm32_port *stm32port) { clk_disable_unprepare(stm32port->clk); } static const struct serial_rs485 stm32_rs485_supported = { .flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | SER_RS485_RTS_AFTER_SEND | SER_RS485_RX_DURING_TX, .delay_rts_before_send = 1 , .delay_rts_after_send = 1 , }; static int stm32_usart_init_port (struct stm32_port *stm32port, struct platform_device *pdev) { struct uart_port *port = &stm32port->port; struct resource *res ; int ret, irq; irq = platform_get_irq(pdev, 0 ); if (irq < 0 ) return irq; port->iotype = UPIO_MEM; port->flags = UPF_BOOT_AUTOCONF; port->ops = &stm32_uart_ops; ort->dev = &pdev->dev; port->has_sysrq = IS_ENABLED(CONFIG_SERIAL_STM32_CONSOLE); port->irq = irq; port->rs485_config = stm32_usart_config_rs485; port->rs485_supported = stm32_rs485_supported; ret = stm32_usart_init_rs485(port, pdev); if (ret) return ret; stm32port->wakeup_src = stm32port->info->cfg.has_wakeup && of_property_read_bool(pdev->dev.of_node, "wakeup-source" ); stm32port->swap = stm32port->info->cfg.has_swap && of_property_read_bool(pdev->dev.of_node, "rx-tx-swap" ); port->membase = devm_platform_get_and_ioremap_resource(pdev, 0 , &res); if (IS_ERR(port->membase)) return PTR_ERR(port->membase); port->mapbase = res->start; spin_lock_init(&port->lock); stm32port->clk = devm_clk_get(&pdev->dev, NULL ); ret = clk_prepare_enable(stm32port->clk); stm32port->port.uartclk = clk_get_rate(stm32port->clk); if (stm32port->fifoen) { stm32_usart_get_ftcfg(pdev, stm32port, "rx-threshold" , &stm32port->rxftcfg); port->fifosize = stm32_usart_get_ftcfg(pdev, stm32port, "tx-threshold" , &stm32port->txftcfg); } if (stm32port->hw_flow_control) { if (mctrl_gpio_to_gpiod(stm32port->gpios, UART_GPIO_CTS) || mctrl_gpio_to_gpiod(stm32port->gpios, UART_GPIO_RTS)) { dev_err(&pdev->dev, "Conflicting RTS/CTS config\n" ); ret = -EINVAL; goto err_clk; } } return ret; err_clk: clk_disable_unprepare(stm32port->clk); return ret; } static struct stm32_port *stm32_usart_of_get_port (struct platform_device *pdev) { id = of_alias_get_id(np, "serial" ); return &stm32_ports[id]; }
stm32_usart_serial_probe stm32_usart_serial_remove : STM32串口驱动的DMA初始化与健壮性设计本代码片段展示了STM32串口驱动的核心初始化函数stm32_usart_serial_probe,以及与之配套的DMA配置和资源清理函数。其核心功能是在驱动探测(probe)阶段,为串口的接收(RX)和发送(TX)尝试 配置DMA通道。这段代码的一个关键特性是其健壮性设计 :它优雅地处理了DMA资源暂时不可用(EPROBE_DEFER)或完全不可用(其他错误)的情况,在失败时能够自动回退到传统的中断模式 ,确保串口的基本功能不受影响。
实现原理分析 该机制是现代Linux设备驱动模型中,一个集成了设备树(Device Tree)、DMA引擎抽象层和运行时电源管理(Runtime PM)的典型范例。
平台驱动probe流程 (stm32_usart_serial_probe) :
资源获取 :
dma_request_chan: 这是与DMA子系统交互的第一步。驱动向通用的DMA引擎框架请求名为”rx”和”tx”的通道。这些名称必须与设备树(Device Tree)文件中为该USART节点定义的dma-names属性相匹配。
依赖处理 (EPROBE_DEFER) :
dma_request_chan可能会返回-EPROBE_DEFER。这是一个特殊的错误码,意味着DMA控制器驱动虽然存在,但尚未准备就绪。probe函数捕获此错误后,会立即中止并返回-EPROBE_DEFER。这会通知内核驱动核心,稍后当DMA驱动准备好后,再重新尝试探测(re-probe)这个串口驱动。这是处理驱动间加载顺序依赖的標準、优雅的方式。
健壮的回退机制 (Fallback to Interrupt Mode) :
如果dma_request_chan返回任何非 -EPROBE_DEFER的错误,或者后续的DMA配置函数stm32_usart_of_dma_rx/tx_probe失败,驱动不会整体失败。相反,它会释放已申请的DMA资源,并将对应的通道指针(stm32port->rx_ch或tx_ch)设为NULL。驱动的其他部分会根据这些指针是否为NULL来决定是使用DMA路径还是传统的中断路径。最后,它会打印一条信息,告知用户该通道已回退到中断模式。
注册到UART核心 : 在所有资源(包括可选的DMA)都配置完毕后,uart_add_one_port函数会将这个uart_port实例注册到通用的UART核心层。从这一刻起,一个/dev/ttySTMx设备节点就会出现,用户空间程序可以通过标准的TTY接口来使用这个串口。
错误处理 : probe函数使用了goto语句链来实现一个清晰的、栈式的错误回滚路径。如果在某一步失败,它会跳转到相应的标签,然后逐层“撤销”所有已经成功执行的初始化步骤(如释放DMA通道、反初始化端口等),确保系统不会留下处于半初始化状态的“僵尸”设备。
DMA通道配置 (stm32_usart_of_dma_rx/tx_probe) :
一致性内存分配 (dma_alloc_coherent) : 这是DMA操作的关键。该函数分配了一块特殊的内存缓冲区,这块内存保证了CPU的缓存和主存之间的一致性 ,并且对DMA控制器是物理上可见的。它返回两个地址:一个供CPU使用的虚拟地址(stm32port->rx_buf),另一个是供DMA控制器使用的总线地址(stm32port->rx_dma_buf)。这避免了复杂的缓存刷新/失效操作。
从设备配置 (dma_slave_config) : 驱动填充一个dma_slave_config结构体,向DMA引擎描述这次传输的特性:
src_addr/dst_addr: 指定了传输的源地址和目的地址。对于RX,源是USART的接收数据寄存器(RDR),目的是内存缓冲区。对于TX,源是内存缓冲区,目的是USART的发送数据寄存器(TDR)。
src_addr_width/dst_addr_width: 指定了外设端的数据宽度(这里是1字节),确保DMA控制器每次只从/向RDR/TDR传输一个字节。
dmaengine_slave_config函数将这个配置应用到DMA通道。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 static void stm32_usart_of_dma_rx_remove (struct stm32_port *stm32port, struct platform_device *pdev) { if (stm32port->rx_buf) dma_free_coherent(&pdev->dev, RX_BUF_L, stm32port->rx_buf, stm32port->rx_dma_buf); } static inline int dmaengine_slave_config (struct dma_chan *chan, struct dma_slave_config *config) { if (chan->device->device_config) return chan->device->device_config(chan, config); return -ENOSYS; } static int stm32_usart_of_dma_rx_probe (struct stm32_port *stm32port, struct platform_device *pdev) { const struct stm32_usart_offsets *ofs = &stm32port->info->ofs; struct uart_port *port = &stm32port->port; struct device *dev = &pdev->dev; struct dma_slave_config config ; int ret; stm32port->rx_buf = dma_alloc_coherent(dev, RX_BUF_L, &stm32port->rx_dma_buf, GFP_KERNEL); if (!stm32port->rx_buf) return -ENOMEM; memset (&config, 0 , sizeof (config)); config.src_addr = port->mapbase + ofs->rdr; config.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; ret = dmaengine_slave_config(stm32port->rx_ch, &config); if (ret < 0 ) { dev_err(dev, "rx dma通道配置失败\n" ); stm32_usart_of_dma_rx_remove(stm32port, pdev); return ret; } return 0 ; } static void stm32_usart_of_dma_tx_remove (struct stm32_port *stm32port, struct platform_device *pdev) { if (stm32port->tx_buf) dma_free_coherent(&pdev->dev, TX_BUF_L, stm32port->tx_buf, stm32port->tx_dma_buf); } static int stm32_usart_of_dma_tx_probe (struct stm32_port *stm32port, struct platform_device *pdev) { const struct stm32_usart_offsets *ofs = &stm32port->info->ofs; struct uart_port *port = &stm32port->port; struct device *dev = &pdev->dev; struct dma_slave_config config ; int ret; stm32port->tx_buf = dma_alloc_coherent(dev, TX_BUF_L, &stm32port->tx_dma_buf, GFP_KERNEL); if (!stm32port->tx_buf) return -ENOMEM; memset (&config, 0 , sizeof (config)); config.dst_addr = port->mapbase + ofs->tdr; config.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE; ret = dmaengine_slave_config(stm32port->tx_ch, &config); if (ret < 0 ) { dev_err(dev, "tx dma通道配置失败\n" ); stm32_usart_of_dma_tx_remove(stm32port, pdev); return ret; } return 0 ; } static int stm32_usart_serial_probe (struct platform_device *pdev) { struct stm32_port *stm32port ; int ret; stm32port = stm32_usart_of_get_port(pdev); if (!stm32port) return -ENODEV; stm32port->info = of_device_get_match_data(&pdev->dev); if (!stm32port->info) return -EINVAL; stm32port->rx_ch = dma_request_chan(&pdev->dev, "rx" ); if (PTR_ERR(stm32port->rx_ch) == -EPROBE_DEFER) return -EPROBE_DEFER; if (IS_ERR(stm32port->rx_ch)) stm32port->rx_ch = NULL ; stm32port->tx_ch = dma_request_chan(&pdev->dev, "tx" ); if (PTR_ERR(stm32port->tx_ch) == -EPROBE_DEFER) { ret = -EPROBE_DEFER; goto err_dma_rx; } if (IS_ERR(stm32port->tx_ch)) stm32port->tx_ch = NULL ; ret = stm32_usart_init_port(stm32port, pdev); if (ret) goto err_dma_tx; if (stm32port->wakeup_src) { device_set_wakeup_capable(&pdev->dev, true ); ret = dev_pm_set_wake_irq(&pdev->dev, stm32port->port.irq); if (ret) goto err_deinit_port; } if (stm32port->rx_ch && stm32_usart_of_dma_rx_probe(stm32port, pdev)) { dma_release_channel(stm32port->rx_ch); stm32port->rx_ch = NULL ; } if (stm32port->tx_ch && stm32_usart_of_dma_tx_probe(stm32port, pdev)) { dma_release_channel(stm32port->tx_ch); stm32port->tx_ch = NULL ; } if (!stm32port->rx_ch) dev_info(&pdev->dev, "interrupt mode for rx (no dma)\n" ); if (!stm32port->tx_ch) dev_info(&pdev->dev, "interrupt mode for tx (no dma)\n" ); platform_set_drvdata(pdev, &stm32port->port); pm_runtime_get_noresume(&pdev->dev); pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); ret = uart_add_one_port(&stm32_usart_driver, &stm32port->port); if (ret) goto err_port; pm_runtime_put_sync(&pdev->dev); return 0 ; err_port: pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); pm_runtime_put_noidle(&pdev->dev); if (stm32port->tx_ch) stm32_usart_of_dma_tx_remove(stm32port, pdev); if (stm32port->rx_ch) stm32_usart_of_dma_rx_remove(stm32port, pdev); if (stm32port->wakeup_src) dev_pm_clear_wake_irq(&pdev->dev); err_deinit_port: if (stm32port->wakeup_src) device_set_wakeup_capable(&pdev->dev, false ); stm32_usart_deinit_port(stm32port); err_dma_tx: if (stm32port->tx_ch) dma_release_channel(stm32port->tx_ch); err_dma_rx: if (stm32port->rx_ch) dma_release_channel(stm32port->rx_ch); return ret; } static void stm32_usart_serial_remove (struct platform_device *pdev) { struct uart_port *port = platform_get_drvdata(pdev); struct stm32_port *stm32_port = to_stm32_port(port); const struct stm32_usart_offsets *ofs = &stm32_port->info->ofs; u32 cr3; pm_runtime_get_sync(&pdev->dev); uart_remove_one_port(&stm32_usart_driver, port); pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); pm_runtime_put_noidle(&pdev->dev); stm32_usart_clr_bits(port, ofs->cr1, USART_CR1_PEIE); if (stm32_port->tx_ch) { stm32_usart_of_dma_tx_remove(stm32_port, pdev); dma_release_channel(stm32_port->tx_ch); } if (stm32_port->rx_ch) { stm32_usart_of_dma_rx_remove(stm32_port, pdev); dma_release_channel(stm32_port->rx_ch); } cr3 = readl_relaxed(port->membase + ofs->cr3); cr3 &= ~USART_CR3_EIE; cr3 &= ~USART_CR3_DMAR; cr3 &= ~USART_CR3_DMAT; cr3 &= ~USART_CR3_DDRE; writel_relaxed(cr3, port->membase + ofs->cr3); if (stm32_port->wakeup_src) { dev_pm_clear_wake_irq(&pdev->dev); device_init_wakeup(&pdev->dev, false ); } stm32_usart_deinit_port(stm32_port); }
uart_ops 与 CONFIG_CONSOLE_POLL: UART操作集与轮询模式调试本代码片段展示了Linux串口驱动的核心 ——uart_ops操作函数集,并特别突出了CONFIG_CONSOLE_POLL选项所启用的轮询模式(Polling Mode)回调函数。stm32_uart_ops结构体将通用的UART核心层API(如startup, shutdown, set_termios等)与STM32 USART硬件的具体实现函数连接起来。CONFIG_CONSOLE_POLL部分则为系统提供了一种在极端情况下(如内核调试器KGDB介入或系统崩溃panic时)进行串口通信的 备用通道 ,此时正常的、基于中断的TTY子系统已完全失效。
实现原理分析
操作函数集 (uart_ops) :
struct uart_ops是Linux串行核心层(Serial Core)定义的标准接口。它是一个包含大量函数指针的结构体。
任何一个串口驱动,都必须实现这些函数指针所指向的函数,并将它们填充到一个uart_ops实例中。
当serial_core需要对一个端口执行通用操作时(例如,当用户程序调用tcsetattr来改变波特率时),它不会直接访问硬件,而是会通过uart_port找到其.ops成员,并调用其中对应的函数指针(如.set_termios)。
这种设计将所有硬件相关的代码隔离 在具体的驱动文件中,使得serial_core可以保持硬件无关,从而能够管理任何符合此接口的串口设备。
轮询模式 (CONFIG_CONSOLE_POLL) :
问题 : 在正常的内核运行中,串口I/O是中断驱动 的,这非常高效。但当内核调试器(KGDB)激活或内核发生panic时,整个系统的调度器、中断处理(部分)和所有高层抽象都已停止工作。此时,不可能再依赖中断来接收或发送数据。
解决方案 : 内核提供了一种“轮询模式”作为回退。在这种模式下,内核会直接、同步地、反复地查询 (poll)硬件状态寄存器来收发数据,完全绕过中断和TTY的复杂分层。
接口 : uart_ops结构体为此预留了三个函数指针:
.poll_init: 在进入轮询模式前调用,用于执行任何必要的硬件初始化(如确保时钟开启)。
.poll_get_char: 非阻塞地 检查硬件是否收到了一个字符。如果收到了,就返回该字符;如果没有,必须立即返回 一个特殊值NO_POLL_CHAR。
.poll_put_char: 阻塞地 发送一个字符。它必须等待,直到硬件发送器准备好接收新字符为止,然后再将字符写入硬件。
特定场景分析:单核、无MMU的STM32H750平台 硬件交互
poll_init : clk_prepare_enable(stm32_port->clk)。在进入调试模式前,内核会调用此函数,确保USART外设的时钟(通常来自RCC)是开启的,否则后续的寄存器读写将全部失效。
poll_get_char :
readl_relaxed(port->membase + ofs->isr) & USART_SR_RXNE: 直接读取STM32 USART的ISR(中断和状态寄存器),并检查RXNE(Read Data Register Not Empty)位。RXNE=1表示硬件已经成功接收一个字符并将其放入了RDR中。
return NO_POLL_CHAR: 如果RXNE=0,立即返回,告知调用者(如KGDB)当前没有输入。
readl_relaxed(port->membase + ofs->rdr) & stm32_port->rdr_mask: 如果RXNE=1,则从RDR(接收数据寄存器)中读取数据。rdr_mask用于屏蔽掉可能存在的额外状态位,只返回纯粹的数据。
poll_put_char :
它直接复用了stm32_usart_console_putchar函数。这个函数内部会执行一个while循环(通过readl_relaxed_poll_timeout_atomic宏),不断检查ISR寄存器的TXE(Transmit Data Register Empty)位,直到TXE=1。然后,它才将字符写入TDR(发送数据寄存器)。这种**“忙等待”**(busy-waiting)的阻塞行为,正是轮询模式所需要的。
单核与无MMU影响
适用性 : 轮询模式的“忙等待”和直接硬件访问的特性,非常契合单核嵌入式系统的调试场景。在这种场景下,CPU已经停止了所有其他工作,专门用于调试,因此独占CPU进行轮询是完全合理的。
MMU : 所有轮询操作都是通过port->membase对物理地址进行直接的内存映射I/O。这与MMU的有无完全无关,是嵌入式驱动与硬件交互最直接的方式。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 static const char *stm32_usart_type (struct uart_port *port) { return (port->type == PORT_STM32) ? DRIVER_NAME : NULL ; } static void stm32_usart_release_port (struct uart_port *port) { } static int stm32_usart_request_port (struct uart_port *port) { return 0 ; } static void stm32_usart_config_port (struct uart_port *port, int flags) { if (flags & UART_CONFIG_TYPE) port->type = PORT_STM32; } static int stm32_usart_verify_port (struct uart_port *port, struct serial_struct *ser) { return -EINVAL; } static void stm32_usart_pm (struct uart_port *port, unsigned int state, unsigned int oldstate) { struct stm32_port *stm32port = container_of(port, struct stm32_port, port); const struct stm32_usart_offsets *ofs = &stm32port->info->ofs; const struct stm32_usart_config *cfg = &stm32port->info->cfg; unsigned long flags; switch (state) { case UART_PM_STATE_ON: pm_runtime_get_sync(port->dev); break ; case UART_PM_STATE_OFF: uart_port_lock_irqsave(port, &flags); stm32_usart_clr_bits(port, ofs->cr1, BIT(cfg->uart_enable_bit)); uart_port_unlock_irqrestore(port, flags); pm_runtime_put_sync(port->dev); break ; } } #if defined(CONFIG_CONSOLE_POLL) static int stm32_usart_poll_init (struct uart_port *port) { struct stm32_port *stm32_port = to_stm32_port(port); return clk_prepare_enable(stm32_port->clk); } static int stm32_usart_poll_get_char (struct uart_port *port) { struct stm32_port *stm32_port = to_stm32_port(port); const struct stm32_usart_offsets *ofs = &stm32_port->info->ofs; if (!(readl_relaxed(port->membase + ofs->isr) & USART_SR_RXNE)) return NO_POLL_CHAR; return readl_relaxed(port->membase + ofs->rdr) & stm32_port->rdr_mask; } static void stm32_usart_poll_put_char (struct uart_port *port, unsigned char ch) { stm32_usart_console_putchar(port, ch); } #endif static const struct uart_ops stm32_uart_ops = { .tx_empty = stm32_usart_tx_empty, .set_mctrl = stm32_usart_set_mctrl, .get_mctrl = stm32_usart_get_mctrl, .stop_tx = stm32_usart_stop_tx, .start_tx = stm32_usart_start_tx, .throttle = stm32_usart_throttle, .unthrottle = stm32_usart_unthrottle, .stop_rx = stm32_usart_stop_rx, .enable_ms = stm32_usart_enable_ms, .break_ctl = stm32_usart_break_ctl, .startup = stm32_usart_startup, .shutdown = stm32_usart_shutdown, .flush_buffer = stm32_usart_flush_buffer, .set_termios = stm32_usart_set_termios, .pm = stm32_usart_pm, .type = stm32_usart_type, .release_port = stm32_usart_release_port, .request_port = stm32_usart_request_port, .config_port = stm32_usart_config_port, .verify_port = stm32_usart_verify_port, #if defined(CONFIG_CONSOLE_POLL) .poll_init = stm32_usart_poll_init, .poll_get_char = stm32_usart_poll_get_char, .poll_put_char = stm32_usart_poll_put_char, #endif };
stm32_usart_set_termios: TTY终端设置到STM32硬件寄存器的转换本代码片段是STM32串口驱动的核心 。stm32_usart_set_termios函数是连接上层通用TTY子系统与底层STM32 USART硬件之间的关键桥梁 。其核心功能是接收一个由TTY核心层传递下来的、抽象的ktermios结构体,将其中的配置(如波特率、数据位、校验、流控等)翻译 成对STM32 USART外设的一系列具体的、精确的寄存器写操作。这是一个“一站式”的硬件重配置函数,几乎所有的串口通信参数都在这里被设定。
实现原理分析 该函数的实现原理是一个精心设计的、顺序严格的硬件配置序列。它必须在保证通信稳定的前提下,原子地更新所有相关参数。
安全第一:停止与同步 :
uart_port_lock_irqsave: 在进行任何硬件修改之前,函数会获取端口的自旋锁并禁用本地中断。这确保了整个多步骤的重配置过程是原子的 ,不会被任何中断(如RX/TX中断)打断,从而避免了硬件进入不一致的状态。
readl_relaxed_poll_timeout_atomic: 它会等待,直到ISR寄存器中的TC(Transmission Complete)位置位,确保上一个数据帧已完全发送出去。
writel_relaxed(0, port->membase + ofs->cr1): 这是最关键的安全步骤 。它通过向CR1寄存器写入0,完全禁用 整个USART外设。这就像在对引擎进行大修之前先将其熄火,防止在配置过程中产生任何意外的收发活动。
配置构建(软件层面) :
函数并不会立即写入寄存器,而是先在几个临时变量(cr1, cr2, cr3)中“构建”出将要写入CR1, CR2, CR3寄存器的最终值。
数据格式 : 它根据termios的c_cflag来设置停止位(CSTOPB -> CR2_STOP_2B)、数据位和校验位。STM32的数据位长度由CR1的M0和M1位共同决定,代码中有一段逻辑根据最终的“字长”(数据位+校验位)来正确配置M0/M1。
流控 : 根据CRTSCTS标志设置CR3寄存器中的CTSE和RTSE位,以使能硬件RTS/CTS流控。
中断与状态掩码 : 根据termios的c_iflag(输入标志),配置port->read_status_mask和port->ignore_status_mask。这些掩码会在中断处理程序中使用,用于决定哪些硬件错误(如奇偶校验错误PE、帧错误FE)应该被报告给TTY层,哪些应该被忽略。
核心计算:波特率设置 :
这是最复杂的计算 。STM32H7的波特率由 Baud = f_ck / (PRESC * BRR) 决定,其中f_ck是USART的内核时钟,PRESC是预分频器值,BRR是波特率寄存器值。
为了在宽广的时钟和波特率范围内找到最佳配置,代码采用了一个迭代循环 :
for (presc = 0; ...): 循环遍历所有可用的预分频器值(stm32_usart_presc_val数组)。
对于每个预分频值,它计算出所需的usartdiv (BRR的理论值)。
过采样 : 它会根据usartdiv的大小,动态决定使用16倍过采样(更稳定)还是8倍过采样(可达更高波特率),并设置CR1的OVER8位。
BRR计算 : usartdiv被分解为整数部分(mantissa)和小数部分(fraction),并组合成最终的brr寄存器值。
合法性检查 : FIELD_FIT(USART_BRR_MASK, brr)检查计算出的brr值是否在硬件支持的范围内。一旦找到一个合法的presc和brr组合,循环就会中断。
高级功能集成 :
函数在构建cr1, cr2, cr3的过程中,会“层叠”上DMA、RS-485和低功耗唤醒等高级功能的配置位。例如,如果DMA被启用,CR3_DMAR和CR3_DMAT位就会被置位。
最终提交与重启 :
在所有寄存器的值都在软件中计算完毕后,函数通过一系列writel_relaxed调用,将presc, brr, cr3, cr2, cr1一次性写入硬件。
stm32_usart_set_bits(port, ofs->cr1, BIT(cfg->uart_enable_bit)): 最后一步,重新置位CR1中的UE(USART Enable)位,重新启动 USART外设,使其以全新的配置开始工作。
uart_port_unlock_irqrestore: 释放锁并恢复中断状态。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 static const unsigned int stm32_usart_presc_val[] = {1 , 2 , 4 , 6 , 8 , 10 , 12 , 16 , 32 , 64 , 128 , 256 };static void stm32_usart_set_termios (struct uart_port *port, struct ktermios *termios, const struct ktermios *old) { struct stm32_port *stm32_port = to_stm32_port(port); const struct stm32_usart_offsets *ofs = &stm32_port->info->ofs; const struct stm32_usart_config *cfg = &stm32_port->info->cfg; struct serial_rs485 *rs485conf = &port->rs485; unsigned int baud, bits, uart_clk, uart_clk_pres; u32 usartdiv, mantissa, fraction, oversampling; tcflag_t cflag = termios->c_cflag; u32 cr1, cr2, cr3, isr, brr, presc; unsigned long flags; int ret; if (!stm32_port->hw_flow_control) cflag &= ~CRTSCTS; uart_clk = clk_get_rate(stm32_port->clk); baud = uart_get_baud_rate(port, termios, old, 0 , uart_clk / 8 ); uart_port_lock_irqsave(port, &flags); ret = readl_relaxed_poll_timeout_atomic(port->membase + ofs->isr, isr, (isr & USART_SR_TC), 10 , 100000 ); if (ret) dev_err(port->dev, "传输未完成\n" ); writel_relaxed(0 , port->membase + ofs->cr1); if (ofs->rqr != UNDEF_REG) writel_relaxed(USART_RQR_TXFRQ | USART_RQR_RXFRQ, port->membase + ofs->rqr); cr1 = USART_CR1_TE | USART_CR1_RE; if (stm32_port->fifoen) cr1 |= USART_CR1_FIFOEN; cr2 = stm32_port->swap ? USART_CR2_SWAP : 0 ; cr3 = readl_relaxed(port->membase + ofs->cr3); cr3 &= USART_CR3_TXFTIE | USART_CR3_RXFTIE; if (stm32_port->fifoen) { if (stm32_port->txftcfg >= 0 ) cr3 |= stm32_port->txftcfg << USART_CR3_TXFTCFG_SHIFT; if (stm32_port->rxftcfg >= 0 ) cr3 |= stm32_port->rxftcfg << USART_CR3_RXFTCFG_SHIFT; } if (cflag & CSTOPB) cr2 |= USART_CR2_STOP_2B; bits = tty_get_char_size(cflag); stm32_port->rdr_mask = (BIT(bits) - 1 ); if (cflag & PARENB) { bits++; cr1 |= USART_CR1_PCE; } if (bits == 9 ) { cr1 |= USART_CR1_M0; } else if ((bits == 7 ) && cfg->has_7bits_data) { cr1 |= USART_CR1_M1; } else if (bits != 8 ) { dev_dbg(port->dev, "不支持的数据位配置: %u bits\n" , bits); cflag &= ~CSIZE; cflag |= CS8; termios->c_cflag = cflag; bits = 8 ; if (cflag & PARENB) { bits++; cr1 |= USART_CR1_M0; } } if (ofs->rtor != UNDEF_REG && (stm32_port->rx_ch || (stm32_port->fifoen && stm32_port->rxftcfg >= 0 ))) { if (cflag & CSTOPB) bits = bits + 3 ; else bits = bits + 2 ; stm32_port->cr1_irq = USART_CR1_RTOIE; writel_relaxed(bits, port->membase + ofs->rtor); cr2 |= USART_CR2_RTOEN; stm32_port->cr3_irq = USART_CR3_RXFTIE; } cr1 |= stm32_port->cr1_irq; cr3 |= stm32_port->cr3_irq; if (cflag & PARODD) cr1 |= USART_CR1_PS; port->status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS); if (cflag & CRTSCTS) { port->status |= UPSTAT_AUTOCTS | UPSTAT_AUTORTS; cr3 |= USART_CR3_CTSE | USART_CR3_RTSE; } for (presc = 0 ; presc <= USART_PRESC_MAX; presc++) { uart_clk_pres = DIV_ROUND_CLOSEST(uart_clk, stm32_usart_presc_val[presc]); usartdiv = DIV_ROUND_CLOSEST(uart_clk_pres, baud); if (usartdiv < 16 ) { oversampling = 8 ; cr1 |= USART_CR1_OVER8; stm32_usart_set_bits(port, ofs->cr1, USART_CR1_OVER8); } else { oversampling = 16 ; cr1 &= ~USART_CR1_OVER8; stm32_usart_clr_bits(port, ofs->cr1, USART_CR1_OVER8); } mantissa = (usartdiv / oversampling) << USART_BRR_DIV_M_SHIFT; fraction = usartdiv % oversampling; brr = mantissa | fraction; if (FIELD_FIT(USART_BRR_MASK, brr)) { if (ofs->presc != UNDEF_REG) { port->uartclk = uart_clk_pres; writel_relaxed(presc, port->membase + ofs->presc); } else if (presc) { dev_err(port->dev, "无法设置波特率,输入时钟过高\n" ); } break ; } else if (presc == USART_PRESC_MAX) { dev_err(port->dev, "无法设置波特率,输入时钟过高\n" ); break ; } } writel_relaxed(brr, port->membase + ofs->brr); uart_update_timeout(port, cflag, baud); port->read_status_mask = USART_SR_ORE; if (termios->c_iflag & INPCK) port->read_status_mask |= USART_SR_PE | USART_SR_FE; if (termios->c_iflag & (IGNBRK | BRKINT | PARMRK)) port->read_status_mask |= USART_SR_FE; port->ignore_status_mask = 0 ; if (termios->c_iflag & IGNPAR) port->ignore_status_mask = USART_SR_PE | USART_SR_FE; if (termios->c_iflag & IGNBRK) { port->ignore_status_mask |= USART_SR_FE; if (termios->c_iflag & IGNPAR) port->ignore_status_mask |= USART_SR_ORE; } if ((termios->c_cflag & CREAD) == 0 ) port->ignore_status_mask |= USART_SR_DUMMY_RX; if (stm32_port->rx_ch) { cr1 |= USART_CR1_PEIE; cr3 |= USART_CR3_EIE; cr3 |= USART_CR3_DMAR; cr3 |= USART_CR3_DDRE; } if (stm32_port->tx_ch) cr3 |= USART_CR3_DMAT; if (rs485conf->flags & SER_RS485_ENABLED) { stm32_usart_config_reg_rs485(&cr1, &cr3, rs485conf->delay_rts_before_send, rs485conf->delay_rts_after_send, baud); if (rs485conf->flags & SER_RS485_RTS_ON_SEND) { cr3 &= ~USART_CR3_DEP; rs485conf->flags &= ~SER_RS485_RTS_AFTER_SEND; } else { cr3 |= USART_CR3_DEP; rs485conf->flags |= SER_RS485_RTS_AFTER_SEND; } } else { cr3 &= ~(USART_CR3_DEM | USART_CR3_DEP); cr1 &= ~(USART_CR1_DEDT_MASK | USART_CR1_DEAT_MASK); } if (stm32_port->wakeup_src) { cr3 &= ~USART_CR3_WUS_MASK; cr3 |= USART_CR3_WUS_START_BIT; } writel_relaxed(cr3, port->membase + ofs->cr3); writel_relaxed(cr2, port->membase + ofs->cr2); writel_relaxed(cr1, port->membase + ofs->cr1); stm32_usart_set_bits(port, ofs->cr1, BIT(cfg->uart_enable_bit)); uart_port_unlock_irqrestore(port, flags); if (UART_ENABLE_MS(port, termios->c_cflag)) stm32_usart_enable_ms(port); else stm32_usart_disable_ms(port); }
stm32_usart_config_rs485: STM32 USART RS-485硬件模式配置本代码片段展示了STM32串口驱动中**.rs485_config回调函数的完整实现——stm32_usart_config_rs485,以及与之配套的辅助函数。其核心功能是将来自serial_core的、抽象的serial_rs485配置结构,翻译 成对STM32H7系列USART硬件中 专用的RS-485模式寄存器**的精确配置。这包括使能DE(Driver Enable)模式、设置DE信号的极性,以及最关键的——计算并设置DE信号的断言(Assertion)和取消断言(De-assertion)时间 。
实现原理分析 此机制充分利用了STM32H7 USART外设内建的硬件RS-485支持 ,将复杂的、对时序要求严格的发送使能(DE/RTS)信号控制,完全下放给硬件 来自动完成,从而极大地解放了CPU。
硬件DE模式 :
RS-485是一种半双工总线,收发器需要一个“方向”信号(通常称为DE或RTS)来控制是发送还是接收。在发送数据前,必须拉高此信号;发送完毕后,必须立即拉低此信号以释放总线,让其他设备可以通信。
STM32H7的USART集成了“DE模式”(Driver Enable Mode)。当CR3寄存器的DEM位置位时,硬件会自动使用一个指定的引脚(DE引脚)来控制收发器。
核心计算 (stm32_usart_config_reg_rs485) :
职责 : 将用户设置的、以毫秒 为单位的delay_rts_before_send(DE断言时间)和delay_rts_after_send(DE取消断言时间),转换为硬件寄存器CR1中DEAT和DEDT位域所需的时钟周期数 。
公式 :
时间(秒) = 周期数 / (波特率 * 过采样率)
周期数 = 时间(秒) * 波特率 * 过采样率
代码实现 : rs485_deat_dedt = delay_ADE * baud * (over8 ? 8 : 16); 这里delay_ADE是以毫秒为单位,所以后面除以1000。rs485_deat_dedt计算出的就是一个粗略的周期数。
钳位 : 计算出的值会被rs485_deat_dedt_max(硬件寄存器位域的最大值,通常是31)进行钳位,防止溢出。
写入 : 最终,计算出的周期数被左移到DEAT和DEDT在CR1寄存器中的正确位置,并合并到cr1变量中。
主配置函数 (stm32_usart_config_rs485) :
安全配置 : stm32_usart_clr_bits(port, ofs->cr1, BIT(cfg->uart_enable_bit)); 在修改任何RS-485相关配置前,先禁用整个USART外设 ,这是确保配置更改能被正确应用的关键步骤。
读取当前状态 : 如果是使能RS-485,它会先读取当前的CR1, CR3, BRR寄存器。这是因为DEAT/DEDT的计算依赖于当前的波特率和过采样模式。
反向计算波特率 : 它从BRR和PRESC寄存器中反向推算出 当前配置的波特率baud,作为DEAT/DEDT计算的输入。
应用配置 :
调用stm32_usart_config_reg_rs485来计算并更新cr1(包含DEAT/DEDT)和cr3(置位DEM)。
根据rs485conf->flags中的RTS_ON_SEND标志,设置CR3寄存器中的DEP(Driver Enable Polarity)位,以决定DE信号是高电平有效还是低电平有效。
将修改后的cr1和cr3写回硬件。
软件RTS控制 (回退) :
最后两行代码stm32_usart_rs485_rts_disable/enable是为那些不使用硬件DE模式,而是通过软件手动翻转RTS引脚 的场景准备的。stm32_usart_rs485_rts_enable/disable会根据RTS_ON_SEND的极性,使用mctrl_gpio_set来直接控制与RTS功能关联的GPIO。这段代码确保了即使在切换到硬件RS-485模式后,软件控制的RTS引脚也能处于一个正确的初始状态。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 static void stm32_usart_rs485_rts_enable (struct uart_port *port) { if (rs485conf->flags & SER_RS485_RTS_ON_SEND) { mctrl_gpio_set(stm32_port->gpios, stm32_port->port.mctrl | TIOCM_RTS); } else { mctrl_gpio_set(stm32_port->gpios, stm32_port->port.mctrl & ~TIOCM_RTS); } } static void stm32_usart_rs485_rts_disable (struct uart_port *port) { if (rs485conf->flags & SER_RS485_RTS_ON_SEND) { mctrl_gpio_set(stm32_port->gpios, stm32_port->port.mctrl & ~TIOCM_RTS); } else { mctrl_gpio_set(stm32_port->gpios, stm32_port->port.mctrl | TIOCM_RTS); } } static void stm32_usart_config_reg_rs485 (u32 *cr1, u32 *cr3, u32 delay_ADE, u32 delay_DDE, u32 baud) { *cr3 |= USART_CR3_DEM; *cr1 &= ~(USART_CR1_DEDT_MASK | USART_CR1_DEAT_MASK); if (over8) rs485_deat_dedt = delay_ADE * baud * 8 ; else rs485_deat_dedt = delay_ADE * baud * 16 ; rs485_deat_dedt = DIV_ROUND_CLOSEST(rs485_deat_dedt, 1000 ); rs485_deat_dedt = rs485_deat_dedt > rs485_deat_dedt_max ? rs485_deat_dedt_max : rs485_deat_dedt; rs485_deat_dedt = (rs485_deat_dedt << USART_CR1_DEAT_SHIFT) & USART_CR1_DEAT_MASK; *cr1 |= rs485_deat_dedt; *cr1 |= rs485_deat_dedt; } static int stm32_usart_config_rs485 (struct uart_port *port, struct ktermios *termios, struct serial_rs485 *rs485conf) { struct stm32_port *stm32_port = to_stm32_port(port); const struct stm32_usart_offsets *ofs = &stm32_port->info->ofs; const struct stm32_usart_config *cfg = &stm32_port->info->cfg; u32 usartdiv, baud, cr1, cr3; bool over8; stm32_usart_clr_bits(port, ofs->cr1, BIT(cfg->uart_enable_bit)); if (rs485conf->flags & SER_RS485_ENABLED) { cr1 = readl_relaxed(port->membase + ofs->cr1); cr3 = readl_relaxed(port->membase + ofs->cr3); usartdiv = readl_relaxed(port->membase + ofs->brr); usartdiv = usartdiv & GENMASK(15 , 0 ); over8 = cr1 & USART_CR1_OVER8; stm32_usart_config_reg_rs485(&cr1, &cr3, rs485conf->delay_rts_before_send, rs485conf->delay_rts_after_send, baud); if (rs485conf->flags & SER_RS485_RTS_ON_SEND) cr3 &= ~USART_CR3_DEP; else cr3 |= USART_CR3_DEP; writel_relaxed(cr3, port->membase + ofs->cr3); writel_relaxed(cr1, port->membase + ofs->cr1); if (!port->rs485_rx_during_tx_gpio) rs485conf->flags |= SER_RS485_RX_DURING_TX; } else { stm32_usart_clr_bits(port, ofs->cr3, USART_CR3_DEM | USART_CR3_DEP); stm32_usart_clr_bits(port, ofs->cr1, USART_CR1_DEDT_MASK | USART_CR1_DEAT_MASK); } stm32_usart_set_bits(port, ofs->cr1, BIT(cfg->uart_enable_bit)); if (stm32_usart_tx_empty(port)) stm32_usart_rs485_rts_disable(port); else stm32_usart_rs485_rts_enable(port); return 0 ; } static int stm32_usart_init_rs485 (struct uart_port *port, struct platform_device *pdev) { struct serial_rs485 *rs485conf = &port->rs485; rs485conf->flags = 0 ; rs485conf->delay_rts_before_send = 0 ; rs485conf->delay_rts_after_send = 0 ; if (!pdev->dev.of_node) return -ENODEV; return uart_get_rs485_mode(port); }
stm32_usart_startup & stm32_usart_shutdown: UART端口的打开与关闭本代码片段展示了STM32串口驱动中两个至关重要的生命周期回调函数 :startup和shutdown。这两个函数是uart_ops操作集的一部分,分别在用户空间第一次打开 (open())串口设备和最后一次关闭 (close())它时,由通用的uart_core层调用。它们的核心功能是管理硬件资源 ,特别是中断请求和DMA操作,确保只有在端口被实际使用时,硬件才处于活动状态。
实现原理分析 这两个函数是典型的资源获取与释放(Resource Acquisition Is Initialization, RAII)模式的体现。startup获取资源并启动硬件,而shutdown则以严格相反的顺序停止硬件并释放所有资源。
启动流程 (stm32_usart_startup) :
职责 : 从一个“冷”状态(仅上电,未配置)将串口硬件完全激活,使其准备好进行数据收发。
申请中断 (request_irq) : 这是最关键的第一步。它向内核中断子系统注册 一个中断处理函数(stm32_usart_interrupt)。从此以后,当STM32 USART硬件产生中断时,CPU会跳转到这个函数执行。
硬件配置 :
USART_CR2_SWAP: 如果设备树中配置了RX/TX引脚交换,则设置相应的寄存器位。
USART_RQR_RXFRQ: 刷新接收FIFO,清除可能残留的旧数据。
启动DMA : 如果配置了RX DMA(stm32_port->rx_ch存在),则调用stm32_usart_rx_dma_start_or_resume来配置并启动DMA接收传输。
使能接收 : stm32_usart_set_bits(port, ofs->cr1, ...)。这一步通过向CR1寄存器写入相应的位,原子地完成三件事:
使能接收器(USART_CR1_RE)。
使能接收相关的中断(stm32_port->cr1_irq,例如RXNEIE或RTOIE)。
使能整个USART外设(BIT(cfg->uart_enable_bit),即UE位)。
关闭流程 (stm32_usart_shutdown) :
职责 : 安全地停止所有硬件活动,释放中断,使端口回到“冷”状态,为下一次startup或系统挂起做准备。
停止DMA :
stm32_usart_tx_dma_terminate: 如果TX DMA正在进行,则终止它。
stm32_usart_rx_dma_terminate: 终止RX DMA。dmaengine_synchronize会阻塞,直到所有挂起的DMA传输都已完成或被取消,这是一个重要的同步步骤。
等待发送完成 (readl_relaxed_poll_timeout) : 在完全关闭硬件之前,它会轮询等待 ISR寄存器的TC(Transmission Complete)位。这确保了所有在关闭前已提交到发送缓冲区的数据都能被完整地发送出去,防止数据丢失。
禁用硬件 : stm32_usart_clr_bits(port, ofs->cr1, val)。它构建一个包含所有活动使能位(TE, RE, UE, 所有中断使能位等)的掩码val,然后通过一次写操作将它们全部清零 ,彻底禁用整个USART外设。
释放中断 (free_irq) : 这是最后一步,向内核注销中断处理函数。这是request_irq的逆操作。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 static int stm32_usart_startup (struct uart_port *port) { struct stm32_port *stm32_port = to_stm32_port(port); const struct stm32_usart_offsets *ofs = &stm32_port->info->ofs; int ret; ret = request_irq(port->irq, stm32_usart_interrupt, IRQF_NO_SUSPEND, name, port); if (ret) return ret; if (stm32_port->swap) { val = readl_relaxed(port->membase + ofs->cr2); val |= USART_CR2_SWAP; writel_relaxed(val, port->membase + ofs->cr2); } stm32_port->throttled = false ; if (ofs->rqr != UNDEF_REG) writel_relaxed(USART_RQR_RXFRQ, port->membase + ofs->rqr); if (stm32_port->rx_ch) { ret = stm32_usart_rx_dma_start_or_resume(port); if (ret) { free_irq(port->irq, port); return ret; } } val = stm32_port->cr1_irq | USART_CR1_RE | BIT(cfg->uart_enable_bit); stm32_usart_set_bits(port, ofs->cr1, val); return 0 ; } static void stm32_usart_shutdown (struct uart_port *port) { struct stm32_port *stm32_port = to_stm32_port(port); if (stm32_usart_tx_dma_started(stm32_port)) stm32_usart_tx_dma_terminate(stm32_port); if (stm32_port->tx_ch) stm32_usart_clr_bits(port, ofs->cr3, USART_CR3_DMAT); stm32_usart_disable_ms(port); val = USART_CR1_TXEIE | USART_CR1_TE; val |= stm32_port->cr1_irq | USART_CR1_RE; val |= BIT(cfg->uart_enable_bit); if (stm32_port->fifoen) val |= USART_CR1_FIFOEN; ret = readl_relaxed_poll_timeout(port->membase + ofs->isr, isr, (isr & USART_SR_TC), 10 , 100000 ); if (stm32_port->rx_ch) { stm32_usart_rx_dma_terminate(stm32_port); dmaengine_synchronize(stm32_port->rx_ch); } if (ofs->rqr != UNDEF_REG) writel_relaxed(USART_RQR_TXFRQ | USART_RQR_RXFRQ, port->membase + ofs->rqr); stm32_usart_clr_bits(port, ofs->cr1, val); free_irq(port->irq, port); }
dma调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 static int stm32_usart_dma_pause_resume (struct stm32_port *stm32_port, struct dma_chan *chan, enum dma_status expected_status, int dmaengine_pause_or_resume(struct dma_chan *), bool stm32_usart_xx_dma_started(struct stm32_port *), void stm32_usart_xx_dma_terminate(struct stm32_port *)) { struct uart_port *port = &stm32_port->port; enum dma_status dma_status ; int ret; if (!stm32_usart_xx_dma_started(stm32_port)) return -EPERM; dma_status = dmaengine_tx_status(chan, chan->cookie, NULL ); if (dma_status != expected_status) return -EAGAIN; ret = dmaengine_pause_or_resume(chan); if (ret) { dev_err(port->dev, "DMA failed with error code: %d\n" , ret); stm32_usart_xx_dma_terminate(stm32_port); } return ret; }
stm32_usart_rx_dma_start_or_resume: USART DMA的循环接收本代码片段展示了STM32串口驱动中利用DMA引擎(DMA Engine)框架 进行数据收发的核心逻辑。stm32_usart_rx_dma_start_or_resume函数是其关键,它配置并启动了一个DMA循环(cyclic)传输 来实现高效的数据接收。与之对应的,stm32_usart_tx_dma_complete和相关函数则构成了DMA发送的完成处理和状态管理。这个机制的目标是将CPU从逐字节搬运数据的中断处理中解放出来,由DMA控制器在后台自动完成数据传输,从而极大地提升系统性能和吞吐量。
实现原理分析 此机制是典型的DMA引擎“从设备(slave)”模式的用法,特别是利用了其循环缓冲区 和异步回调 的特性。
DMA循环接收 (stm32_usart_rx_dma_start_or_resume) :
职责 : 启动或恢复一个不间断的DMA接收操作。
循环模式 : 这是最核心的概念。驱动不是为每一个数据包都发起一次新的DMA传输,而是设置一个循环缓冲区 。DMA控制器被编程为:当缓冲区被填满(或达到一半,即一个周期period)时,自动 回到缓冲区的起始(或中间)位置,继续接收数据,永不停止 。
dmaengine_prep_dma_cyclic: 这是配置循环传输的关键API。
stm32_port->rx_dma_buf: DMA使用的总线地址 。
RX_BUF_L: 整个循环缓冲区的总长度。
RX_BUF_P: 周期(period)长度 。当DMA传输了RX_BUF_P这么多字节后,它会触发一次中断/回调。这里RX_BUF_L通常是RX_BUF_P的两倍,构成一个“乒乓缓冲”。
DMA_DEV_TO_MEM: 指定传输方向为“设备到内存”。
异步回调 (desc->callback) : desc->callback = stm32_usart_rx_dma_complete; 这一行设置了一个回调函数。当DMA完成一个周期(传输了RX_BUF_P字节)后,DMA引擎框架会调用stm32_usart_rx_dma_complete。
提交与启动 : dmaengine_submit(desc)将这个配置好的传输描述符提交给DMA驱动的队列,dma_async_issue_pending则命令DMA控制器立即开始执行队列中的任务。
DMA接收完成处理 (stm32_usart_rx_dma_complete) :
职责 : 在DMA完成一个接收周期后,处理已收到的数据。
实现 : 它调用stm32_usart_receive_chars(代码未完全显示,但其作用是检查DMA缓冲区的指针,计算出新接收了多少数据),并将这些数据推送到TTY核心层的flip_buffer。tty_flip_buffer_push会唤醒任何正在read()系统调用上等待的用户空间进程。
由于DMA是循环的,驱动不需要 在这里重新启动DMA,硬件会自动继续填充缓冲区的另一半。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 static bool stm32_usart_rx_dma_started (struct stm32_port *stm32_port) { return stm32_port->rx_ch ? stm32_port->rx_dma_busy : false ; } static void stm32_usart_rx_dma_terminate (struct stm32_port *stm32_port) { dmaengine_terminate_async(stm32_port->rx_ch); stm32_port->rx_dma_busy = false ; } static int stm32_usart_rx_dma_pause (struct stm32_port *stm32_port) { return stm32_usart_dma_pause_resume(stm32_port, stm32_port->rx_ch, DMA_IN_PROGRESS, dmaengine_pause, stm32_usart_rx_dma_started, stm32_usart_rx_dma_terminate); } static int stm32_usart_rx_dma_resume (struct stm32_port *stm32_port) { return stm32_usart_dma_pause_resume(stm32_port, stm32_port->rx_ch, DMA_PAUSED, dmaengine_resume, stm32_usart_rx_dma_started, stm32_usart_rx_dma_terminate); } static void stm32_usart_rx_dma_complete (void *arg) { struct uart_port *port = arg; struct tty_port *tport = &port->state->port; unsigned int size; unsigned long flags; uart_port_lock_irqsave(port, &flags); size = stm32_usart_receive_chars(port, false ); uart_unlock_and_check_sysrq_irqrestore(port, flags); if (size) tty_flip_buffer_push(tport); } static int stm32_usart_rx_dma_start_or_resume (struct uart_port *port) { if (stm32_port->rx_dma_busy) { rx_dma_status = dmaengine_tx_status(stm32_port->rx_ch, stm32_port->rx_ch->cookie, NULL ); if (rx_dma_status == DMA_IN_PROGRESS) return 0 ; if (rx_dma_status == DMA_PAUSED && !stm32_usart_rx_dma_resume(stm32_port)) return 0 ; dev_err(port->dev, "DMA failed : status error.\n" ); stm32_usart_rx_dma_terminate(stm32_port); } stm32_port->rx_dma_busy = true ; desc = dmaengine_prep_dma_cyclic(stm32_port->rx_ch, stm32_port->rx_dma_buf, RX_BUF_L, RX_BUF_P, DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); if (!desc) { dev_err(port->dev, "rx dma prep cyclic failed\n" ); stm32_port->rx_dma_busy = false ; return -ENODEV; } desc->callback = stm32_usart_rx_dma_complete; desc->callback_param = port; ret = dma_submit_error(dmaengine_submit(desc)); if (ret) { dmaengine_terminate_sync(stm32_port->rx_ch); stm32_port->rx_dma_busy = false ; return ret; } dma_async_issue_pending(stm32_port->rx_ch); return 0 ; }
STM32 USART Dual-Mode Reception: PIO与DMA的协同工作 本代码片段是STM32串口驱动的心脏 ,它展示了驱动程序如何从硬件接收数据,并将其递交给上层TTY子系统。其核心是一个精巧的**双模式(Dual-Mode)**设计:
PIO模式 (Programmed I/O) : 通过CPU逐字节地从数据寄存器中读取,这是基础模式。
DMA模式 (Direct Memory Access) : 配置DMA控制器在后台自动将数据从串口外设搬运到内存,这是高效的高性能模式。
stm32_usart_receive_chars函数作为顶层分发器 ,它不仅根据配置选择使用PIO还是DMA,更重要的是,它实现了一种健壮的混合错误处理机制 ,即便在DMA模式下,也能优雅地切换回PIO模式来处理串口错误,然后再无缝地切回DMA模式。
实现原理分析 此机制的原理是分层和状态驱动的。底层函数分别实现纯粹的PIO和DMA数据处理,而顶层函数则根据当前的状态(DMA是否启用、是否出错)来调用底层函数。
PIO模式实现 (stm32_usart_receive_chars_pio) :
职责 : 在中断上下文中,尽可能多地从USART的接收寄存器(或FIFO)中读取所有待处理的字符。
循环与条件 (while (stm32_usart_pending_rx_pio(...))) : stm32_usart_pending_rx_pio函数是循环的守卫。它读取ISR(中断和状态寄存器),并检查RXNE(接收数据寄存器非空)位。只要RXNE为1,就表示至少还有一个字符可以读取。
错误处理 :
它会检查ISR中的错误标志(USART_SR_ERR_MASK),如ORE(溢出)、PE(奇偶校验)、FE(帧错误)。
硬件差异 : if (... ofs->icr != UNDEF_REG) 这段代码处理了不同STM32型号的差异。在STM32H7/F7上,错误标志必须通过向ICR(中断清除寄存器)写入来显式清除;而在旧的F4上,通过“先读SR,后读DR”的序列来自动清除。
数据打包 : 对于每个字符,它会读取字符本身、检查错误标志、更新统计计数器(port->icount),并为该字符设置一个flag(如TTY_NORMAL, TTY_PARITY, TTY_BREAK)。
递交上层 : uart_insert_char函数被调用,将字符、状态和标志一起递交给通用的serial_core层,最终存入TTY的flip_buffer。
DMA模式实现 (stm32_usart_receive_chars_dma & stm32_usart_push_buffer_dma) :
职责 : 处理由DMA控制器填充的循环缓冲区 。
指针数学 : 这是核心算法。DMA控制器有一个内部指针,指示它当前正在向缓冲区的哪个位置写入。驱动程序通过dmaengine_tx_status可以获取到这个指针的位置(体现在residue——剩余传输字节数)。驱动自身也维护一个last_res变量,记录了上次 检查时指针的位置。
计算新数据 : 新接收的数据量就是last_res - current_residue。
处理回绕 (Wrap-around) : if (stm32_port->rx_dma_state.residue > stm32_port->last_res) 这段逻辑处理了循环缓冲区的回绕情况。如果DMA指针已经越过缓冲区的末尾回到了开头,那么新的residue就会比last_res大。此时,函数会分两部分处理:先处理从last_res到缓冲区末尾的数据,再处理从缓冲区开头到新的residue位置的数据。
批量递交 : tty_insert_flip_string函数被用来将一大块新接收到的数据一次性 推送到TTY的flip_buffer,这远比逐字节插入要高效。
顶层分发与混合错误处理 (stm32_usart_receive_chars) :
职责 : 这是在RX DMA完成回调或RX PIO中断中被调用的统一入口。
模式决策 : 它首先检查DMA是否已启动。
DMA路径 :
获取DMA状态,计算并处理_dma函数接收到的数据。
混合错误处理 : 这是最精巧的部分 。在处理完DMA数据后,它再次 检查USART的ISR状态寄存器。如果ISR中出现了错误标志(USART_SR_ERR_MASK),这意味着DMA虽然搬运了数据,但某个字节伴随着一个错误(DMA本身不处理这些错误)。此时,它会:
stm32_usart_clr_bits(port, ofs->cr3, USART_CR3_DMAR): 临时禁用 硬件的DMA接收请求。
size += stm32_usart_receive_chars_pio(port): 切换到PIO模式 来读取这个带有错误状态的字符。PIO模式可以精确地将错误与字符关联起来。
stm32_usart_set_bits(port, ofs->cr3, USART_CR3_DMAR): 处理完错误后,立即重新使能 DMA接收请求,无缝地回到DMA模式。
如果DMA本身报告了错误(例如总线错误),则彻底终止DMA,并完全回退到PIO中断模式。
PIO路径 : 如果DMA未启动,则直接调用stm32_usart_receive_chars_pio。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 static bool stm32_usart_pending_rx_pio (struct uart_port *port, u32 *sr) { struct stm32_port *stm32_port = to_stm32_port(port); const struct stm32_usart_offsets *ofs = &stm32_port->info->ofs; *sr = readl_relaxed(port->membase + ofs->isr); if (*sr & USART_SR_RXNE) { if (!stm32_usart_rx_dma_started(stm32_port)) return true ; if (*sr & USART_SR_ERR_MASK) return true ; } return false ; } static u8 stm32_usart_get_char_pio (struct uart_port *port) { struct stm32_port *stm32_port = to_stm32_port(port); const struct stm32_usart_offsets *ofs = &stm32_port->info->ofs; unsigned long c; c = readl_relaxed(port->membase + ofs->rdr); c &= stm32_port->rdr_mask; return c; } static unsigned int stm32_usart_receive_chars_pio (struct uart_port *port) { while (stm32_usart_pending_rx_pio(port, &sr)) { sr |= USART_SR_DUMMY_RX; flag = TTY_NORMAL; if ((sr & USART_SR_ERR_MASK) && ofs->icr != UNDEF_REG) writel_relaxed(sr & USART_SR_ERR_MASK, port->membase + ofs->icr); c = stm32_usart_get_char_pio(port); port->icount.rx++; size++; if (sr & USART_SR_ERR_MASK) { if (sr & USART_SR_ORE) { port->icount.overrun++; } else if (sr & USART_SR_PE) { port->icount.parity++; } else if (sr & USART_SR_FE) { if (!c) { port->icount.brk++; if (uart_handle_break(port)) continue ; } else { port->icount.frame++; } } sr &= port->read_status_mask; if (sr & USART_SR_PE) { flag = TTY_PARITY; } else if (sr & USART_SR_FE) { if (!c) flag = TTY_BREAK; else flag = TTY_FRAME; } } if (uart_prepare_sysrq_char(port, c)) continue ; uart_insert_char(port, sr, USART_SR_ORE, c, flag); } return size; } static void stm32_usart_push_buffer_dma (struct uart_port *port, unsigned int dma_size) { dma_start = stm32_port->rx_buf + (RX_BUF_L - stm32_port->last_res); if (!(stm32_port->rdr_mask == (BIT(8 ) - 1 ))) for (i = 0 ; i < dma_size; i++) *(dma_start + i) &= stm32_port->rdr_mask; dma_count = tty_insert_flip_string(ttyport, dma_start, dma_size); port->icount.rx += dma_count; if (dma_count != dma_size) port->icount.buf_overrun++; stm32_port->last_res -= dma_count; if (stm32_port->last_res == 0 ) stm32_port->last_res = RX_BUF_L; } static unsigned int stm32_usart_receive_chars_dma (struct uart_port *port) { struct stm32_port *stm32_port = to_stm32_port(port); unsigned int dma_size, size = 0 ; if (stm32_port->rx_dma_state.residue > stm32_port->last_res) { dma_size = stm32_port->last_res; stm32_usart_push_buffer_dma(port, dma_size); size = dma_size; } dma_size = stm32_port->last_res - stm32_port->rx_dma_state.residue; stm32_usart_push_buffer_dma(port, dma_size); size += dma_size; return size; } static unsigned int stm32_usart_receive_chars (struct uart_port *port, bool force_dma_flush) { struct stm32_port *stm32_port = to_stm32_port(port); const struct stm32_usart_offsets *ofs = &stm32_port->info->ofs; enum dma_status rx_dma_status ; u32 sr; unsigned int size = 0 ; if (stm32_usart_rx_dma_started(stm32_port) || force_dma_flush) { rx_dma_status = dmaengine_tx_status(stm32_port->rx_ch, stm32_port->rx_ch->cookie, &stm32_port->rx_dma_state); if (rx_dma_status == DMA_IN_PROGRESS || rx_dma_status == DMA_PAUSED) { size = stm32_usart_receive_chars_dma(port); sr = readl_relaxed(port->membase + ofs->isr); if (sr & USART_SR_ERR_MASK) { stm32_usart_clr_bits(port, ofs->cr3, USART_CR3_DMAR); size += stm32_usart_receive_chars_pio(port); stm32_usart_set_bits(port, ofs->cr3, USART_CR3_DMAR); } } else { stm32_usart_rx_dma_terminate(stm32_port); dev_dbg(port->dev, "DMA错误,回退到中断模式\n" ); size = stm32_usart_receive_chars_pio(port); } } else { size = stm32_usart_receive_chars_pio(port); } return size; }
STM32 USART Dual-Mode Transmission: PIO与DMA的协同发送 本代码片段是STM32串口驱动中数据发送的心脏 。它展示了一个与接收逻辑对称的双模式(Dual-Mode)设计,用于将TTY核心层xmit_fifo缓冲区中的数据发送出去。stm32_usart_transmit_chars作为 顶层分发器 ,根据配置(DMA是否可用)和当前状态(是否有x_char待发送、xmit_fifo是否为空),决定是调用PIO模式 的stm32_usart_transmit_chars_pio,还是DMA模式 的stm32_usart_transmit_chars_dma。这个机制同样具备健壮的**回退(fallback)**能力,并优雅地处理了RS-485和软件流控(x_char)等特殊情况。
实现原理分析 此机制的原理是分层、状态驱动和机会主义的。顶层函数负责决策,底层函数负责执行,并且在DMA模式失败时,能够机会主义地回退到PIO模式,保证数据总能被发送出去。
顶层分发器 (stm32_usart_transmit_chars) :
职责 : 这是start_tx回调或TX中断/DMA完成回调中被调用的统一入口。它的任务是检查所有前置条件,并启动一次发送操作。
特殊情况处理 :
RS-485 : 在发送开始前,它会为软件控制的RS-485手动使能RTS信号。在发送完成后,它会使能TC(Transmission Complete)中断,以便在数据完全移出移位寄存器后,能够精确地禁用RTS。
x_char (软件流控) : x_char用于发送高优先级的XON/XOFF字符。如果port->x_char非零,它会暂停 任何正在进行的DMA传输,轮询等待 TXE,将x_char抢先 插入硬件发送寄存器,然后再恢复DMA。这确保了流控字符的低延迟发送。
模式决策 :
如果xmit_fifo为空或端口被流控停止,则禁用TX中断并返回。
清除TC标志,为下一次“发送完成”检测做准备。
if (stm32_port->tx_ch): 这是核心决策点。如果TX DMA通道存在,则调用DMA发送函数;否则,调用PIO发送函数。
PIO模式发送 (stm32_usart_transmit_chars_pio) :
职责 : 在中断上下文中,尽可能多地从xmit_fifo中取出数据,并填入硬件的发送寄存器(或FIFO)。
循环与条件 : while (...)循环会持续进行,直到以下任一条件满足:
硬件发送寄存器不为空 (!(... & USART_SR_TXE))。这意味着硬件正忙,CPU不能再写入新数据。
xmit_fifo中没有更多数据 (!uart_fifo_get(...))。
中断控制 :
在循环结束后,如果xmit_fifo已空,则禁用 TXE中断(stm32_usart_tx_interrupt_disable)。因为没有数据可发了,再响应TXE中断(“我可以发了”)是没有意义的。
如果xmit_fifo中还有数据,则保持或使能 TXE中断。这样,当硬件发送完当前字符、TXE再次变为1时,会再次触发中断,从而回来继续发送剩余的数据。
DMA模式发送 (stm32_usart_transmit_chars_dma) :
职责 : 从xmit_fifo中取出一大块 数据,启动一次DMA传输来发送它。
状态管理 (tx_dma_busy) : 使用一个软件标志来防止在一次DMA传输完成(即tx_dma_complete回调被调用)之前,重复启动新的DMA传输。
数据准备 : kfifo_out_peek: 从xmit_fifo中窥探(peek)并拷贝数据到DMA缓冲区tx_buf,但 不 从xmit_fifo中移除数据。
DMA配置 : dmaengine_prep_slave_single: 配置一个单次 的、内存到设备的DMA传输。
回调与启动 : 与RX类似,设置tx_dma_complete回调,然后dmaengine_submit和dma_async_issue_pending。
数据移除 : uart_xmit_advance(port, count): 在DMA传输成功启动后 ,才调用此函数,将count个字节真正地 从xmit_fifo中移除。
回退 (fallback_err) : 如果任何DMA步骤(prep或submit)失败,它会立即调用PIO发送函数 stm32_usart_transmit_chars_pio来发送数据,保证了即使DMA子系统出问题,发送功能也不会完全失效。
DMA完成与续传 (stm32_usart_tx_dma_complete) :
职责 : 在一次DMA发送完成后,清理状态并启动下一次发送。
stm32_usart_tx_dma_terminate: 清理DMA状态,将tx_dma_busy设为false。
stm32_usart_transmit_chars(port): 这是关键的续传逻辑 。它重新调用顶层分发器。分发器会检查xmit_fifo,如果还有数据,就会自动启动下一次DMA传输,形成一个发送链 。
代码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 static void stm32_usart_tx_dma_terminate (struct stm32_port *stm32_port) { dmaengine_terminate_async(stm32_port->tx_ch); stm32_port->tx_dma_busy = false ; } static bool stm32_usart_tx_dma_started (struct stm32_port *stm32_port) { return stm32_port->tx_dma_busy; } static void stm32_usart_tx_dma_complete (void *arg) { struct uart_port *port = arg; struct stm32_port *stm32port = to_stm32_port(port); unsigned long flags; stm32_usart_tx_dma_terminate(stm32port); uart_port_lock_irqsave(port, &flags); stm32_usart_transmit_chars(port); uart_port_unlock_irqrestore(port, flags); } static void stm32_usart_transmit_chars_pio (struct uart_port *port) { while (1 ) { unsigned char ch; if (!(readl_relaxed(port->membase + ofs->isr) & USART_SR_TXE)) break ; if (!uart_fifo_get(port, &ch)) break ; writel_relaxed(ch, port->membase + ofs->tdr); } if (kfifo_is_empty(&tport->xmit_fifo)) stm32_usart_tx_interrupt_disable(port); else stm32_usart_tx_interrupt_enable(port); } static void stm32_usart_transmit_chars_dma (struct uart_port *port) { if (stm32_usart_tx_dma_started(stm32port)) { return ; } count = kfifo_out_peek(&tport->xmit_fifo, &stm32port->tx_buf[0 ], TX_BUF_L); desc = dmaengine_prep_slave_single(stm32port->tx_ch, stm32port->tx_dma_buf, count, DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT); if (!desc) goto fallback_err; stm32port->tx_dma_busy = true ; desc->callback = stm32_usart_tx_dma_complete; desc->callback_param = port; ret = dma_submit_error(dmaengine_submit(desc)); if (ret) { goto fallback_err; } dma_async_issue_pending(stm32port->tx_ch); uart_xmit_advance(port, count); return ; fallback_err: stm32_usart_transmit_chars_pio(port); } static void stm32_usart_transmit_chars (struct uart_port *port) { if (!stm32_port->hw_flow_control && port->rs485.flags & SER_RS485_ENABLED && (port->x_char || !(kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port)))) { stm32_usart_tc_interrupt_disable(port); stm32_usart_rs485_rts_enable(port); } if (port->x_char) { return ; } if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port)) { stm32_usart_tx_interrupt_disable(port); return ; } if (stm32_port->tx_ch) stm32_usart_transmit_chars_dma(port); else stm32_usart_transmit_chars_pio(port); }