To access BRAM (or any other memory mapped device) in PetaLinux, I wrote myself some helper functions.
Some background: I created a VHDL design which connects two BRAM blocks to two 12bit DACs on the one side and to the CPU on the other. The data inside the BRAM is padded to 16bit.
bram_access.h
#ifndef BRAM_ACCESS_H_
#define BRAM_ACCESS_H_
#include <stdio.h>
#include <sys/mman.h> //mmap and munmap
#include <fcntl.h> //options for open
#include <unistd.h> // close, getpagesize
#include <string.h> // memcpy
#include <stdbool.h> // boolean logic
#include <stdint.h> //uint16_t
#include <errno.h> //get errno
#define ENABLE_DEBUG 0 // if 1, write additional info to LOG_MSG
#define DAC_MASK 0x0FFF // 12lower bits set
#define dac_value uint16_t //using a unsiged 16bit to represent one dac value
#define DAC_VALUE_NUMBER 8192u //size of bram in dac_value slots
#define _BRAM_SIZE sizeof(dac_value) * DAC_VALUE_NUMBER
//size of bram in bytes
//16384u but better to have it set automatically
//memory offsets for each channel
//actual values are defined in the .c file
extern const off_t channel_offset[];
enum Status //return value enum
{
STATUS_OK,
STATUS_OPENING_DEV_MEM_FAILED,
STATUS_MAPPING_FAILED,
STATUS_UNMAPPING_FAILED,
STATUS_CLOSING_DEV_MEM_FAILED,
STATUS_COPYING_DATA_FAILED,
STATUS_MSYNC_FAILED
};
//make Status human readable
const char* getStatus(enum Status status);
void print_if_err(enum Status status);
//functions to bind and unbind memory of a physical address
//to an address in the address space of the program via /dev/mem
//I am using volatile here to make sure that writes and read
//to the memory address actually happen and are not optimized away
int map(volatile void **ptr, size_t size, int prot, off_t offset);
int unmap(volatile void **ptr, unsigned size);
//helper functions to make gpio and bram bind/unbind a bit easier
int bind_gpio(volatile uint32_t **ptr);
int unbind_gpio(volatile uint32_t **ptr);
int bind_bram(volatile dac_value **ptr, uint8_t channel);
int unbind_bram(volatile dac_value **ptr);
#endif /* BRAM_ACCESS_H_ */
bram_access.c
#include "bram_access.h" //dac access via bram and /dev/mem
const off_t channel_offset[] = {0x40000000u, 0x40020000u};
//addresses need to match the vhdl, check if they are correct!
//debugging enable/disable at compile time
#if ENABLE_DEBUG
#define LOG_MSG printf
#else
#define LOG_MSG(...)
#endif
//status enum to human readable
const char* getStatus(enum Status status)
{
switch (status){
case STATUS_OK: return "OK";
case STATUS_OPENING_DEV_MEM_FAILED: return "OPENING_DEV_MEM_FAILED";
case STATUS_MAPPING_FAILED: return "MAPPING_FAILED";
case STATUS_UNMAPPING_FAILED: return "UNMAPPING_FAILED";
case STATUS_CLOSING_DEV_MEM_FAILED: return "CLOSING_DEV_MEM_FAILED";
case STATUS_COPYING_DATA_FAILED: return "COPYING_DATA_FAILED";
case STATUS_MSYNC_FAILED: return "MSYNC_FAILED";
default: return "UNDEFINED";
}
}
//print if an error occured
void print_if_err(enum Status status){
if(!status == STATUS_OK){
printf(getStatus(status));
}
}
//map a physical memory address to the process local memory utilizing /dev/mem
//**ptr is a pointer to the pointer which is suppoed to be bound to the physical memory
//size is the size in bytes
//prot is the mode (read,write,execute) of the physical memory
//offset is the phyiscal memory address to be mapped
int map(volatile void **ptr, size_t size, int prot, off_t offset){
LOG_MSG("Calling map\n");
int fd;
fd = open("/dev/mem", O_RDWR);
if(fd < 1) return STATUS_OPENING_DEV_MEM_FAILED;
LOG_MSG("/dev/mem opened\n");
*ptr = (volatile void *) mmap(NULL, size, prot, MAP_SHARED, fd, offset);
if(*ptr == MAP_FAILED) return STATUS_MAPPING_FAILED;
LOG_MSG("mapping successful\n");
if(!close(fd)){//we can close the file descriptor as mmap keeps it automatically open until we munmap
LOG_MSG("file descriptor closed\n");
return STATUS_OK;
}else{
return STATUS_CLOSING_DEV_MEM_FAILED;
}
}
//unmap physical memory from the process local memory
int unmap(volatile void **ptr, size_t size){
LOG_MSG("Calling unmap\n");
if(!(munmap(*((void **)ptr), size) == 0)){
return STATUS_UNMAPPING_FAILED;
}
LOG_MSG("unmapping worked\n");
*ptr = NULL;
return STATUS_OK;
}
//helper functions to make using map and unmap easier
int bind_gpio(volatile uint32_t **ptr){
unsigned offset = 0x41200000; //fixed offset of the gpio
//we allocate one pagesize as this is the minimum
return map((volatile void **) ptr, getpagesize(), PROT_READ|PROT_WRITE, offset);
}
int unbind_gpio(volatile uint32_t **ptr){
return unmap((volatile void **) ptr, getpagesize());
}
int bind_bram(volatile dac_value **ptr, uint8_t channel){
LOG_MSG("binding bram channel %d\n", channel);
return map((volatile void **) ptr, _BRAM_SIZE, PROT_READ|PROT_WRITE, channel_offset[channel]);
}
int unbind_bram(volatile dac_value **ptr){
LOG_MSG("unbinding bram\n");
return unmap((volatile void **) ptr, _BRAM_SIZE);
}
main.c
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
#include <stdbool.h>
#include "bram_access.h"
//example program:
//- assumes two bram blocks
//- in my vhdl design, these bram blocks
// are used as lookup tables for a DAC
// hence the name of the variables
int main(int const argc, char const * const * const argv){
//pointers to bram
volatile dac_value *ch0, *ch1;
//bind pointers to the bram
//also check for errors
print_if_err(bind_bram(&ch0, 0));
print_if_err(bind_bram(&ch1, 1));
//write a constant value to one channel
//write a ramp to other
uint16_t i=0;
for(i=0; i< DAC_VALUE_NUMBER; i++){
ch0[i] = 4000;
ch1[i] = i;
}
//disconnect the pointers from the bram
print_if_err(unbind_bram(&ch0));
print_if_err(unbind_bram(&ch1));
return 0;
}