首页 > 解决方案 > Linux 内核:AXI 互连,它在哪里以及如何处理?

问题描述

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

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

我已经能够向硬件发送请求,它运行良好,但我觉得我错过了一些东西。在内核中,我将物理地址映射到虚拟地址,ioremap()并且我自己实现了读/写,如下所示:

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;
}

在用户方面,我尝试像这样读/写:

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(内部使用/dev/mem)从不受支持的地址读取,我会收到总线错误:

root@petalinux:~# devmem 0xCAFEBABE
Bus error

但是如果我尝试通过我自己的字符设备来做同样的事情,它就会挂起。似乎没有收到 SIGBUS。

我不确定我是否非常清楚,所以在这里回顾一下我的两个问题:

标签: clinuxlinux-kernellinux-device-driverhardware

解决方案


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

    如何在硬件级别完成此操作的细节取决于硬件架构。例如,mach-ipx4xx 使用__is_io_address()宏来确定应该使用ipx4xx_pci_read()(通过inl())还是__raw_readl() / __indirect_readl()

  2. loff_t已签名,并且当偏移量无效时返回 -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(),所以这不是功能更改;通过跟上当前推荐的内部内核接口,这更多是为了更容易长期维护。

    (“[某些]架构不能使用这个通用接口”的评论是针对架构维护者的,他们不能仅仅依赖通用接口,而是需要在特定于架构的文件中实现其硬件架构的宏。驱动程序开发人员可以依赖这些宏可用并正常工作。)

  4. 在这里提高 SIGBUS 并没有什么意义,因为用户空间不是直接访问内存,而是使用系统调用;返回 -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 过去​​尤其帮助过此类工作。


推荐阅读