Writing to a device at a known physical address

Do you have a question? Post it now! No Registration Necessary

Translate This Thread From English to

Threaded View

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!!

Re: Writing to a device at a known physical address

Quoted text here. Click to load it

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


Re: Writing to a device at a known physical address
Quoted text here. Click to load it

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

Re: Writing to a device at a known physical address
Quoted text here. Click to load it

Full man can be found at
http://linux.die.net/man/3/mmap

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 <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>


//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;
}

Re: Writing to a device at a known physical address
Quoted text here. Click to load it
instead,
Quoted text here. Click to load it
strerror(errno));
Quoted text here. Click to load it

That helps!! Thanks a ton!!

Re: Writing to a device at a known physical address
Quoted text here. Click to load it

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-<your-arch>/io.h). Its more save, more portable and the
source is easier to read.

JB

Re: Writing to a device at a known physical address
Quoted text here. Click to load it


I worked with this simple example:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>


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,


Re: Writing to a device at a known physical address
Quoted text here. Click to load it

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.

Re: Writing to a device at a known physical address
Quoted text here. Click to load it

Thanks so much! This has been a huge help.

Re: Writing to a device at a known physical address
Quoted text here. Click to load it

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.

Re: Writing to a device at a known physical address
Hi Janaka,

Quoted text here. Click to load it

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

Re: Writing to a device at a known physical address
Quoted text here. Click to load it

Thanks and cheers.

Re: Writing to a device at a known physical address
Quoted text here. Click to load it

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..

Re: Writing to a device at a known physical address
James,

Quoted text here. Click to load it

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.

Quoted text here. Click to load it

?
man open mmap close ?

Quoted text here. Click to load it

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!

Quoted text here. Click to load it

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


Re: Writing to a device at a known physical address
Quoted text here. Click to load it


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

Site Timeline