29

How does memory-mapped I/O addressing work?

I'm trying to understand a sample supplied I2S: Anyone got it running?.

Configuring Clocks:

#define BCM2708_PERI_BASE        0x20000000
#define CLOCK_BASE               (BCM2708_PERI_BASE + 0x101000) /* Clocks */

It first maps the code like so...

clk_map = (unsigned char *)mmap(
      (caddr_t)clk_mem,
      MAP_BLOCK_SIZE,
      PROT_READ|PROT_WRITE,
      MAP_SHARED|MAP_FIXED,
      mem_fd,
      CLOCK_BASE
   );

Then it does something...

 // Always use volatile pointer!
   clk = (volatile unsigned *)clk_map;

And when it's referenced there's these odd addtions of 0x26 & 0x27, what's that about?

 printf("Disabling I2S clock\n");
 *(clk+0x26) = 0x5A000000;
 *(clk+0x27) = 0x5A000000;

 usleep(10);

 printf("Confiure I2S clock\n");
 *(clk+0x26) = 0x5A000001;
 *(clk+0x27) = 0x5A000000 | 3<<12 | 1<<9; // divider: 3.125==0b11.001

 usleep(10);
 printf("Enabling I2S clock\n");
 *(clk+0x26) = 0x5A000011;

Looking at the datasheet, I can see where they've got some of these values, like the base address, but I'm struggling to understand the others. Where is that CLOCK_BASE determined and what's going on?

Peter Mortensen
  • 1,984
  • 2
  • 14
  • 17
Dog Ears
  • 1,937
  • 6
  • 18
  • 27
  • 1
    This is probably best suited for StackOverflow. Although it relates to the RPi You'll be more likely to get answers to programming questions there. – Jivings Jun 22 '12 at 07:59
  • 4
    Maybe, but I feel it's a more a general Pi programming related question combining the interpretation of the datasheet and the Pi hardware. let see if it gets some good info. – Dog Ears Jun 22 '12 at 08:05
  • Okay. Let's see how it goes :) – Jivings Jun 22 '12 at 08:09
  • 1
    I don't think this would do too well on Stack Overflow - it's pretty specialist and would likely get more expert attention here. – Flexo Jun 23 '12 at 15:32

2 Answers2

18

On a computer you write to a specified 'memory address'. This address is recognised by the system as a hardware address, and the appropriate hardware receives or sends the appropriate value.

Most hardware systems have many different registers that can be set or read. Some might have a few, some might have many. These registers will be grouped into a continuous range. A base pointer points to the first in the range, and you write to, for example, the second port with base_pointer+1. You don't have to, you could write direct to a pointer, but using an offset makes things easier to work with.

The Raspberry Pi recognises a massive range of hardware registers at the address 0x20000000. A range of registers that control clock systems are accessed from BCM2708_PERI_BASE + 0x101000. The registers that control the I2S clock are the 38th and 39th register in that block, written to using  BCM2708_PERI_BASE + 0x101000 + 0x26 and 0x27

You can't just change the clock values though, you have to disable the clock, change the values and restart it.

If this answer is too basic, my apologies. In which case your question is really hardcore, good luck. You might find this link helpful

Update: Why use mmap and not write directly to memory?

When a program is running the memory addresses it thinks it has aren't real addresses, they are mapped to real addresses by the memory manager. This stops one program from being able to affect another. Two processes can read and write to their own address 1234 perfectly happily, and the memory manager will keep the two locations completely separate.

Hardware ports, however, are at absolute physical addresses. But you cant write to them directly because the memory manager will take your address and map it to your personal memory area.

On Linux /dev/mem is a 'character device file that is an image of the main memory of the computer'

If you open this like a file then you can read and write to it like a file. In the supplied sample mem_fd is a file handle that resulted from opening /dev/mem

Another system that can make life much easier is the ability to map a file to memory and write to it like memory. So if you have a file in which you want to read or write different specific bits then instead of moving the file pointer backwards and forwards, you can map it to a location in memory, and then write to it directly as if it was memory.

So in this sample the code is creating a handle to physical memory, as though it were a file on disk, and then asking the system to treat it as though it was memory. A bit convoluted, but necessary in order to get round the virtual memory manager and write to an actual physical address. The value 0x20000000, it seems, is a bit of a red herring. The code is proposing this address as a hint, the system does not have to map /dev/mem here, though it probably does. Normally the value null would be passed, and the system would map the file handle to whatever address it thought best.

Now the physical memory is mapped to the processes virtual memory, and reads and writes go where you expect.

References:

http://www.kernel.org/doc/man-pages/online/pages/man2/mmap.2.html

http://www.raspberrypi.org/phpBB3/viewtopic.php?f=44&t=8496&p=104359

https://superuser.com/questions/71389/what-is-dev-mem

David Sykes
  • 1,395
  • 3
  • 18
  • 28
  • I still have a couple of questions: Why do they mmap? Why not just access the memory directly? – Alex Chamberlain Jun 25 '12 at 13:09
  • @AlexChamberlain Because the code runs on linux, so you can't access memory directly as each process gets their own virtual memory space. However one can open and mmap /dev/mem to gain direct access to the physical memory – nos Jan 07 '14 at 15:08
1

@AlexChamberlain this is due to the OS structure. You can go without mmap but the paging is declared, hence no direct access. In kernel mode you can go without mmap, by, for example inserting your driver as kernel module with no need for mmap. Also, in simplest OS case, where no page table memory is used you can access without mmap either, by ie. direct physical address access.

SlySven
  • 3,581
  • 1
  • 15
  • 44