Writing to a device at a known physical address

I know this is a pretty basic question but bear with me.

I'm running Linux (2.6.10) on a Freescale Coldfire (M5484) chip. I ported the Colilo boot loader and made some mods to the kernel to run on a custom board and things are working really well. Now I need to access particular hardware that are configured (on a Flexbus) at particular addresses. A simple example are the LED's that are used for indicating life in the system. They are mapped to 0x4200 0000 and I can access them easily from the boot loader. However once Linux is up and running I get seg faults when I try to write to that address. This makes sense since the MMU is remapping where things are but how do I work with that? How do I know where these devices are in memory and how do I get to them? Must it be done as a driver in kernel space rather than user space?

Any help much appreciated!!

Reply to
James Kimble
Loading thread data ...

Open the /dev/mem device node and work with mmap() on it.

Or switch to a current 2.6.23 kernel and you can use the UIO driver instead, to access memory mapped hardware from userspace.

JB

Reply to
Juergen Beisert

Can you give an example of this? I've been looking at mmap usage and it's not real clear...

Reply to
James Kimble

Full man can be found at

formatting link

Example: /* * Maps real memory address to a virtual address. The virtual address * then could be used to read/write the real memory as if it was accessed * directly. * Adapted by Janaka Subhawickrama. Copyright 2007. * GPL software. */ #include #include #include #include #include #include #include #include #include #include

//Virtual address that is associated with the physical address static char *regmap_addr = NULL;

//The following is the physical address of memory mapped registers of the CPU #define REGISTRY_MAP_ADDRESS_OFFSET (0xE0000000) #define REGISTRY_MAP_SIZE (0x000FFFFF) #define GPIO2_DATA_DIR_REG_OFFSET (0x00000D00) #define GPIO2_DATA_REG_OFFSET (0x00000D08)

#define GPIO2_DONE_PIN (0x00800000) #define GPIO2_FAULT_PIN (0x00400000) #define GPIO2_PROGB_PIN (0x01000000)

int main(int argc, char *argv[]) { int devmem; // this is the "/dev/mem" descriptor //Registers of the CPU I am using is 32bit volatile unsigned int uiGet; volatile unsigned int *ptmp = NULL;

//On embedded systems you may have to mknod /dev/mem printf("\nOpening /dev/mem"); devmem = open("/dev/mem", O_RDWR | O_SYNC);

if (devmem < 0) { printf("\nOpening of /dev/mem failed with (%d) %s\n", errno, strerror(errno)); return -1; }

printf("\nMapping Memory mapped registers at %08X with size %08X", REGISTRY_MAP_SIZE, REGISTRY_MAP_ADDRESS_OFFSET); regmap_addr = (char *)mmap( 0, REGISTRY_MAP_SIZE, PROT_READ| PROT_WRITE, MAP_SHARED, devmem, REGISTRY_MAP_ADDRESS_OFFSET);

if (regmap_addr == (char *)MAP_FAILED) { printf("\nCould not map registers (%d) %s", errno, strerror(errno)); close(devmem); return -1; }

//Now you can write to CPU registers as if you were from a boot loader

//Setup directions of GPIO pins *(volatile unsigned int *)(regmap_addr + GPIO2_DATA_DIR_REG_OFFSET) = GPIO2_FAULT_PIN | GPIO2_PROGB_PIN; //Turn some GPIO Pins on *(volatile unsigned int *)(regmap_addr + GPIO2_DATA_REG_OFFSET) = GPIO2_FAULT_PIN | GPIO2_PROGB_PIN; //Get some values of GPIO lines uiGet = *(volatile unsigned int *)(regmap_addr + GPIO2_DATA_REG_OFFSET);

printf("\nGPIO register %08X", uiGet);

//Cleanup munmap((void *)REGISTRY_MAP_ADDRESS_OFFSET, REGISTRY_MAP_SIZE); close(devmem); return 0; }

Reply to
Janaka

instead,

strerror(errno));

That helps!! Thanks a ton!!

Reply to
James Kimble

As you are talking to hardware, at this point you should consider there are architectures in the wild that do some kind of access reordering in hardware. For example PowerPC: In your example it will do the read access prior the (buffered) write access (even if the compiler generates code in the other order!). Your program would fail at least on this architecture.

So: Do not use volatile types in this way. Use the read?()/write?() inline functions from the architecture specific part of the linux source instead (refer include/asm-/io.h). Its more save, more portable and the source is easier to read.

JB

Reply to
Juergen Beisert

I worked with this simple example:

#include #include #include #include #include #include

void* mmio_map(off_t PhysAddr, unsigned long Size) { off_t PageOffset, PageAddress; void *base; int fd;

PageOffset = PhysAddr % getpagesize();; PageAddress = PhysAddr - PageOffset; Size += PageOffset;

if((fd = open("/dev/mem", O_RDWR)) < 0) { perror("open(/dev/mem)"); return(NULL); }

base = mmap ((caddr_t)0, Size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, PageAddress);

if (base == MAP_FAILED) { return((void*)NULL); } else { return((void*)((char*)base + PageOffset)); } }

void mmio_unmap(void* VirtAddr, unsigned long Size) { off_t PageOffset, PageAddress;

PageOffset = (off_t)VirtAddr % getpagesize();; PageAddress = (off_t)VirtAddr - PageOffset; Size += PageOffset;

munmap((void*)PageAddress, Size); }

// To access your registers, you would do something like this: main() { int i; volatile unsigned short *leds;

leds = (unsigned short*)mmio_map(0x42000000, sizeof(*leds));

if (!leds) { perror("mmap"); exit(1); }

for (i=0; i < 5; i++) { *leds = 0xa5ff; /* Alternate leds on from 0 */ sleep(1); *leds = 0x5aff; /* Alternate leds on from 1 */ }

mmio_unmap((void*)leds, sizeof(*leds)); }

Which works to an extent but I have two issues I don't understand. Firstly I get an "Illegal instruction" message with each run. I put in some comments and the message comes at the end of execution (no particular instruction). Secondly the "leds" get set to each configuration only once rather than switching back and forth as I would expect from the loop. I tried putting in an msync (MS_SYNC flag) after each setting of "leds" but it made no difference. Why doesn't the memory map update with each change?

BTW thanks for the help,

Reply to
James Kimble

Thanks Juergen. Anything else that I should lookout for when accessing mmaped space ? I have come up with one HW design flaw where some of the registers on the chip are middle-endian while others are little-endian. BTW I am using MPC8349 in little-endian mode with PowerPC arch type (not PPC). And it works as expected so far. Does this re-ordering happen with optimized compiler options or is this HW architecture cache lining caveat ? As you may be aware of the MPC8349 uses the e300 core and its has a beast of a doc.

Reply to
Janaka

  1. You should munmap the exact memory space (Virtual address and size) that you Mmaped. So I would guess that the illigal instruction happens when you try to munmap it.
  2. Note that in your loop, you set the leds to 0x5aff and immidiately set it to 0xa5ff on the next loop cycle. What this would mean is you would not see one of the LED states when the loop is run (too quick for your eye). Put another sleep(1) after "*leds = 0x5aff; /* Alternate leds on from 1 */" line. Note that depending on how the LEDs are wired and how the registers are setup the actual LED state could be ON for a 0 and OFF for a 1.
Reply to
Janaka

That's the problem. This reordering happens in hardware. So the compiler has

*no* effect on it. But I'm not sure if it happens on all PPC cores. Its easy to understand: A read access would stop program execution, while a write access can be done in background later while program execution continues. This increases the execution speed when you are working with regular RAM (with or without cache). But could crash, when there is no RAM and hardware registers instead. As the CPU works with the same mnemonics, it cannot differentiate if it talks to RAM or hardware. Same is done on x86: But there are chipset related register bits to set, that forbits reordering in *some* address areas. Don't know if something like this also exists on PPC/PowerPC.

You need the "sync" command between all hardware access commands. The "volatile" keeps command order in assembler in sync with your C code and the "sync" command keeps assembler command order and execution order in sync. Very easy ;-)

JB

Reply to
Juergen Beisert

Thanks so much! This has been a huge help.

Reply to
James Kimble

Thanks and cheers.

Reply to
Janaka

This is a follow on question to the above. The program described above is a user space program. Of course because I'm working directly with hardware this should be done as a driver. My question is how do I access /dev/mem from user space? Every example driver I've seen requires the user space program to open the device in order to call the driver functions on that device. But a normal user cannot open /dev/mem. Do I need a device pointer (sort of like / dev/modem and /dev/Stty) that has general user access?

I'm really looking for the best way to approach this. I've never written anything but a trivial driver and I want this to be as efficient as possible. (Yes, I've got the Rubini book on order....).

Thanks for any input you can give me..

Reply to
James Kimble

This is not always true. Hardware abstraction is always a good idea. But if you only need to set some bits at startup or when the system is already up and running it could make sense to use this userspace way.

? man open mmap close ?

Yes, you need to be root to open /dev/mem. As you have access to almost everything in your system this device is restricted to root only. Note: You can destroy everything in your system, when you are using /dev/mem in a wrong way!

Take a look into the UIO (=user I/O) framework, coming with the fresh 2.6.23 kernel. With the help of this framework you can write your own trivial (=very small) kernel driver that restrict access to a specific piece of hardware (it works like /dev/mem but for the specific piece of hardware only). The advantage is, you can continue to handle this piece of hardware from userland, but any misuse is restricted to this piece of hardware instead of the whole system. And with the help of "udev" you can create the permissions for this device as you need it in your application.

Refer Documentation/DocBook/uio-howto.tmpl for the UI framework.

JB

Reply to
Juergen Beisert

re

in

the

ther

rce

re

g

has

AM

in

User space program look easier for programming, but do it in a device driver seems more reasonable. Even you don't have to use mmap in your user space progrem.

Write a simple device driver to access hardware directly. "ioremap" can be used to map physical address to kernel virtual address space, then you use readb/w/l and writeb/w/l to read/write such address. In user space, you write simple program to cooperate with your device driver, ioctl system call is enough for most of scenario.

Thanks, Marco Wang

Reply to
Marco Wang

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.