注释掉 printk 语句导致 linux 设备驱动程序测试崩溃

commenting out a printk statement causes crash in a linux device driver test

我在简单的 linux 驱动程序测试 (arm64) 中看到了一个奇怪的案例。
用户程序调用设备驱动程序的 ioctl 并将 uint64_t 的数组 'arg' 作为参数传递。顺便说一句,arg[2] 包含指向应用程序中变量的指针。下面是代码片段。

    case SetRunParameters:
        copy_from_user(args, (void __user *)arg, 8*3);
        offs = args[2] % PAGE_SIZE;
        down_read(&current->mm->mmap_sem);
        res = get_user_pages( (unsigned long)args[2], 1, 1, &pages, NULL);
        if (res) {
            kv_page_addr = kmap(pages);
            kv_addr = ((unsigned long long int)(kv_page_addr)+offs);
            args[2] = page_to_phys(pages) + offset; // args[2] changed to physical
        }
        else {
            printk("get_user_pages failed!\n");
        }
        up_read(&current->mm->mmap_sem);
        *(vaddr + REG_IOCTL_ARG/4) = virt_to_phys(args);  // from axpu_regs.h
        printk("ldd:writing %x at %px\n",cmdx,vaddr + REG_IOCTL_CMD/4); // <== line 248. not ok w/o this printk line why?..
        *(vaddr + REG_IOCTL_CMD/4) = cmdx;  // this command is different from ioctl cmd!
        put_page(pages); //page_cache_release(page);
        break;
    case ...

我在上面的代码中标记了第248行。如果我在那里注释掉 printk,则会发生陷阱并且虚拟机崩溃(我在 qemu 虚拟机上这样做)。 cmdx是根据app的ioctl命令设置的整数值,vaddr是设备的虚拟地址(从ioremap获取)。如果我保留 printk,它会按我预期的那样工作。什么情况下可以做到这一点? (缓存还是 tlb?)

通过 *(vaddr + REG_IOCTL_ARG/4) 等简单的 C 结构访问内存映射寄存器是一个坏主意。如果访问权限是 volatile-qualified,您可能会在某些平台上摆脱它,但它不会在某些平台上可靠地工作或根本无法工作。访问内存映射寄存器的正确方法是通过 #include <asm/io.h>#include <linux/io.h> 声明的函数。这些将处理任何特定于 arch 的要求,以确保就 CPU 而言,写入的顺序正确 1.

内存映射寄存器访问函数在 Bus-Independent Device Accesses 下的 Linux 内核文档中有描述。

此代码:

        *(vaddr + REG_IOCTL_ARG/4) = virt_to_phys(args);
        *(vaddr + REG_IOCTL_CMD/4) = cmdx;

可以改写为:

        writel(virt_to_phys(args), vaddr + REG_IOCTL_ARG/4);
        writel(cmdx, vaddr + REG_IOCTL_CMD/4);

1 如果寄存器写入的顺序很重要,特定总线类型(例如 PCI)的写入顺序可能需要额外的代码来读取写入不同寄存器之间的寄存器。这是因为写入是异步“发布”到 PCI 总线的,而 PCI 设备可能会乱序处理对不同寄存器的写入。在处理完所有前面的写入之前,设备不会处理中间寄存器读取,因此它可用于强制执行已发布写入的顺序。