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),该部分由驱动框架或平台层负责。
驱动代码 调用方 reg_read / reg_write 安全封装层 NPU寄存器 映射后虚拟地址 addr, width volatile 访问 内部校验流程 addr == NULL ? 对齐检查 宽度分发 32/64 调试日志 READ/WRITE addr width val 错误处理 返回0 / 空操作