How to read Raspberry Pi board revision and memory size

As I mentioned yesterday, I now have some new Raspberry Pi boards to play with, and also some extra hardware. I had planned to spend this evening writing some GPIO code to control the colours and brightness of the LEDborg. I even got as far as plugging it in to one of the boards. Then I read the instructions. It seems that the piborg team chose to drive the LED using a GPIO pin which has changed its function between board revisions. This has resulted in a confusing mess of drivers and code.

I did not want to get into writing code which is specific to one of my Pi boards, so instead I set off on a quest to find out how to determine the raspberry Pi board revision at runtime from a bare metal program.

This turned out to be much more tricky than I expected. I found plenty of example code in both C and Python for determining the board revision, but on closer inspection the great majority of them were just delegating the hard work to Linux, usually by reading a pseudo file. Obviously, this kind of facility is not available to a bare metal program, so I had to look further. Eventually, I found a document describing the "mailbox" interface which seems to be the way for user-written software to talk to the hardware. I recalled that the version of the screen framebuffer initialization code which I had eventually got working used these mailboxes so I took a bit of inspriation from there and started coding my own.

Of course, it didn't work straight away, so I started by using the same morse code debugging that I had used before, but very quickly got fed up with that. I decided I needed more information, so I opted to use the UART cable which I had now proved working, but this in turn meant writing some software to use it. For simplicity, I based my code on uart01 from dwelch67. His later versions seem smarter, providing such delights as input and interrupt handling. But that can come later - today I just needed a way to show debugging messages. I stuffed the serial code in with the GPIO code to control the OK LED, for now. What I ended up looks like:

gpio.h

#ifndef GPIO_H
#define GPIO_H

#include <stdint.h>

void gpio_init();

void raspi_okled_set(int state);
void raspi_timer_wait(int usec);
void raspi_mini_uart_send_char(char c);
void raspi_mini_uart_send_string(const char* s);
void raspi_mini_uart_send_hex(uint32_t n);
void raspi_mini_uart_send_newline(void);

#endif

gpio.c

void raspi_mini_uart_init() {
    PUT32(AUX_ENABLES,1);
    PUT32(AUX_MU_IER_REG,0);
    PUT32(AUX_MU_CNTL_REG,0);
    PUT32(AUX_MU_LCR_REG,3);
    PUT32(AUX_MU_MCR_REG,0);
    PUT32(AUX_MU_IER_REG,0);
    PUT32(AUX_MU_IIR_REG,0xC6);
    PUT32(AUX_MU_BAUD_REG,270);

    uint32_t ra = GET32(GPFSEL1);
    ra &= ~(7<<12); //gpio14
    ra |= 2<<12;    //alt5
    PUT32(GPFSEL1,ra);

    PUT32(GPPUD,0);
    for(ra=0; ra<150; ra++) dummy(ra);
    PUT32(GPPUDCLK0,(1<<14));
    for(ra=0;ra<150;ra++) dummy(ra);
    PUT32(GPPUDCLK0,0);

    PUT32(AUX_MU_CNTL_REG,2);
}

void raspi_mini_uart_wait_for_write() {
  while(1) {
    if (GET32(AUX_MU_LSR_REG)&0x20) break;
  }
}

void raspi_mini_uart_write_char(char c) {
    PUT32(AUX_MU_IO_REG, c);
}

void raspi_mini_uart_send_char(char c) {
  raspi_mini_uart_wait_for_write();
  raspi_mini_uart_write_char(c);
}

void raspi_mini_uart_send_string(const char* s) {
  while (*s) {
    raspi_mini_uart_send_char(*s++);
  }
}

const char* hex_chars = "0123456789ABCDEF";

void raspi_mini_uart_send_hex(uint32_t n) {
  char hex[8+1] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
  for (int i = 7; i >= 0; --i) {
    hex[i] = hex_chars[n & 0xF];
    n >>= 4;
  }
  raspi_mini_uart_send_string(hex);
}

void raspi_mini_uart_send_newline(void) {
  raspi_mini_uart_send_char('\r');
  raspi_mini_uart_send_char('\n');
}

void gpio_init() {
  raspi_okled_init();
  raspi_mini_uart_init();
}

With that yak successfully shaved I was able to go back to trying to request and extract the mailbox data. Printing information to the mini UART was very useful, I was able to get much more information on my terminal screen than I would have been able to do with just flashing a light:

uart-debug-cropped

Once I had used this technique to get to the bottom of my problems (mostly some misunderstandings about lengths in bytes rather than 32-bit words) I was able to fetch and print out a few of the values, including board revision and memory sizes:

mailboxdata-cropped

At the moment all the code is still lumped in with "main" while I work on it, and it also takes a bit of a short-cut by only supporting a single tag per mailbox request, but those can be fixed another day. Now I need to finish this post and get some sleep.

There is one oddity in this code. The warmup() function serves no purpose other than to give me time to start up a serial terminal session after plugging the raspberry Pi uart lead into the USB socket on my development computer. It is somewhat annoying that every time I unplug the cable, the serial port "vanishes" and the terminal session just ends. This is especially annoying because as soon as I plug the serial cable in, the Pi boots up and starts running my code. I'm hoping that I will be able to power-cycle the Pi by just putting a make/break switch into the 3V line at the raspberry Pi end, but that's also another project for another day.

main.c

#include "gpio.h"
#include "morse.h"
#include "mailbox.h"

/* Use some free memory in the area below the kernel/stack, 16-byte aligned */
#define BUFFER_ADDRESS 0x1000

#define RR_REQUEST 0x00000000
#define RR_RESPONSE_OK 0x80000000
#define RR_RESPONSE_ERROR 0x80000001

#define SLOT_OVERALL_LENGTH 0
#define SLOT_RR 1
#define SLOT_TAGSTART 2

#define SLOT_TAG_ID 0
#define SLOT_TAG_BUFLEN 1
#define SLOT_TAG_DATALEN 2
#define SLOT_TAG_DATA 3

#define MBOX_HEADER_LENGTH 2
#define TAG_HEADER_LENGTH 3

#define MBX_DEVICE_SDCARD 0x00000000
#define MBX_DEVICE_UART0 0x00000001
#define MBX_DEVICE_UART1 0x00000002
#define MBX_DEVICE_USBHCD 0x00000003
#define MBX_DEVICE_I2C0 0x00000004
#define MBX_DEVICE_I2C1 0x00000005
#define MBX_DEVICE_I2C2 0x00000006
#define MBX_DEVICE_SPI 0x00000007
#define MBX_DEVICE_CCP2TX 0x00000008

#define MBX_TAG_GET_FIRMWARE 0x00000001 /* in 0, out 4 */
#define MBX_TAG_GET_BOARD_MODEL 0x00010001 /* in 0, out 4 */
#define MBX_TAG_GET_BOARD_REVISION 0x00010002 /* in 0, out 4 */
#define MBX_TAG_GET_MAC_ADDRESS 0x00010003 /* in 0, out 6 */
#define MBX_TAG_GET_BOARD_SERIAL 0x00010004 /* in 0, out 8 */
#define MBX_TAG_GET_ARM_MEMORY 0x00010005 /* in 0, out 8 (4 -> base addr, 4 -> len in bytes) */
#define MBX_TAG_GET_VC_MEMORY 0x00010006 /* in 0, out 8 (4 -> base addr, 4 -> len in bytes) */
#define MBX_TAG_GET_COMMANDLINE 0x00050001 /* in 0, out variable */
#define MBX_TAG_GET_DMA_CHANNELS 0x00060001 /* in 0, out 4 */

#define MBX_TAG_GET_POWER_STATE 0x00020001 /* in 4 -> dev id, out 8 (4 -> device, 4 -> status) */
#define MBX_TAG_GET_TIMING 0x00020002 /* in 0, out 4 */
#define MBX_TAG_GET_FIRMWARE 0x00000001 /* in 0, out 4 */

// there are a load more, see https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface

// TODO support more than one tag in buffer
void add_mailbox_tag(volatile uint32_t* buffer, uint32_t tag, uint32_t buflen, uint32_t len, uint32_t* data) {
  volatile uint32_t* start = buffer + SLOT_TAGSTART;
  start[SLOT_TAG_ID] = tag;
  start[SLOT_TAG_BUFLEN] = buflen;
  start[SLOT_TAG_DATALEN] = len & 0x7FFFFFFF;

  uint32_t bufwords = buflen >> 2;

  if (0 == data) {
    for (int i = 0; i < bufwords; ++i) {
      start[SLOT_TAG_DATA + i] = 0;
    }
  } else {
    for (int i = 0; i < bufwords; ++i) {
      start[SLOT_TAG_DATA + i] = data[i];
    }
  }

  start[SLOT_TAG_DATA+bufwords] = 0; // end of tags, unless overwritten later
}

// TODO support more than one tag in buffer
void build_mailbox_request(volatile uint32_t* buffer) {
  uint32_t tag_length = buffer[MBOX_HEADER_LENGTH + SLOT_TAG_BUFLEN];
  uint32_t end = (MBOX_HEADER_LENGTH*4) + (TAG_HEADER_LENGTH*4) + tag_length;
  uint32_t overall_length = end + 4;
  buffer[SLOT_OVERALL_LENGTH] = overall_length;
  buffer[SLOT_RR] = RR_REQUEST;
}

volatile uint32_t *mailbuffer = (uint32_t *) BUFFER_ADDRESS;

void dump_parameter(const char* name, uint32_t value) {
  raspi_mini_uart_send_string(" ");
  raspi_mini_uart_send_string(name);
  raspi_mini_uart_send_string(": ");
  raspi_mini_uart_send_hex(value);
  raspi_mini_uart_send_newline();
}

void dump_mailbox_to_uart() {
  raspi_mini_uart_send_string("Mailbox[");
  raspi_mini_uart_send_newline();
  dump_parameter("mailbox length", mailbuffer[0]);
  dump_parameter("mailbox rr", mailbuffer[1]);
  int i = 2;
  while (mailbuffer[i] > 0) {
    dump_parameter("tag id", mailbuffer[i++]);
    uint32_t buflen = mailbuffer[i++];
    dump_parameter("tag buffez", buflen);
    dump_parameter("tag len", mailbuffer[i++]);

    dump_parameter("tag data(w0)", mailbuffer[i]);
    i += buflen >>2;
  }
  raspi_mini_uart_send_string("]");
  raspi_mini_uart_send_newline();
}

void warmup(void) {
  for (int i = 3; i > 0; --i) {
    morse_char('a');
    raspi_mini_uart_send_char('0'+i);
  }
  raspi_mini_uart_send_newline();
}

void dump_response(const char* name, int nwords) {
  raspi_mini_uart_send_string(name);
  raspi_mini_uart_send_string(": ");
  for (int i = 0; i < nwords; ++i) {
    uint32_t value = mailbuffer[MBOX_HEADER_LENGTH + TAG_HEADER_LENGTH + i];
    raspi_mini_uart_send_hex(value);
    raspi_mini_uart_send_char(' ');
  }
  raspi_mini_uart_send_newline();
}

void print_parameter(const char* name, uint32_t tag, int nwords) {
  add_mailbox_tag(mailbuffer, tag, nwords * 4, 0, 0);
  build_mailbox_request(mailbuffer);

//  raspi_mini_uart_send_string("before:");
//  raspi_mini_uart_send_newline();
//  dump_mailbox_to_uart();

  writemailbox(8, (uint32_t)mailbuffer);
  readmailbox(8);

  /* Valid response in data structure */
  if(mailbuffer[1] != 0x80000000) {
    raspi_mini_uart_send_string("error:");
    raspi_mini_uart_send_newline();
    dump_mailbox_to_uart();
  } else {
    dump_response(name, nwords);
  }
}

int main(void) {
  gpio_init();

  warmup();

  print_parameter("firmware", MBX_TAG_GET_FIRMWARE, 1);
  print_parameter("board model", MBX_TAG_GET_BOARD_MODEL, 1);
  print_parameter("board rev", MBX_TAG_GET_BOARD_REVISION, 1);
  print_parameter("mac address", MBX_TAG_GET_MAC_ADDRESS, 2);
  print_parameter("board serial", MBX_TAG_GET_BOARD_SERIAL, 2);
  print_parameter("arm mem", MBX_TAG_GET_ARM_MEMORY, 2);
  print_parameter("vc mem", MBX_TAG_GET_VC_MEMORY, 2);

  halt("done ");
}

If you want to play with any of this code, feel free to fork the git repository at https://github.com/raspberry-alpha-omega/corn-mainline.

4 Comments

  1. Pingback: Automatic Raspberry Pi board revision detection: model A, B1 and B2 | Raspberry Alpha Omega

  2. The warmup() function serves no purpose other than to give me time to start up a serial terminal session after plugging the raspberry Pi uart lead into the USB socket on my development computer. This is especially annoying because as soon as I plug the serial cable in, the Pi boots up and starts running my code.
    So you’re using the 5V out from the USB to serial cable to power your Pi? That’s not recommended as the Pi may try to draw more current than the 500mA that USB (theoretically, at least) is limited to. Much better to only connect GND, RX and TX from the USB to serial adaptor to your Pi, then you don’t need to unplug anything and can leave your serial terminal session constantly running :)

    I’m hoping that I will be able to power-cycle the Pi by just putting a make/break switch into the 3V line at the raspberry Pi end, but that’s also another project for another day.
    The way I solved the power on/off problem was to use plug my mains MicroUSB power adaptor into one of those remote-control power sockets you can get (e.g. http://www.maplin.co.uk/remote-controlled-mains-socket-set-531547 ) and that way I can switch on/off my Pi with a (wireless) button press, no need to plug/unplug anything. Very handy!
    Alternatively there’s http://raspi.tv/2012/making-a-reset-switch-for-your-rev-2-raspberry-pi which does a hardware-level reset i.e. still works even if your Pi has locked up.

    • There’s also been some smarter options produced since I wrote this. I have on my desk at the moment a kit for a PiSupply, which I plan to assemble and blog when I get the chance. This is supposed to be clever enough to startup and shutdown the Pi more cleanly than just yanking the power lead!

Leave a Reply

Your email address will not be published. Required fields are marked *