c - 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。
我不确定我是否非常清楚,所以在这里回顾一下我的两个问题:
- 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()宏来确定应该使用ipx4xx_pci_read()(通过
inl()
)还是__raw_readl() / __indirect_readl()。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; }
而不是
readl()
,请考虑使用较新的ioread32()
(或者ioread32be()
,如果设备始终使用大端字节序)。有关详细信息,请参见include/asm-generic/iomap.h。在许多架构上,
ioread32()
确实只是调用readl()
,所以这不是功能更改;通过跟上当前推荐的内部内核接口,这更多是为了更容易长期维护。(“[某些]架构不能使用这个通用接口”的评论是针对架构维护者的,他们不能仅仅依赖通用接口,而是需要在特定于架构的文件中实现其硬件架构的宏。驱动程序开发人员可以依赖这些宏可用并正常工作。)
在这里提高 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 过去尤其帮助过此类工作。
推荐阅读
- ios - Cocoapod 安装后未找到代码签名
- html - 滑动卡间距问题
- c# - Assert.True 没有通过测试
- animation - Flutter - 如何拥有任意数量的隐式动画
- python - 如何在 django sqlite db 中上传创建的 xml 文件
- c# - 异步数据加载但 UI 变得无响应 WPF
- azure - 在 MVC 应用程序中添加 Azure Active Directory 用户
- javascript - 错误:用户 'postgres'@'localhost' 的访问被拒绝(命令行)
- sql - 使用 If Exists 语句更新或插入行的 SQL 查询不起作用
- python - Dask between_time 是否存在?