You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

x86平台串口IO端口0x2f8能否mmap到用户态?求实现步骤

可以在x86平台下将UART IO端口mmap到用户态,但你的实现存在几个关键错误

先明确:x86的IO端口(比如传统UART的0x2f8)属于独立的IO地址空间,不是物理内存空间,所以你当前用virt_to_phys处理ioport_map返回的地址是完全错误的——这也是你得到诡异物理地址0x6587800102f8的原因。下面是问题分析和正确实现步骤:

你的代码中的核心错误

  • 错误的物理地址计算:ioport_map返回的是内核虚拟地址,对应IO端口空间而非物理内存,virt_to_phys仅适用于映射到物理内存的内核虚拟地址,对IO空间地址无效,所以得到的物理地址完全不可用。
  • 用户态访问地址错误:你在用户态中尝试访问address[0x2f8 + i],但mmap返回的address是映射区域的基地址,若你映射的是0x2f8开始的端口,正确的访问方式应该是address[i]对应0x2f8+i的端口;如果映射的是整个IO页(0x0000-0x0FFF),则应该是*(unsigned char*)(address + 0x2f8 + i)。
  • 未正确设置VMA属性:你没有添加VM_PFNMAP标志,这对于映射非物理内存的IO空间是必须的。

正确的实现步骤

1. 驱动端mmap函数修正

针对IO端口的mmap,核心是利用x86的IO空间映射机制,正确设置VMA属性并使用ioport_to_pfn获取IO端口对应的页帧号(PFN),而非错误的virt_to_phys。以下是修正后的mmap函数:

static int my_dev_mmap(struct file *filep, struct vm_area_struct *vma) {
    SERIAL_DEV *my_dev = (SERIAL_DEV*)filep->private_data;
    unsigned long base_port = my_dev->base_addr; // 0x2f8
    size_t map_size = vma->vm_end - vma->vm_start;
    unsigned long pfn;
    unsigned long port_page = base_port & PAGE_MASK; // 获取端口所在的IO页起始地址(0x0000)

    // 限制映射大小为包含目标端口的整页,或你需要的8字节
    if (map_size != PAGE_SIZE && map_size != 8) {
        printk("GNA: Invalid map size\n");
        return -EINVAL;
    }

    // 设置VMA属性:VM_IO标记IO空间,VM_PFNMAP表示映射的是PFN而非物理内存,禁用扩展和转储
    vma->vm_flags |= VM_IO | VM_PFNMAP | VM_DONTEXPAND | VM_DONTDUMP;
    // 设置非缓存属性,IO端口访问不能缓存
    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

    // 获取IO页对应的PFN
    pfn = ioport_to_pfn(port_page);

    // 映射整个IO页到用户态
    if (remap_pfn_range(vma, vma->vm_start, pfn, PAGE_SIZE, vma->vm_page_prot)) {
        printk("GNA: remap_pfn_range failed\n");
        return -EAGAIN;
    }

    printk("GNA: mmap success, user base: 0x%lx, IO page port: 0x%lx\n", 
           vma->vm_start, port_page);
    return 0;
}

2. 用户态代码修正

如果映射的是整个IO页(0x0000-0x0FFF),用户态需要通过偏移0x2f8来访问目标UART端口,并且要先写入scratch pad寄存器再读取(该寄存器是可读写的,默认值不确定):

#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>

#define PAGE_SIZE 4096
#define UART_BASE_PORT 0x2F8
#define SCRATCH_PAD_OFFSET 7

int main() {
    int configfd = open("/dev/custom_serial_device", O_RDWR);
    if (configfd < 0) {
        perror("open failed");
        return 1;
    }

    uint8_t *address = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, configfd, 0);
    if (address == MAP_FAILED) {
        perror("mmap failed");
        close(configfd);
        return 1;
    }

    // 获取scratch pad寄存器的用户态地址
    uint8_t *scratch_reg = address + UART_BASE_PORT + SCRATCH_PAD_OFFSET;

    // 先写入0x5A到scratch pad寄存器
    *scratch_reg = 0x5A;
    // 读取验证
    uint8_t val = *scratch_reg;
    printf("Scratch pad value: 0x%x\n", val);

    // 也可以遍历测试端口
    for (int i = 0; i < 8; i++) {
        uint8_t *port = address + UART_BASE_PORT + i;
        printf("Port 0x%x value: 0x%x\n", UART_BASE_PORT + i, *port);
    }

    munmap(address, PAGE_SIZE);
    close(configfd);
    return 0;
}

3. 额外注意事项

  • 串口配置验证:确保你已经正确配置了UART的软件回环模式,并且scratch pad寄存器是可访问的(有些UART硬件可能不支持该寄存器,需要确认你的硬件规格)。
  • 权限问题:驱动需要正确设置设备文件的权限(比如chmod 666 /dev/custom_serial_device),否则用户态程序可能无法打开或mmap设备。
  • 内核版本兼容性:上述代码基于主流Linux内核(5.x+),不同版本的内核API可能略有差异,需根据你的内核版本调整。

内容的提问来源于stack exchange,提问作者Haswell

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:26:55