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。
我不确定我是否说得很清楚所以总结一下我的两个问题:
- Linux 内核中的 AXI 东西在哪里处理?会不会是 Xilinx 驱动程序“覆盖”了我的驱动程序?
- 如何像
/dev/mem
那样通过向用户态应用程序发送 SIGBUS 来处理硬件故障?
查看 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() 应该使用
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;
}
而不是 readl()
,考虑使用更新的 ioread32()
(或 ioread32be()
,如果设备始终使用大端字节顺序)。有关详细信息,请参阅 include/asm-generic/iomap.h。
在许多架构上,ioread32()
只是调用 readl()
,因此这不是功能性更改;通过跟上当前推荐的内部内核接口,这更多的是为了更容易长期维护。
(“[certain] architectures cannot use this generic interface”的评论是针对架构维护者的,他们不能仅仅依赖通用接口,而是需要在特定架构中为他们的硬件架构实现宏文件。驱动程序开发人员可以依赖这些可用且正常工作的宏。)
在这里提高 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 过去特别帮助过此类工作。
我正在为我的公司编写 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。
我不确定我是否说得很清楚所以总结一下我的两个问题:
- Linux 内核中的 AXI 东西在哪里处理?会不会是 Xilinx 驱动程序“覆盖”了我的驱动程序?
- 如何像
/dev/mem
那样通过向用户态应用程序发送 SIGBUS 来处理硬件故障?
查看 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() 应该使用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; }
而不是
readl()
,考虑使用更新的ioread32()
(或ioread32be()
,如果设备始终使用大端字节顺序)。有关详细信息,请参阅 include/asm-generic/iomap.h。在许多架构上,
ioread32()
只是调用readl()
,因此这不是功能性更改;通过跟上当前推荐的内部内核接口,这更多的是为了更容易长期维护。(“[certain] architectures cannot use this generic interface”的评论是针对架构维护者的,他们不能仅仅依赖通用接口,而是需要在特定架构中为他们的硬件架构实现宏文件。驱动程序开发人员可以依赖这些可用且正常工作的宏。)
在这里提高 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 过去特别帮助过此类工作。