3. 实现U-Boot的串口驱动
任务目标
为自定义开发板在U-Boot中实现串口驱动,完成底层寄存器操作,并注册到U-Boot驱动模型,使控制台能够通过 putc/getc 收发字符。
前置条件
- 已有可用的设备树文件(
arch/arm/dts/yourboard.dts),串口节点 compatible 属性设为"vendor,uart"。 - U-Boot 源码树已配置,且
CONFIG_DM_SERIAL与CONFIG_SYS_NS16550未启用(本驱动独立实现,不依赖NS16550框架)。
驱动实现
在 drivers/serial/ 下新建 uart_bsp.c,完整代码如下:
// drivers/serial/uart_bsp.c
#include <common.h>
#include <dm.h>
#include <serial.h>
#include <asm/io.h>
/* 寄存器偏移(以APB基址+0x1000为例) */
#define UART_BASE 0x10001000
#define UART_THR 0x00 /* 发送保持寄存器 */
#define UART_RBR 0x00 /* 接收缓冲寄存器 */
#define UART_LSR 0x14 /* 线状态寄存器 */
#define LSR_THRE (1 << 5) /* 发送保持寄存器空 */
#define LSR_DR (1 << 0) /* 数据就绪 */
struct uart_bsp_priv {
phys_addr_t base;
};
static int uart_bsp_putc(struct udevice *dev, const char ch)
{
struct uart_bsp_priv *priv = dev_get_priv(dev);
/* 等待发送FIFO为空 */
while (!(readb(priv->base + UART_LSR) & LSR_THRE))
;
writeb(ch, priv->base + UART_THR);
return 0;
}
static int uart_bsp_getc(struct udevice *dev)
{
struct uart_bsp_priv *priv = dev_get_priv(dev);
/* 等待接收数据就绪 */
while (!(readb(priv->base + UART_LSR) & LSR_DR))
;
return readb(priv->base + UART_RBR);
}
static int uart_bsp_pending(struct udevice *dev, bool input)
{
struct uart_bsp_priv *priv = dev_get_priv(dev);
if (input)
return readb(priv->base + UART_LSR) & LSR_DR;
else
return readb(priv->base + UART_LSR) & LSR_THRE;
}
static int uart_bsp_probe(struct udevice *dev)
{
struct uart_bsp_priv *priv = dev_get_priv(dev);
priv->base = dev_read_addr(dev);
if (priv->base == FDT_ADDR_T_NONE)
return -EINVAL;
/* 初始化:8N1,无流控,使能FIFO */
writeb(0x00, priv->base + 0x0C); /* LCR = 0 */
writeb(0x83, priv->base + 0x0C); /* DLAB = 1 */
writeb(0x01, priv->base + 0x00); /* DLL = 1 (115200 @ 1.8432MHz) */
writeb(0x00, priv->base + 0x04); /* DLM = 0 */
writeb(0x03, priv->base + 0x0C); /* LCR = 8N1 */
writeb(0x07, priv->base + 0x08); /* FCR 使能FIFO */
return 0;
}
static const struct dm_serial_ops uart_bsp_ops = {
.putc = uart_bsp_putc,
.getc = uart_bsp_getc,
.pending = uart_bsp_pending,
};
static const struct udevice_id uart_bsp_ids[] = {
{ .compatible = "vendor,uart" },
{ }
};
U_BOOT_DRIVER(uart_bsp) = {
.name = "uart_bsp",
.id = UCLASS_SERIAL,
.of_match = uart_bsp_ids,
.priv_auto = sizeof(struct uart_bsp_priv),
.probe = uart_bsp_probe,
.ops = &uart_bsp_ops,
};
设备树节点
在开发板设备树 &uart0 节点中确保以下内容:
uart0: serial@10001000 {
compatible = "vendor,uart";
reg = <0x10001000 0x100>;
status = "okay";
};
构建与烧录
- 将
uart_bsp.c加入drivers/serial/Makefile:obj-$(CONFIG_DM_SERIAL) += uart_bsp.o - 确认
configs/yourboard_defconfig中已启用CONFIG_DM_SERIAL=y和CONFIG_DM=y。 - 编译:
make yourboard_defconfig && make -j$(nproc) - 烧录生成的
u-boot.bin到开发板启动介质。
验证方法
- 上电后观察串口终端(115200, 8N1)是否出现 U-Boot 启动日志。若出现,说明
putc已正常工作。 - 在 U-Boot 命令行输入
help并回车,终端回显输入字符且命令执行有输出,说明getc与putc均正常。 - 若终端无输出,检查:
- 设备树 reg 地址与硬件实际基址是否一致;
- 时钟分频配置(示例中 1.8432MHz 对应 115200,需按实际时钟调整 DLL/DLM);
- 驱动是否被正确绑定(可在
uart_bsp_probe中加printf调试)。
通过标准:串口控制台能稳定输入输出,无乱码,且 dm tree 命令中能看到 uart_bsp 设备节点状态为 active。