HardFault
HardFault

Reputation: 11

Accessing external bus in kernel space on an ARM based board

I'm trying to write an LCD display driver on an ARM based board. The LCD controller is plugged on the external memory bus. So I try to convert the physical address of registers of the controller to the virtual one.

I use the following pieces of code to do that :

#define AT91_VA_BASE_CS2    phys_to_virt(0x50000000)

static inline unsigned char at91_CS2_read(unsigned int reg)
{
    void __iomem *CS2_base = (void __iomem *)AT91_VA_BASE_CS2;

    return __raw_readb(CS2_base + reg);
}

static inline void at91_CS2_write(unsigned int reg, unsigned char value)
{
    void __iomem *CS2_base = (void __iomem *)AT91_VA_BASE_CS2;

    __raw_writeb(value, CS2_base + reg);
}

void write_lcd_port (int mode, unsigned char cmd_dat)
{
   while ((read_lcd_port() & 0x03) != 0x03) {
      /* wait while LCD is busy!!! */
   } /* endwhile */


   /* Send Command */
   if (mode == 1)
   {
       at91_CS2_write(4, cmd_dat);
   }
   /* Send Data */
   if (mode == 0)
   {
       at91_CS2_write(0, cmd_dat);
   }
}

I get the following message :

Unable to handle kernel paging request at virtual address 4f000004
pgd = c39bc000
[4f000004] *pgd=00000000
Internal error: Oops: 5 [#1]
Modules linked in: module_complet dm9000 at91_wdt vfat fat jffs2 nls_iso8859_1 nls_cp437 nls_base usb_storage sd_mod sg scsie
CPU: 0
PC is at read_lcd_port+0x1c/0x38 [module_complet]
LR is at 0x1
pc : [<bf0a21b8>]    lr : [<00000001>]    Tainted: P
sp : c380bf1c  ip : 60000093  fp : c380bf2c
r10: 0003a804  r9 : c380a000  r8 : c001de64
r7 : 00000000  r6 : fefff000  r5 : 0000009c  r4 : 00000001
r3 : 4f000000  r2 : 00000000  r1 : 00001438  r0 : bf0a25cc
Flags: nZCv  IRQs on  FIQs on  Mode SVC_32  Segment user
Control: C000717F  Table: 239BC000  DAC: 00000015
Process insmod (pid: 903, stack limit = 0xc380a198)
Stack: (0xc380bf1c to 0xc380c000)
bf00:                                                                00000001
bf20: c380bf44 c380bf30 bf0a21f4 bf0a21ac 00000000 fefa0000 c380bf54 c380bf48
bf40: bf0a2288 bf0a21e4 c380bf64 c380bf58 bf0a246c bf0a2280 c380bf84 c380bf68
bf60: bf0a4058 bf0a2464 40017000 c01c89a0 bf0a2d80 c01c8990 c380bfa4 c380bf88
bf80: c004cd20 bf0a4010 00000003 00000000 0000000c 00000080 00000000 c380bfa8
bfa0: c001dcc0 c004cbc8 00000000 0000000c 00900080 40017000 0000162e 00041050
bfc0: 00000003 00000000 0000000c bea0fde4 bea0fec4 00000002 0003a804 00000000
bfe0: bea0fd10 bea0fd04 0001b290 400d1d20 60000010 00900080 20002031 20002431
Backtrace:
[<bf0a219c>] (read_lcd_port+0x0/0x38 [module_complet]) from [<bf0a21f4>] (write_lcd_port+0x20/0x80 [module_complet])
 r4 = 00000001
[<bf0a21d4>] (write_lcd_port+0x0/0x80 [module_complet]) from [<bf0a2288>] (wr_cmd+0x18/0x1c [module_complet])
 r5 = FEFA0000  r4 = 00000000
[<bf0a2270>] (wr_cmd+0x0/0x1c [module_complet]) from [<bf0a246c>] (lcd_init+0x18/0x80 [module_complet])
[<bf0a2454>] (lcd_init+0x0/0x80 [module_complet]) from [<bf0a4058>] (mon_module_init+0x58/0xcc [module_complet])
[<bf0a4000>] (mon_module_init+0x0/0xcc [module_complet]) from [<c004cd20>] (sys_init_module+0x168/0x2c8)
 r6 = C01C8990  r5 = BF0A2D80  r4 = C01C89A0
[<c004cbb8>] (sys_init_module+0x0/0x2c8) from [<c001dcc0>] (ret_fast_syscall+0x0/0x2c)
 r7 = 00000080  r6 = 0000000C  r5 = 00000000  r4 = 00000003
Code: e59f001c eb3e43c2 e3a0344f e59f0014 (e5d34004)
 Segmentation fault

Note that this method works for internal peripherals (such as timers). So in some cases, phys_to_virt works. I think that no page is allocated at the address 0x50000000. How can I allocate a page at this specific address ? I found functions like kmap but it seems to be very complicated and I don't know how to use it.

Upvotes: 1

Views: 880

Answers (1)

David Brigada
David Brigada

Reputation: 594

The best way to access memory-mapped peripherals is with the kernel's ioremap and friends.

First, declare that you want to use a specific region of memory for your peripheral:

struct resource *res = request_mem_region(0x50000000, region_size, "at91");

When you unload your driver, you will want to free that memory region.

release_mem_region(0x50000000, region_size);

Now, you can remap the I/O region before use.

void *ptr = ioremap(0x50000000, region_size);

If you want to prevent caching of these registers, use ioremap_nocache instead. You can also only remap a subregion of your device's memory space if you're only using that part.

Now that you have the iomapped region, you can do I/O on that memory.

iowrite8(value, (char *)ptr + reg);
unsigned int val = ioread8((char *)ptr + reg);

Once you're done reading from and writing to that region of memory, you can unmap it.

iounmap(ptr);

I hope this helps. I would recommend reading (or at least using as a reference) Linux Device Drivers, 3rd Edition, which can be read online for free.

Upvotes: 4

Related Questions