Linux 内核:AXI 互连,在何处以及如何处理?

Linux Kernel : AXI interconnect, where and how is it handled?

我正在为我的公司编写 Linux 驱动程序,以便将我们的硬件移植到 GNU/Linux 桌面。我根本不是硬件专家,我正在努力了解内核和硬件之间的通信是如何进行的。

我们基本上有一个 AXI 互连,上面连接了一些 IP(我们使用的是 Xilinx 开发板 运行 PetaLinux)。

我已经能够向硬件发送请求,它运行良好,但我觉得我遗漏了什么。 在内核中,由于 ioremap(),我将物理地址映射到虚拟地址,并且我自己实现了 read/write,如下所示:

static void __iomem *mailbox;

static ssize_t mailbox_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    ssize_t retval = 0;
    uint32_t mlb_data = 0; 

    if(down_interruptible(&sem))
        return -ERESTARTSYS;

    // *f_pos must be a multiple of 4
    // *f_pos must be in bounds
    // count must be 4 (mailbox supports only reading 4 bytes at a time)
    if(*f_pos % 4 || *f_pos >= MAILBOX_SIZE || count != sizeof(mlb_data)) {
        retval = -EINVAL;
        goto out;
    }

    mlb_data = readl(mailbox + *f_pos);

    if(copy_to_user(buf, &mlb_data, count)) {
        retval = -EFAULT;
        goto out;
    }

    *f_pos += count;
    retval = count;

out:
    up(&sem);
    return retval;
}

int mailbox_init(dev_t device)
{
    mailbox = ioremap_nocache(MLB_BASE_ADDR, MAILBOX_SIZE);

    if(!mailbox) {
        printk(KERN_ERR DRIVER_NAME ": cannot map mailbox, ioremap failed.\n");
        return err;
    }

    return 0;
}

在用户端,我尝试 read/write 像这样:

int dev_fd = open("/dev/" DRIVER_NAME, O_RDWR);
if(dev_fd < 0) return whatever;

int data;
pread(dev_fd, &data, sizeof data, 0);

close(dev_fd);

效果很好,但我不明白怎么会这么简单,所有 AXI 东西都在哪里处理?我以为那是我必须要做的事情,但我很惊讶地看到一切都已经很好了。

我发现一切都是透明的,这真的很好,但问题是我想实现一些错误处理,但我不知道如何去做。

例如,如果我尝试在内部使用 devmem (which uses /dev/mem)从不受支持的地址读取,我会收到总线错误:

root@petalinux:~# devmem 0xCAFEBABE
Bus error

但如果我尝试通过我自己的角色设备执行相同操作,它就会挂起。似乎没有收到 SIGBUS。

我不确定我是否说得很清楚所以总结一下我的两个问题:

  1. 查看 Xilinx AXI 以太网驱动程序 (drivers/net/ethernet/xilinx/xilinx_axienet_main.c),设置 iomem cookie (mailbox) 导致 readl()writel()memcpy_fromio() 等等以正确处理访问。

    如何在硬件级别完成此操作的详细信息取决于硬件架构。例如,mach-ipx4xx 使用 __is_io_address() macro to determine whether ipx4xx_pci_read() (via inl()) or __raw_readl()/__indirect_readl() 应该使用

  2. loff_t 已签名,当偏移量无效时,return -EFAULT 而不是 -EINVAL 可能更有意义:

    // *f_pos must be within bounds
    if (*f_pos < 0 || *f_pos >= MAILBOX_SIZE) {
        retval = -EFAULT;
        goto out;
    }
    
    // *f_pos must be a multiple of 4, and
    // count must be 4 (mailbox supports only reading 4 bytes at a time)
    if ((*f_pos & 3) || count != sizeof mlb_data) {
        retval = -EINVAL;
        goto out;
    }
    
  3. 而不是 readl(),考虑使用更新的 ioread32()(或 ioread32be(),如果设备始终使用大端字节顺序)。有关详细信息,请参阅 include/asm-generic/iomap.h

    在许多架构上,ioread32() 只是调用 readl(),因此这不是功能性更改;通过跟上当前推荐的内部内核接口,这更多的是为了更容易长期维护。

    (“[certain] architectures cannot use this generic interface”的评论是针对架构维护者的,他们不能仅仅依赖通用接口,而是需要在特定架构中为他们的硬件架构实现宏文件。驱动程序开发人员可以依赖这些可用且正常工作的宏。)

  4. 在这里提高 SIGBUS 没有意义,因为用户空间不是试图直接访问内存,而是使用系统调用; returning -EFAULT 在这里绝对更合适。但是,要提高 SIGBUS,您需要

    struct kernel_siginfo info;
    
    clear_siginfo(&info);      // Important! So you don't leak to userspace.
    
    info.si_signo = SIGBUS;
    info.si_code = BUS_ADRALN; // Reason, see include/uapi/asm-generic/siginfo.h
    info.si_errno = 0;
    info.si_addr = addr;       // Address of the error
    info.si_addr_lsb = lsb;    // Least significant bit of the address
    
    force_siginfo(&info);
    

我希望这对您有所帮助,并希望您将 GPL 驱动程序推向上游,即使只有少数用户。 Greg KH 过去​​特别帮助过此类工作。