Search code examples
linux-kernellinux-device-driver

how to read a register in device driver?


in a linux device driver, in the init function for the device, I tried reading an address (which is SMMUv3 device for arm64) like below.

uint8_t *addr1;

addr1 = ioremap(0x09050000, 0x20000);  
printk("SMMU_AIDR     : 0x%X\n", *(addr1 + 0x1c));

but I get Internal error: synchronous external abort: 96000010 [#1] SMP error.
Is it not permitted to map an address to virtual address using ioremap and just reading that address?


Solution

  • I gave a fixed value 0x78789a9a to SMMU IDR[2] register. (at offset 0x8, 32 bit register. This is possible because it's qemu.) SMMU starts at 0x09050000 and it has address space 0x20000.

    __iomem uint32_t *addr1 = NULL;
    
    static int __init my_driver_init(void)
    {
    ...
    addr1 = ioremap(0x09050000, 0x20000); // smmuv3
    printk("SMMU_IDR[2]     : 0x%X\n", readl(addr1 +0x08/4));
    ..}
    

    This is the output when the driver is initialized.(The value is read ok)

    [  453.207261] SMMU_IDR[2]     : 0x78789A9A
    

    The first problem was that the access width was wrong for that address. Before, it was defined as uint8_t *addr1; and I used printk("SMMU_AIDR : 0x%X\n", *(addr1 + 0x1c)) so it was reading byte when it was not allowed by the SMMU model.

    Second problem (I think this didn't cause the trap because arm64 provides memory mapped io) was that I used memory access(pointer dereferencing) for memory mapped IO registers. As people commented, I should have used readl function. (Mainly because to make the code portable. readl works also for iomap platforms like x86_64. using the mmio adderss as pointer will not work on such platforms. I later found that readl function takes care of the memory barrier problem too). ADD : I fixed volatile to __iomem for variable addr1.(thanks @0andriy)