【问题标题】:Reading from USB device and writing to physical address从 USB 设备读取并写入物理地址
【发布时间】:2015-04-25 13:43:11
【问题描述】:

我有一个输出一个字节大小的数据的 USB 设备,我想将这些字节传递给存在于 AXI 桥上的 FPGA 组件,FPGA 和 CPU 在同一个芯片上......它是 SoC FPGA Altera Cyclone V . CPU是ARM Cortex-A9。内核版本 3.7.0。

有一个软件可以从 USB 设备读取数据并写入转储文件……它工作得很好。我尝试使用mmap() 将FPGA地址映射到虚拟空间并从用户空间写入。这样做时……过了一分钟,内核似乎崩溃了。

我为我的 FPGA 组件编写了一个驱动程序,并将驱动程序路径作为文件传递给该软件,以便它写入它,并最终写入我的 FPGA 组件,但结果相同......内核在一个随机时间。

我还编写了一个简单的程序,它从本地文件中读取字节并将其传递给 FPGA……这两种方法都可以正常工作(使用mmap() 或驱动模块),文件传递到 FPGA 时没有问题不管文件有多大。

所以问题是从 USB 设备传递到 FPGA 时,使用mmap() 或驱动模块。

这是一个示例崩溃消息:

  Internal error: Oops - undefined instruction: 0 [#1] SMP ARM
  Modules linked in: ipv6
  CPU: 1    Not tainted  (3.7.0 #106)
  PC is at scheduler_ipi+0x8/0x4c
  LR is at handle_IPI+0x10c/0x19c
  pc : [<800521a0>]    lr : [<800140d4>]    psr: 80000193
  sp : bf87ff58  ip : 8056acc8  fp : 00000000
  r10: 00000000  r9 : 413fc090  r8 : 00000001
  r7 : 00000000  r6 : bf87e000  r5 : 80535018  r4 : 8053eec0
  r3 : 8056ac80  r2 : bf87ff58  r1 : 00000482  r0 : 00000481
  Flags: Nzcv  IRQs off  FIQs on  Mode SVC_32  ISA ARM  Segment kernel
  Control: 10c5387d  Table: 3f0c404a  DAC: 00000015
  Process swapper/1 (pid: 0, stack limit = 0xbf87e240)
  Stack: (0xbf87ff58 to 0xbf880000)
  ff40:                                                       00000000 800140d4
  ff60: fffec10c 8053e418 bf87ff90 fffec100 8000f6e0 8000851c 8000f708 8000f70c
  ff80: 60000013 ffffffff bf87ffc4 8000e180 00000000 00000000 00000001 00000000
  ffa0: bf87e000 80565688 803ddfb0 80541fc8 8000f6e0 413fc090 00000000 00000000
  ffc0: 8053e9b8 bf87ffd8 8000f708 8000f70c 60000013 ffffffff 00000020 8000f894
  ffe0: 3f86c06a 00000015 10c0387d 805658d8 0000406a 003d1ee8 31ca2085 5c1021c3
  Code: eaffffad 80564700 e92d4800 e1a0200d (4c4c9b50)
  ---[ end trace 9e492cde975c41f9 ]---

其他崩溃消息的开头如下:

  Unable to handle kernel paging request at virtual address 2a7a4390
  Internal error: Oops - bad syscall: ebcffb [#1] SMP ARM
  pgd = bf318000
  [2a7a4390] *pgd=00000000

还有:

 Internal error: Oops - undefined instruction: 0 [#2] SMP ARM
 Modules linked in: ipv6
 CPU: 1    Tainted: G      D       (3.7.0 #106)

这是完整的crash messages

我注意到我收到的所有崩溃消息都与 PC 和 LR 位置相交,但实际上我以前没有使用 Linux 内核的经验。我在网上发现了类似的错误消息,但没有一个建议的解决方案对我有用。

源代码:

每当新的字节缓冲区从 USB 到达时,都会调用此函数:

static void rtlsdr_callback(unsigned char *buf, uint32_t len, void *ctx)
{
    if (ctx) {
        if (do_exit)
            return;

        if ((bytes_to_read > 0) && (bytes_to_read < len)) {
            len = bytes_to_read;
            do_exit = 1;
            rtlsdr_cancel_async(dev);
        }

/*      if (fwrite(buf, 1, len, (FILE*)ctx) != len) {
            fprintf(stderr, "Short write, samples lost, exiting!\n");
            rtlsdr_cancel_async(dev);
        }
*/
        if (fm_receiver_addr == NULL)
        {
            virtual_base = mmap(NULL, HPS2FPGA_SPAN, PROT_WRITE, MAP_PRIVATE, fd, HPS2FPGA_BASE);
            if (virtual_base == MAP_FAILED)
            {
                perror("mmap");
                close(fd);
                exit(1);
            }

            fm_receiver_addr = (unsigned char*)(virtual_base + FM_DEMOD_OFFSET);
        }

        int i, j;
        for (i = 0; i < len; i++)
        {
            *fm_receiver_addr = buf[i];
            for (j = 0; j < 150; j++);
        }

        if (bytes_to_read > 0)
            bytes_to_read -= len;
    }
}

您看到我评论了fwrite() 函数(原始代码使用它来写入文件)并将其替换为写入我的 FPGA 组件的代码:*fm_receiver_addr = buf[i];。在此之前,我会检查地址是否有效,如果无效则获取另一个地址。

另一种方式,驱动模块,我写了这段代码:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include <linux/ioport.h>
#include <linux/io.h>

#define HPS2FPGA_BASE       0xC0000000
#define HPS2FPGA_SPAN       PAGE_SIZE

void* fm_demod_addr;
int i;

// Get a driver entry in Sysfs
static struct device_driver fm_demod_driver = 
{
    .name = "fm-demodulator",   // Name of the driver
    .bus = &platform_bus_type,  // Which bus does the device exist
};

// Function that is used when we read from the file in /sys, but we won't use it
ssize_t fm_demod_read(struct device_driver* drv, char* buf)
{ return 0; }

// Function that is called when we write to the file in /sys
ssize_t fm_demod_write_sample(struct device_driver* drv, const char* buf, size_t count)
{
    if (buf == NULL)
    {
        pr_err("Error! String must not be NULL!\n");
        return -EINVAL;
    }

    for (i = 0; i < count; i++)
    {
        iowrite8(buf[i], fm_demod_addr);
    }

    return count;
}

// Set our module's pointers and set permissions mode
static DRIVER_ATTR(fm_demod, S_IWUSR, fm_demod_read, fm_demod_write_sample);

// Set module information
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Siraj Muhammad <sirajmuhammad@outlook.com>");
MODULE_DESCRIPTION("Driver for FPGA component 'FM Demodulator'");

static int __init fm_demod_init(void)
{
    int ret;
    struct resource* res;

    // Register driver in kernel
    ret = driver_register(&fm_demod_driver);
    if (ret < 0)
        return ret;

    // Create file system in /sys
    ret = driver_create_file(&fm_demod_driver, &driver_attr_fm_demod);
    if (ret < 0)
    {
        driver_unregister(&fm_demod_driver);
        return ret;
    }

    // Request exclusive access to the memory region we want to write to
    res = request_mem_region(HPS2FPGA_BASE, HPS2FPGA_SPAN, "fm-demodulator");
    if (res == NULL)
    {
        driver_remove_file(&fm_demod_driver, &driver_attr_fm_demod);
        driver_unregister(&fm_demod_driver);
        return -EBUSY;
    }

    // Map the address into virtual memory
    fm_demod_addr = ioremap(HPS2FPGA_BASE, HPS2FPGA_SPAN);
    if (fm_demod_addr == NULL)
    {
        driver_remove_file(&fm_demod_driver, &driver_attr_fm_demod);
        driver_unregister(&fm_demod_driver);
        release_mem_region(HPS2FPGA_BASE, HPS2FPGA_SPAN);
        return -EFAULT;
    }

    return 0;
}

static void __exit fm_demod_exit(void)
{
    // Remove file system from /sys
    driver_remove_file(&fm_demod_driver, &driver_attr_fm_demod);
    // Unregister the driver
    driver_unregister(&fm_demod_driver);
    // Release requested memory
    release_mem_region(HPS2FPGA_BASE, HPS2FPGA_SPAN);
    // Un-map address
    iounmap(fm_demod_addr);

}

module_init(fm_demod_init);
module_exit(fm_demod_exit);

我将用户空间代码恢复到其原始状态,并将驱动程序路径:/sys/bus/platform/drivers/fm-demodulator/fm_demod 传递给用户空间应用程序以对其进行写入。

有没有想过?

【问题讨论】:

  • 我不是这个领域的专家,但你确定 mmap 是最好的解决方案吗? request_mem_region 怎么样? (makelinux.net/ldd3/chp-9-sect-4)
  • 我在驱动模块方法中使用了这个功能,但没有解决问题...它有时可以防止崩溃,但没有字节传输到FPGA。其他时候内核崩溃了。我只是想知道为什么从文件读取到 FPGA 时一切顺利,而从 USB 设备读取到 FPGA 时却没有。我希望我能解码产生的错误日志......当内核崩溃时,PC 和 LR 总是在 scheduler_ipi+0x8/0x4c 和 handle_IPI+0x10c/0x19c。
  • 您尝试过使用 addr2line 吗? elinux.org/Addr2line_for_kernel_debugging 对我来说这看起来像是内存对齐问题
  • 如果我们假设从 mmap() 获得的虚拟地址在中断或某些事情发生改变该地址时发生改变(顺便说一句,这会发生吗?我只是想知道),不应该驱动模块有一个永远不会改变的固定地址?另外,为什么从本地文件复制时不显示错误?
  • 可以是任何东西:错误的 RAM 时序,错误的编译器(顺便说一句,哪个编译器用于构建内核和构建模块?)。如果您可以共享用户空间工具的代码(适用于mmap(),不涉及内核模块),那将是很好的。基本上,对我来说,这看起来像是内存损坏(ARM 尝试执行一些指令(在 RAM 中),这不是正确的 ARM 指令,这可能是由于内存损坏造成的)。我不认为它与未对齐的访问有关,因为它会给你另一个例外。

标签: c linux-kernel embedded-linux


【解决方案1】:
  Internal error: Oops - undefined instruction: 0 [#1] SMP ARM
  PC is at scheduler_ipi+0x8/0x4c
  LR is at handle_IPI+0x10c/0x19c
  pc : [<800521a0>]    lr : [<800140d4>]    psr: 80000193
  [snip]
  Code: eaffffad 80564700 e92d4800 e1a0200d (4c4c9b50)
  ---[ end trace 9e492cde975c41f9 ]---

没有人可能绝对知道答案。注意:未定义指令

PC 在 scheduler_ipi+0x8/0x4c,这是硬核 ARM-Linux 调度;处理器间中断。您可以反汇编“代码:”部分以提供帮助,

   0:   eaffffad        b       0xfffffebc
   4:   80564700        subshi  r4, r6, r0, lsl #14
   8:   e92d4800        push    {fp, lr}
   c:   e1a0200d        mov     r2, sp
  10:   4c4c9b50        mcrrmi  11, 5, r9, ip, cr0

崩溃是在指令mcrrmi 处,这似乎是无意义的。如果你反汇编 sched/core.o 你会看到指令序列,但我敢打赌'4c4c9b50'的值已经损坏了。也就是说,这不是编译器生成的代码。

所以问题是当从 USB 设备传递到 FPGA 时,使用mmap() 或驱动模块。

我会用一个禅宗的动作来思考一下。 USB 设备使用 DMA?您的 FPGA 可能还可以控制 ARM/AXI 总线。我至少会考虑 FPGA 破坏总线周期并可能翻转地址位并导致物理写入内核代码空间的可能性。当您使用像 DMA 外设这样的无辜的旁观者时,可能会发生这种情况。 ARM CPU 将使用缓存并爆破所有内容。

检查事项,

  1. (括号)中的代码地址报告为编译器产生的。如果没有,硬件可能已经损坏了一些东西。 Linux 代码很难做到这一点,因为内核代码页通常是 R/O
  2. 您还应该为任何代码生成反汇编程序并查看有效的寄存器。例如,(4c4c9b50) 代码可以找到,

    printf '\x50\x9b\x4c\x4c' > arm.bin
    objdump -marm -b 二进制 -D arm.bin

您可以通过objdump vmlinux 找到scheduler_ipi 例程的汇编程序,然后确定指针可能是什么。例如,如果this_rq()R9 中,而R9 是假的,那么你就有线索了。

如果代码损坏,您需要总线分析器和/或一些例程来监控位置并在代码发生更改时报告以尝试定位损坏源。

【讨论】:

  • 感谢您的回复。在某些情况下,崩溃不会产生“未定义指令”,而是“无法在虚拟地址处理内核分页请求”。另外,“代码:”部分并不总是相同的。 USB 设备没有使用 DMA,是的,我的 FPGA 组件控制着 AXI 总线,因为我使用从属 Avalon-MM 接口将其连接到 AXI 总线。我不确定 FPGA 如何破坏总线周期。
  • objdump vmlinux 看看地址scheduler_ipi+0x8 的操作码是否是0x4c4c9b50?值 0x80564700 看起来像内核地址(文字 ROM 数据),值 0xe92d4800 或 push {fp, lr} 看起来像函数入口。用FPGA开发,可能BUS上的东西不太对劲; AXI 协议相当复杂!对于某些 USB 设备寄存器没有缓存。许多 ARM 操作码可能会转换为 nop,您将收到 分页请求,因为上一条本应加载指针的指令没有执行任何操作。
猜你喜欢
  • 1970-01-01
  • 2020-03-25
  • 1970-01-01
  • 1970-01-01
  • 2013-08-15
  • 2011-04-08
  • 2014-09-14
  • 2016-10-28
  • 2022-10-01
相关资源
最近更新 更多