Linux SuperH (SH) 架构技术指南:Store Queue 存储队列、寄存器组、新板卡移植与 Machine/总线接口
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
本文基于 Linux 内核仓库中的 SuperH Interfaces Guide 及其 toctree 关联的 DeviceTree 启动说明、新板卡移植指南、寄存器组使用说明,并结合 SH-4 Store Queue 管理模块、Dreamcast AICA RTC 驱动、SH-X3 ILSEL 中断管理 与 Maple 总线核心驱动 的实际源码进行展开。读完本文后,你将掌握 SH-4 存储队列(Store Queue)的 remap/flush 完整 API 及底层实现机制、SH-3/SH-4 寄存器组(SR.RB banking)在内核中的具体用途、向 Linux SH 移植新板卡的完整步骤(目录结构、Kconfig、defconfig 与构建命令),以及 Dreamcast Maple 总线、AICA RTC 等机器特定接口的实现细节。
1. 文档体系与总体结构
Documentation/arch/sh/index.rst 是 Linux SH(SuperH)架构文档的入口,作者为 Paul Mundt。其结构由四个部分组成:
- toctree 子文档:booting.rst(DeviceTree 启动)、new-machine.rst(新板卡移植)、register-banks.rst(寄存器组)、features.rst(功能特性列表);
- Memory Management / SH-4 / Store Queue API:通过
kernel-doc直接导出 arch/sh/kernel/cpu/sh4/sq.c 的函数文档; - Machine Specific Interfaces:导出 mach-dreamcast 的 AICA RTC 与 mach-x3proto 的 ILSEL 管理 两个机器级接口的文档;
- Busses / Maple:导出 drivers/sh/maple/maple.c 的 Maple 总线 API 文档。
值得注意的一点是 features.rst 的内容仅有一行kernel-feat:: features sh,它并不包含正文,而是由 Sphinx 构建系统在执行kernel-feat扩展时扫描 SH 架构 Kconfig 后动态生成全部特性清单。这意味着 SH 架构支持的功能全集(CPU 型号、板卡选项、MMU/无 MMU 等)应始终以构建生成的文档为准,这也是判断某个 SH 配置项是否存在的可靠途径。
2. SH 平台启动流程:DeviceTree 约定
booting.rst 明确了 SH 平台的现代启动契约:
支持 Device-tree 的 SH 引导程序(bootloader)必须将 device tree blob 的物理地址放在 r4 寄存器中。
由此衍生出三条关键约束:
- 兼容引导程序(将 DTB 物理地址放入 r4)是标准路径,内核以
CONFIG_SH_DEVICE_TREE启用设备树解析; - 老式引导程序不保证任何初始寄存器状态,因此要与旧 bootloader 互操作的内核,必须内建 DTB(builtin DTB),或者选择一个不使用设备树的 legacy 板卡选项;
- 文档同时指出,非设备树的 legacy 板卡支持正在被逐步淘汰,设备树是方向。
从当前源码树看,这一演进已经落地:arch/sh/configs/ 下提供了ap325rxa_defconfig、dreamcast_defconfig、ecovec24_defconfig、edosk7705_defconfig、edosk7760_defconfig、apsh4a3a_defconfig、apsh4ad0a_defconfig等一整套 defconfig,而 arch/sh/Makefile 中默认KBUILD_DEFCONFIG := shx3_defconfig,即默认构建目标就是设备树驱动的 SH-X3 平台。
3. 寄存器组(Register Banks):SR.RB 的内核级用法
register-banks.rst 专门讲解内核中对 SH 寄存器组的使用规则,这是阅读 SH 架构汇编代码(异常入口、中断框架)的前置知识。
3.1 硬件背景:只有 r0–r7 被分组
SH-3 与 SH-4 家族传统上带有一个部分寄存器组:由状态寄存器SR.RB位选择当前活跃组,且只有r0–r7是 banked 的。其他 CPU 家族要么有更完整的分组能力,要么完全没有。
在 SR.RB banking 模式下:
- 若
SR.RB指向目标组,banked 寄存器直接映射为 r0–r7访问; - 处于另一组上下文时,仍可用
ldc/stc指令以r0_bank–r7_bank名称访问对方组的寄存器; - 编写使用这些 banked 寄存器的代码时,必须时刻留意当前 SR.RB 取值;
- 用户态无法触碰 bank1 的值,因此内核可以把 bank1 的寄存器当作高质量的私有暂存寄存器使用。
3.2 内核对 bank1 各寄存器的实际分配
文档列出了内核当前占用的寄存器,这些约定在内核 SH 架构代码(异常向量、do_IRQ()等)中是硬约束:
| Bank1 寄存器 | 别名 | 用途 |
|---|---|---|
r0_bank,r1_bank | k0, k1 | 异常处理时的暂存寄存器(scratch registers) |
r2_bank | — | 跟踪 EXPEVT/INTEVT 码,供do_IRQ()等按中断异常向量跳转表偏移做 IRQ 映射 |
r6_bank | — | 全局中断掩码;SR.IMASK 中断处理路径用它设置中断优先级(对应local_irq_enable()一类操作) |
r7_bank | current | 保存当前任务指针(current) |
理解这张表的价值在于:当你在 SH 汇编路径中看到一个"无名"的 banked 寄存器读写,可以先按上述分配对号入座,避免误判为通用寄存器而破坏其隐式语义。
4. SH-4 Store Queue:内存管理与高性能 DMA 映射
4.1 什么是 Store Queue
Store Queue(SQ)是 SH-4 CPU 集成的写缓冲/预取结构:主机侧对 SQ 映射区间的普通存储访问会被缓冲在 SQ 中,再异步写入目标物理地址(通常是帧缓冲、外设 DMA 缓冲区),从而让 CPU 不必等待慢速总线事务。Linux SH 为此提供了 arch/sh/kernel/cpu/sh4/sq.c 中"SH-4 integrated Store Queues 通用管理 API",该模块以module_init(sq_api_init)形式注册,启动时打印sq: Registering store queue API.。
4.2 核心 API 三个函数
sq_remap(phys, size, name, prot)—— 把物理地址经 SQ 映射,返回可用的 SQ 虚拟地址。其内核文档注释与实现要点:
- 参数:
phys物理地址、size长度、name发起映射的用户名(启动时及通过 sysfs 记录)、prot保护位; - 实现(sq.c#L143-L197)中有多层防护:
- 拒绝
size == 0以及phys + size - 1回绕(end < phys),返回-EINVAL; - 拒绝把普通内存拉进 SQ:
phys < virt_to_phys(high_memory)直接返回-EINVAL,防止普通内存被当作 SQ 目标重映射; - 物理地址按页对齐(
phys &= PAGE_MASK),长度向上页对齐; - 用
bitmap_find_free_region(sq_bitmap, 0x04000000 >> PAGE_SHIFT, get_order(size))在 SQ 地址空间(0x0400_0000 页粒度位图)中分配页,得到map->sq_addr = P4SEG_STORE_QUE + (page << PAGE_SHIFT); - 成功时打印
sqremap: <name> [N pages] va 0x... pa 0x...便于调试。
- 拒绝
映射建立(__sq_remap,sq.c#L101-L130)分两种情况:
- 有 MMU(
CONFIG_MMU):调用__get_vm_area_caller()在 SQ 地址范围内申请VM_IOREMAP虚拟区,再用ioremap_page_range()把该 VA 段映射到目标物理地址; - 无 MMU:直接把每个队列的 QACR 寄存器写入物理地址的对应高位(
((addr >> 26) << 2) & 0x1c),分别落到SQ_QACR0/SQ_QACR1。
sq_flush_range(start, len)—— 线性地冲刷@start到@start + @len的 SQ 内容。实现(sq.c#L57-L67)非常有代表性:
void sq_flush_range(unsigned long start, unsigned int len) { unsigned long *sq = (unsigned long *)start; /* Flush the queues */ for (len >>= 5; len--; sq += 8) prefetchw(sq); /* Wait for completion */ store_queue_barrier(); }它以 32 字节(len >>= 5、sq += 8)为粒度发起prefetchw,然后通过store_queue_barrier()等待完成——该屏障宏(sq.c#L42-L47)先读P4SEG_STORE_QUE状态寄存器,再写 0 到其 +0 与 +8 两个控制寄存器,从而强制排空队列。
sq_unmap(vaddr)—— 释放先前由sq_remap()建立的映射:按sq_addr在映射链表上查找(找不到会打印bad store queue address),释放位图区间;MMU 情况下调用remove_vm_area()拆掉 VMA;最后从链表移除并释放sq_mapping对象。文档注释说明它同时释放了内核页表中的 PTE 并丢弃 UTLB 翻译。
三个函数均通过EXPORT_SYMBOL导出,供模块态使用。
4.3 用户态/sysfs 接口:per-CPU 的mapping属性
sq.c#L247-L370 实现了"刻意复杂"的 sysfs 接口(源码注释自嘲为 "Needlessly complex sysfs interface"):通过subsys_interface挂在cpu_subsys上,为每个 CPU在/sys/devices/system/cpu/cpuN/sq/下创建一个mapping属性文件(权限 0644,见__ATTR(mapping, 0644, ...)):
- 读(
mapping_show):逐行输出sq_addr 起始-结束 [物理地址]: 名字,即当前全部 SQ 映射的清单; - 写(
mapping_store):格式为"<base> <len>"(sscanf(buf, "%lx %lx", ...))。len非零时以名字"Userspace"、PAGE_SHARED权限调用sq_remap(base, len, ...);len为 0 则调用sq_unmap(base)。
这为调试 SQ 驱动提供了不改代码即可创建/拆除映射的手段。
4.4 初始化与资源边界
sq_api_init()(sq.c#L372-L399)创建store_queue_cacheslab 缓存、按0x04000000 >> PAGE_SHIFT个页分配位图,并注册 subsys 接口;模块作者署名为 Paul Mundt 与 M. R. Brown。从位图尺寸可以确认:SQ 可分配的地址空间总预算为64 MiB(0x0400_0000)。
5. 机器特定接口(Machine Specific Interfaces)
5.1 mach-dreamcast:AICA RTC 的时间服务
index.rst 用:internal:方式导出 arch/sh/boards/mach-dreamcast/rtc.c 的 kernel-doc,对应 Dreamcast 主板上 AICA 芯片的 RTC。实现要点(源码注释与代码直接给出):
- 纪元修正:AICA RTC 的 Epoch 是 1950-01-01,因此读取时要减去 20 年(秒)、写入时加上 20 年换算到 Unix Epoch,代码用
TWENTY_YEARS ((20 * 365LU + 5) * 86400)(含 5 个闰年日); - 32 位秒计数器由两个 16 位寄存器组成:
AICA_RTC_SECS_H = 0xa0710000、AICA_RTC_SECS_L = 0xa0710004; - 抗撕裂读:
aica_rtc_gettimeofday()连续读两遍高低位组合值,do { ... } while (val1 != val2)直到两次读取一致,防止在 0xFFFF -> 0x0000 跨越瞬间读到撕裂值;aica_rtc_settimeofday()写后同样回读校验一致性; - 通过
rtc_class_ops(read_time/set_time)注册到通用 RTC 框架:aica_time_init()用platform_device_register_data(NULL, "rtc-generic", -1, ...)注册rtc-generic平台设备,由arch_initcall启动。
这是"机器相关硬件 + 通用内核框架"的教科书式组合:板级代码只提供硬件操作函数,时间子系统(rtc_time64_to_tm等)全部复用通用实现。
5.2 mach-x3proto:ILSEL 中断源选择管理
arch/sh/boards/mach-x3proto/ilsel.c 为 SH-X3 原型板提供 ILSEL(Interrupt Level Select)辅助例程,SH-X3 的 15 级中断源选择寄存器布局(源码注释给出):
ILSEL0 - 0xb8100004 [ Levels 1 - 4 ] ILSEL1 - 0xb8100006 [ Levels 5 - 8 ] ILSEL2 - 0xb8100008 [ Levels 9 - 12 ] ILSEL3 - 0xb810000a [ Levels 13 - 15 ]每个 16 位寄存器内 4 个 4-bit 字段各选一个中断源。三个导出 API 的内核文档(EXPORT_SYMBOL_GPL):
ilsel_enable(set):把给定非别名ILSEL 源(set <= ILSEL_KEY)挂到当前可用的最高中断级别上;调用方应注意按降序中断级别组织调用点。别名 FPGA 与外部板 IRQ 必须用ilsel_enable_fixed(),否则返回-EINVAL。实现上用find_first_zero_bit+test_and_set_bit的自旋式抢位分配ilsel_level_map,返回值是可直接用作 IRQ 号的 bit 位置;ilsel_enable_fixed(set, level):把 ILSEL 源固定在指定级别(1–15),用于级别预留以及只存在于特殊 ILSEL# 上的别名源;若该级别已被占用返回-EBUSY;ilsel_disable(irq):清掉对应 4-bit 字段并释放级别位。
源码注释特别解释了一个设计:IRQ 号与中断级别是反向编号的,因此ilsel_level_map按"从最高级别往下"组织 bit 位,保证 bit 位置与 IRQ 号 1:1 对应,ilsel_enable*()的返回值可以直接用于后续的禁用路径记账。地址/移位换算由mk_ilsel_addr()/mk_ilsel_shift()完成(ILSEL_BASE + ((ILSEL_LEVELS - bit - 1) >> 1) & ~0x1)。
6. 移植新板卡到 Linux SH(new-machine 指南精读)
new-machine.rst(作者 Paul Mundt)给出了在 2.5/2.6 系列内核上添加新板卡的完整流程,并顺带交代了相对 2.4 的重大结构变化。下面按"目录结构 → 必备代码 → 构建系统接线 → defconfig"的顺序完整继承其要点。
6.1 新的目录结构
2.4 时代大部分板级代码直接放在arch/sh/kernel/,板级头文件堆在include/asm-sh/;新内核按板型、伴晶(companion chip)类型、CPU 类型拆分:
- 板级代码:
arch/sh/boards/<board>/,板级头文件:include/asm-sh/<board>/; - 伴晶代码:
arch/sh/cchips/<chip>/<subchip>/(例如hd6446x/hd64461),其头文件同样按include/asm-sh/<chip>/组织; - CPU 家族抽象:
arch/sh/kernel/cpu/sh2|sh3|sh4/放各家族的通用文件,arch/sh/mm/同样按 CPU 家族拆分,让每个家族拥有自己的 cache/tlb 函数;头文件位于include/asm-sh/cpu-sh2|sh3|sh4/。文档同时提醒:CPU 子类型(subtype)并不被抽象,仍需由 CPU 家族代码自行处理。
6.2 每个板卡必须提供的代码
每个板卡至少需要提供get_system_type()与platform_setup()两个定义。文档给出的"虚拟板卡 vapor"示例:
/* * arch/sh/boards/vapor/setup.c - Setup code for imaginary board */ #include <linux/init.h> const char *get_system_type(void) { return "FooTech Vaporboard"; } int __init platform_setup(void) { /* * If our hardware actually existed, we would do real * setup here. Though it's also sane to leave this empty * if there's no real init work that has to be done for * this board. */ /* Start-up imaginary PCI ... */ /* And whatever else ... */ return 0; }6.3 machvec:机器的能力向量
板卡还需通过 machvec(machine vector)接入内核。machvec 函数分为几类:
- I/O 内存函数(inb 等)与 PCI/主存函数(readb 等);
- I/O 映射函数(
ioport_map、ioport_unmap等); - "heartbeat" 函数;
- PCI 与 IRQ 初始化例程;
- 一致性分配器(consistent allocator),用于需要从板级特殊 SRAM 中分配 DMA 句柄的板卡。
原文提醒"machvec 的函数随时间增删,务必以include/asm-sh/machvec.h的当前状态为准"。需要说明的是:从当前源码树核实,include/asm-sh/machvec.h已不存在,即 machvec 机制已从当前树中移除,SH 平台的能力描述已迁移到设备树与 platform 设备体系。因此本节内容应作为理解历史板级代码(以及阅读老补丁集)的背景知识;新板卡移植的实际接入点请参考第 2 节的 DeviceTree 约定与 arch/sh/Kconfig 中的板卡选项。
其余移植步骤文档描述如下:
若板级定义很精简(绝大多数板卡如此),一个板级头文件即可。新建
include/asm-sh/vapor.h,声明以机器名为前缀的板级 IO 函数原型(如vapor_inb)。这些原型由__IO_PREFIX宏自动生成:#define __IO_PREFIX vapor #include <asm/io_generic.h>文档还要求:仍在用旧式
io.h的板卡应整体删除旧文件、切换到新模式。在板级
setup.c中加入机器向量定义,最低限度为:struct sh_machine_vector mv_vapor __initmv = { .mv_name = "vapor", }; ALIAS_MV(vapor)最后添加
arch/sh/boards/vapor/io.c,实现板级 IO 函数(数量值得的话)。
文档还指出内核会在启动时自动为未定义指针包上通用例程,因为 machvec 函数指针在树中被无条件引用;不同板卡差距悬殊——有的 machvec 极其稀疏(如 dreamcast 与 sh03),有的几乎要定义全部(如 rts7751r2d)。对于板族(board family),更合适的做法是在arch/sh/boards/下建公共目录、再按家族成员建子目录,Solution Engine 与 hp6xx 就是这种模式。
6.4 接入构建系统
Kconfig 项:在 arch/sh/Kconfig 的 "System type" 菜单下添加:
config SH_VAPOR bool "Vapor" help select Vapor if configuring for a FooTech Vaporboard.machdir 接线:所有板卡都需要
machdir-y条目(arch/sh/Makefile),条目名必须是arch/sh/boards下的目录名,若位于子目录,需逐级列出父目录。示例:machdir-$(CONFIG_SH_VAPOR) += vapor若
include/asm-sh/下的目录名与板目录不同名(常见于同族多板),需要用incdir-y叠加显式追加,原文建议参考 Solution Engine 与 hp6xx 的既有写法。mach-types:在 arch/sh/tools/mach-types 文件末尾追加一行,即可在通用代码中使用隐式板型判断:
/* Make sure we're on the FooTech Vaporboard */ if (!mach_is_vapor()) return -ENODEV;注意
mach_is_boardname()会被隐式强制小写,即使 mach-types 条目本身是大写。defconfig:为新板卡提供 defconfig(假设为
arch/sh/configs/vapor_defconfig),别人拿到该板卡时可直接参考。一旦 defconfig 就位,它自动成为构建目标并出现在make help输出中,形如:Architecture specific targets (sh): ======================= ============================================= zImage Compressed kernel image (arch/sh/boot/zImage) adx_defconfig Build for adx cqreek_defconfig Build for cqreek dreamcast_defconfig Build for dreamcast ... vapor_defconfig Build for vapor ======================= =============================================随后即可执行:
$ make ARCH=sh CROSS_COMPILE=sh4-linux- vapor_defconfig vmlinux该命令会拷贝该板卡的 defconfig、跑
oldconfig(对创建以来的新选项逐一询问),随即进入新板卡内核的正常构建流程。当前树中可直接参考的真实示例包括 arch/sh/configs/dreamcast_defconfig、arch/sh/configs/ecovec24_defconfig、arch/sh/configs/edosk7760_defconfig 等。
7. 总线:Maple(Dreamcast 外设总线)
index.rst 的 "Busses / Maple" 一节通过kernel-doc导出 drivers/sh/maple/maple.c。Maple 是 Dreamcast 主机的外设总线,该驱动是模块(MODULE_AUTHOR为 Adrian McMenamin,描述 "Maple bus driver for Dreamcast")。从源码可确认的核心 API 与机制:
maple_driver_register(drv)/maple_driver_unregister(drv):把maple_driver挂到私有maple_bus_type总线上,driver_register()之后,函数 ID 匹配的设备会被自动 probe——即典型的内核总线模型(bus_type + device + driver 三方匹配);maple_add_packet(mdev, function, command, length, data):向某设备的 Maple 队列追加一条指令。32 位字计数长度,function以cpu_to_be32()大端化写入发送缓冲首字(Maple 线序为大端),随后加锁挂入maple_waitq等待发送;maple_getcond_callback(dev, callback, interval, function):为周期查询类设备(如控制器摇杆状态)注册回调,interval以 jiffies 为单位,function为大端函数码;- DMA 与 VBLANK 触发:
maple_dma_reset()揭示了 Maple 系统寄存器的位定义(源码注释):位 31–16 为超时(20ns 单位)、位 12 硬触发(置 0 则持续响应 VBLANK)、位 9–8 速率(00 = 2 Mbps,01 = 1 Mbps)、位 3–0 为 VBLANK 到 DMA 启动的延迟(1.3ms 单位,最大 11)。驱动把MAPLE_TRIGTYPE置 1 使用 VBLANK 硬件触发,并把发送缓冲物理地址写入MAPLE_DMAADDR后使能; - 设备释放:
maple_release_device()释放mapleq队列与其 recvbuf,体现 Maple 设备"基础单元 + 从属设备"的端口树模型(subdevice_map[MAPLE_PORTS]、baseunits[MAPLE_PORTS]等结构)。
8. 小结:如何使用这套文档与代码
- 查 API 文档:SH 架构的机器级/总线级 API 文档不单独成文,而是内嵌在源码的 kernel-doc 注释中,由 index.rst 以
kernel-doc::指令聚合导出。想查某个 SH 特定函数(如sq_remap、ilsel_enable、maple_add_packet),直接读对应源码文件中的 doc 注释即可,构建后的 Sphinx 文档会将其整理进 "SuperH Interfaces Guide"; - 移植板卡:以第 2 节 DeviceTree 启动约定为前提,按第 6 节的目录、Kconfig、
machdir-y/incdir-y、mach-types、defconfig 五步接线,最后用make ARCH=sh CROSS_COMPILE=... <board>_defconfig vmlinux验证构建; - 深入机制:Store Queue 的 64MiB 地址空间位图分配、per-CPU sysfs 接口(第 4 节),SR.RB bank1 的四项内核占用(第 3 节),AICA RTC 的 1950 纪元与抗撕裂读写(第 5.1 节),Maple 的 VBLANK 触发 DMA 位定义(第 7 节),都给出了可直接核对的源码位置,便于进一步追读。
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考