static void pl011_console_write(struct console *co, const char *s, unsigned int count) { struct uart_amba_port *uap = amba_ports[co->index]; unsigned int old_cr = 0, new_cr; unsigned long flags; int locked = 1; clk_enable(uap->clk); local_irq_save(flags); if (uap->port.sysrq) locked = 0; else if (oops_in_progress) locked = spin_trylock(&uap->port.lock); else spin_lock(&uap->port.lock); /* * First save the CR then disable the interrupts */ if (!uap->vendor->always_enabled) { old_cr = pl011_read(uap, REG_CR); new_cr = old_cr & ~UART011_CR_CTSEN; new_cr |= UART01x_CR_UARTEN | UART011_CR_TXE; pl011_write(new_cr, uap, REG_CR); } uart_console_write(&uap->port, s, count, pl011_console_putchar); /* * Finally, wait for transmitter to become empty and restore the * TCR. Allow feature register bits to be inverted to work around * errata. */ while ((pl011_read(uap, REG_FR) ^ uap->vendor->inv_fr) & uap->vendor->fr_busy) cpu_relax(); if (!uap->vendor->always_enabled) pl011_write(old_cr, uap, REG_CR); if (locked) spin_unlock(&uap->port.lock); local_irq_restore(flags); clk_disable(uap->clk); }
时间: 2025-06-27 10:08:14 浏览: 31
<think>嗯,我现在要分析这段代码,它看起来是关于PL011串口控制台的写入函数。首先,这个函数的名字是pl011_console_write,参数包括一个console结构体指针,字符串s和字符数count。我需要理解每个部分的作用。
首先,函数开始处定义了一些变量:uap指向amba_ports数组中的一个元素,根据co->index获取。然后有old_cr和new_cr用于控制寄存器,flags用于保存中断状态,locked标志锁的状态。接下来,clk_enable(uap->clk)应该是启用时钟,确保UART模块有工作时钟。
然后保存本地中断状态,local_irq_save(flags),防止中断干扰。接下来检查sysrq和oops_in_progress,这里可能是在处理系统请求或内核oops时,决定是否加锁。如果sysrq开启,可能不需要锁;如果oops发生,尝试获取锁,否则直接加锁。这部分可能需要进一步理解spin_trylock和spin_lock的区别,以及它们在中断上下文中的使用。
接下来,如果vendor的always_enabled未设置,保存旧的CR(控制寄存器)值,然后修改CR:禁用CTS,启用UART和发送器。这里用到了pl011_read和pl011_write函数,应该是读写寄存器的操作。
然后调用uart_console_write,这个函数可能是通用的控制台写入函数,传入端口、字符串、长度和putchar函数pl011_console_putchar。这里putchar具体实现可能需要看另一个函数,但当前代码中未给出。
之后,等待发送器空闲,通过检查FR(标志寄存器)中的busy位,可能用到了vendor特定的反转位和busy位定义。循环直到发送器空闲,使用cpu_relax降低CPU占用。
恢复旧的CR值,如果之前修改过的话。然后根据locked变量释放自旋锁,恢复中断状态,最后禁用时钟。
可能的疑问点:为什么在sysrq或oops时处理锁的方式不同?spin_trylock在oops_in_progress时使用,是否因为此时可能已经持有锁,避免死锁?另外,时钟的启用和禁用是否必要,特别是在异常情况下是否正确处理了?
另外,关于vendor结构体的字段,比如always_enabled、inv_fr、fr_busy,这些可能用于处理不同厂商的PL011变种,比如兼容性处理或硬件错误规避。比如inv_fr可能表示某些标志位需要取反,fr_busy是busy位的掩码。
需要确认各个寄存器的位定义,如UART011_CR_CTSEN、UART01x_CR_UARTEN、UART011_CR_TXE,这些控制位的设置是否正确,以确保发送功能启用,同时可能禁用CTS流控。
最后,整个流程是确保在控制台写入时,正确配置UART,处理并发访问,并在完成后恢复原有设置,保证系统的稳定性和硬件的正确操作。</think>---
### **代码功能概述**
此函数 `pl011_console_write` 是 **PL011 串口驱动的控制台输出实现**,负责将数据通过串口发送到控制台。其核心逻辑包括:
1. **硬件资源管理**:时钟使能/关闭、中断控制。
2. **并发安全**:通过自旋锁确保多线程/中断场景下的数据一致性。
3. **硬件配置**:动态修改控制寄存器(CR)以启用发送功能。
4. **数据传输**:调用底层字符发送函数,等待发送完成。
---
### **关键步骤解析**
#### **1. 获取硬件资源**
```c
struct uart_amba_port *uap = amba_ports[co->index];
clk_enable(uap->clk); // 启用串口时钟
```
- **`amba_ports`**:全局数组,存储所有 PL011 串口设备实例。
- **`co->index`**:通过 `console` 结构标识具体串口实例。
- **时钟管理**:`clk_enable` 确保串口控制器处于工作状态。
---
#### **2. 并发控制与中断处理**
```c
local_irq_save(flags); // 保存中断状态并禁用中断
if (uap->port.sysrq) // 处理 SysRq 特殊场景(如内核调试)
locked = 0;
else if (oops_in_progress) // 内核崩溃时尝试非阻塞加锁
locked = spin_trylock(&uap->port.lock);
else
spin_lock(&uap->port.lock); // 常规加锁
```
- **自旋锁**:保护共享资源(如发送缓冲区)不被并发访问。
- **`oops_in_progress`**:内核发生严重错误时,可能已持有锁,需避免死锁。
- **`sysrq`**:系统请求处理期间,可能需绕过锁机制直接操作硬件。
---
#### **3. 配置控制寄存器(CR)**
```c
if (!uap->vendor->always_enabled) {
old_cr = pl011_read(uap, REG_CR); // 读取当前 CR
new_cr = old_cr & ~UART011_CR_CTSEN; // 禁用 CTS 流控
new_cr |= UART01x_CR_UARTEN | UART011_CR_TXE; // 启用 UART 和发送器
pl011_write(new_cr, uap, REG_CR); // 写入新 CR
}
```
- **寄存器操作**:
- `UART01x_CR_UARTEN`:全局启用 UART。
- `UART011_CR_TXE`:启用发送器。
- `UART011_CR_CTSEN`:清除 CTS 使能位(避免硬件流控阻塞发送)。
- **厂商适配**:`vendor->always_enabled` 标记某些硬件无需动态配置 CR(如已由固件初始化)。
---
#### **4. 发送数据**
```c
uart_console_write(&uap->port, s, count, pl011_console_putchar);
```
- **核心逻辑**:调用通用串口控制台写入函数 `uart_console_write`。
- **参数**:串口端口、数据指针、长度、字符发送函数 `pl011_console_putchar`。
- **字符发送**:`pl011_console_putchar` 直接操作硬件寄存器发送单个字符(未在代码中展示,但需实现类似 `writel` 操作)。
---
#### **5. 等待发送完成**
```c
while ((pl011_read(uap, REG_FR) ^ uap->vendor->inv_fr) & uap->vendor->fr_busy)
cpu_relax(); // 降低 CPU 占用
```
- **状态寄存器(FR)检查**:
- **`fr_busy`**:标识发送器忙的标志位(如 `UART01x_FR_BUSY`)。
- **`inv_fr`**:某些硬件需对 FR 值取反(兼容性适配)。
- **忙等待**:循环直到发送器空闲,确保后续操作安全。
---
#### **6. 恢复硬件状态**
```c
if (!uap->vendor->always_enabled)
pl011_write(old_cr, uap, REG_CR); // 恢复原始 CR
```
- **寄存器恢复**:若之前修改了 CR,需还原其原始值(避免影响其他功能)。
---
#### **7. 释放资源**
```c
if (locked) spin_unlock(&uap->port.lock);
local_irq_restore(flags); // 恢复中断状态
clk_disable(uap->clk); // 关闭时钟(若引用计数允许)
```
- **锁与中断**:确保并发安全后恢复系统状态。
- **时钟管理**:通过引用计数机制安全关闭时钟。
---
### **设计要点总结**
| **设计目标** | **实现方式** |
|-----------------------|-----------------------------------------------------------------------------|
| **并发安全** | 自旋锁 + 中断禁用,适配 `sysrq` 和 `oops` 场景。 |
| **硬件兼容性** | `vendor` 结构体适配不同厂商硬件特性(如反转标志位、CR 配置策略)。 |
| **低延迟输出** | 直接操作寄存器发送数据,绕过高层抽象。 |
| **资源管理** | 动态时钟控制、寄存器状态保存与恢复。 |
---
### **典型应用场景**
1. **内核日志输出**:在系统启动早期或发生崩溃时,通过串口输出调试信息。
2. **用户控制台交互**:在无图形界面系统中提供命令行交互功能。
3. **低层调试**:绕过驱动框架直接操作硬件,用于诊断硬件或固件问题。
---
### **代码关联性**
- **与早期控制台(earlycon)的区别**:
- **`pl011_early_write`**:直接操作寄存器,无锁或时钟管理(依赖启动阶段的特殊环境)。
- **`pl011_console_write`**:完整的驱动实现,集成并发控制和电源管理,适用于正常运行的内核。
阅读全文
相关推荐


















