2. 寄存器读写封装
任务说明
本案例实现NPU驱动中寄存器读写的最小安全封装,支持32位与64位访问宽度,并内置调试日志输出。核心目标是:对物理地址映射后的虚拟地址执行对齐检查、空指针校验、访问宽度分发,并在每次读写时打印地址、宽度与值,便于驱动调试与问题定位。
寄存器读写封装实现
// reg_rw.h
#ifndef REG_RW_H
#define REG_RW_H
#include <stdint.h>
#include <stddef.h>
/* 调试日志开关:1使能,0关闭 */
#ifndef REG_DEBUG_EN
#define REG_DEBUG_EN 1
#endif
#if REG_DEBUG_EN
#include <stdio.h>
#define REG_LOG(fmt, ...) \
printf("[REG] " fmt "\n", ##__VA_ARGS__)
#else
#define REG_LOG(fmt, ...) do {} while (0)
#endif
/* 寄存器访问宽度枚举 */
typedef enum {
REG_WIDTH_32 = 4,
REG_WIDTH_64 = 8
} reg_width_t;
/* 读取寄存器,addr必须为映射后的虚拟地址 */
static inline uint64_t reg_read(const volatile void *addr, reg_width_t width)
{
if (addr == NULL) {
REG_LOG("ERROR: read addr is NULL");
return 0;
}
uintptr_t ptr = (uintptr_t)addr;
if (ptr % (uintptr_t)width != 0) {
REG_LOG("ERROR: unaligned read addr=0x%lx width=%u",
(unsigned long)ptr, (unsigned)width);
return 0;
}
uint64_t val = 0;
switch (width) {
case REG_WIDTH_32:
val = *(volatile uint32_t *)addr;
break;
case REG_WIDTH_64:
val = *(volatile uint64_t *)addr;
break;
default:
REG_LOG("ERROR: unsupported width=%u", (unsigned)width);
return 0;
}
REG_LOG("READ addr=0x%lx width=%u val=0x%lx",
(unsigned long)ptr, (unsigned)width, (unsigned long)val);
return val;
}
/* 写寄存器,addr必须为映射后的虚拟地址 */
static inline void reg_write(volatile void *addr, reg_width_t width, uint64_t val)
{
if (addr == NULL) {
REG_LOG("ERROR: write addr is NULL");
return;
}
uintptr_t ptr = (uintptr_t)addr;
if (ptr % (uintptr_t)width != 0) {
REG_LOG("ERROR: unaligned write addr=0x%lx width=%u",
(unsigned long)ptr, (unsigned)width);
return;
}
switch (width) {
case REG_WIDTH_32:
*(volatile uint32_t *)addr = (uint32_t)val;
break;
case REG_WIDTH_64:
*(volatile uint64_t *)addr = val;
break;
default:
REG_LOG("ERROR: unsupported width=%u", (unsigned)width);
return;
}
REG_LOG("WRITE addr=0x%lx width=%u val=0x%lx",
(unsigned long)ptr, (unsigned)width, (unsigned long)val);
}
#endif /* REG_RW_H */
使用示例与验证
// demo.c
#include <stdio.h>
#include <stdlib.h>
#include "reg_rw.h"
int main(void)
{
/* 模拟寄存器映射区:使用malloc对齐到64字节 */
void *mapped = NULL;
if (posix_memalign(&mapped, 64, 16) != 0) {
perror("posix_memalign");
return 1;
}
/* 32位写读验证 */
reg_write(mapped, REG_WIDTH_32, 0x12345678);
uint64_t r32 = reg_read(mapped, REG_WIDTH_32);
printf("check32: %s\n", (r32 == 0x12345678) ? "PASS" : "FAIL");
/* 64位写读验证 */
reg_write(mapped, REG_WIDTH_64, 0xDEADBEEFCAFEBABEULL);
uint64_t r64 = reg_read(mapped, REG_WIDTH_64);
printf("check64: %s\n", (r64 == 0xDEADBEEFCAFEBABEULL) ? "PASS" : "FAIL");
/* 错误路径验证:空指针与未对齐 */
reg_read(NULL, REG_WIDTH_32);
reg_write((void *)((char *)mapped + 1), REG_WIDTH_32, 1);
free(mapped);
return 0;
}
编译与运行
gcc -Wall -O2 -o demo demo.c
./demo
预期输出与判定标准
程序正常退出且打印以下内容时判定通过:
[REG] WRITE addr=0x... width=4 val=0x12345678
[REG] READ addr=0x... width=4 val=0x12345678
check32: PASS
[REG] WRITE addr=0x... width=8 val=0xdeadbeefcafebabe
[REG] READ addr=0x... width=8 val=0xdeadbeefcafebabe
check64: PASS
[REG] ERROR: read addr is NULL
[REG] ERROR: unaligned write addr=0x... width=4
其中addr=0x...为实际映射地址。若check32与check64均输出PASS,且错误路径打印了对应的ERROR日志,则说明寄存器读写封装功能正确、调试日志生效。
关键设计说明
- 对齐与空指针保护:读写前检查地址是否为
NULL以及是否按访问宽度对齐,不满足条件时记录错误日志并安全返回,避免总线错误。 - 宽度分发:通过
switch区分32/64位访问,使用volatile指针防止编译器优化掉实际硬件读写。 - 调试日志:通过
REG_DEBUG_EN宏控制日志编译开关,默认开启;日志包含操作类型、地址、宽度与值,便于追踪访问序列。 - 适用范围:本封装仅处理映射后虚拟地址的读写,不包含物理地址映射过程(如
mmap或ioremap),该部分由驱动框架或平台层负责。