Commit 2aa44996 authored by Jeffrey Lee's avatar Jeffrey Lee

Implement BCMSupport module

Detail:
  The BCMSupport module provides the following functionality:
  * A SWI interface which allows multiple clients to send and receive messages via the ARM<->GPU mailbox
  * A higher-level SWI interface to make dealing with the mailbox property interface easier
  * A CPU clock device for controlling the ARM clock rate and measuring the SoC temperature
  The implementation is mostly in C and aims to be thread/multicore safe
Admin:
  Tested on Raspberry Pi 1B/2B/3B


Version 0.03. Tagged as 'BCMSupport-0_03'
parent 8ed481ce
hdr/** gitlab-language=armasm linguist-language=armasm linguist-detectable=true
s/** gitlab-language=armasm linguist-language=armasm linguist-detectable=true
c/** gitlab-language=c linguist-language=c linguist-detectable=true
h/** gitlab-language=c linguist-language=c linguist-detectable=true
cmhg/** gitlab-language=cmhg linguist-language=cmhg linguist-detectable=true
BCMSupport specification
------------------------
The primary purpose of the BCMSupport module is to provide shared access to the
mailbox channels that are used for communication between the ARM and the
VideoCore GPU.
For the low-level details of this interface, see:
https://github.com/raspberrypi/firmware/wiki/Mailboxes
BCMSupport exposes the mailboxes via the following SWI interface:
BCMSupport_SendMBMessage
(SWI &591C0)
In: R0 = Mailbox channel (bits 0-3) and data (bits 4-31)
R1 = Flags:
Bit 0:
0 -> Block and wait for response
1 -> Don't block, call callback specified in R4-R6 on receipt of
response
Bit 1:
0 -> Block if the send buffer is full
1 -> Return error if the send buffer is full
R2 = Response mask
R3 = Response value
If R1 bit 0 is set:
R4 = Response callback R0
R5 = Response callback R12
R6 = Response callback function pointer
Out: All registers preserved
If bit 0 is clear, and a response is expected (R2 != 0), R0 = response word
Error on failure to send
This is the lowest-level interface for sending messages via the mailbox.
The word provided in R0 is written directly to the send channel. The values of
R2 and R3 are used to identify the response in the receive channel, using the
following logic:
if ((response AND R2) == R3)
{
// this is my response
}
else
{
// this is not my response
}
Specifying a response mask of 0 indicates that no response is expected.
Each sent message which is expecting a response is pushed onto a queue. When a
response is received from the GPU the queue is searched in FIFO order until a
match is found. By correctly setting the response mask and value in R2 and R3
this allows the interface to cope with mailbox channels which only respond to
messages in-order (e.g. channels 0 and 1) versus those that reply out-of-order
(e.g. channel 8)
If the call is operating in a blocking manner (R1 bit 0 or bit 1 are unset)
then it must be called with interrupts enabled.
Any response which fails to match against an entry in the expected-responses
list will be discarded.
Callback functions are called as follows:
In: R0 = R4 value from send SWI
R1 = Response word
R12 = R5 value from send SWI
CPU in privileged mode
Interrupts disabled
Out: R0-R3, R12 can be corrupted
The callback function pointer can also be null, which is taken as an indication
that the response should be ignored.
Callback functions are called directly from the mailbox interrupt handler, and
so must be quick and must not enable interrupts. Sending further messages from
within a callback is discouraged (non-blocking sends are possible via making
sure bits 0 and 1 are set, but because there is no guarantee the send will
succeed the ability is of limited use)
BCMSupport_MBSync
(SWI &591C1)
In: R0 = Flags:
Bit 0: 1 -> Sync based on response mask/value
Bit 1: 1 -> Sync based on callback R0
Bit 2: 1 -> Sync based on callback R12
Bit 3: 1 -> Sync based on callback pointer
R1 = Response mask
R2 = Response value
R3 = Callback R0
R4 = Callback R12
R5 = Callback function pointer
Out: All registers preserved
This SWI can be used to block until all responses (of a given type) have been
received. This can be used, e.g. during your modules finalisation sequence to
make sure that all pending responses have been received.
For each flag specified in R0, all the criteria must match for BCMSupport to
consider the pending response to be one that it needs to block waiting for.
Entries which have a null callback function pointer can still match on the R0
and R12 values, allowing them to be used as unique tokens by your code.
To block for receipt of all messages, you can set bit 0 of R0 and set both R1
and R2 to zero.
This SWI is only guaranteed to block for the receipt of messages which were
sent prior to the SWI being called. New messages which are sent after the SWI
has begun executing may not be waited for.
This SWI must not be used from within one of the message callback functions.
BCMSupport_AllocPropertyBuffer
(SWI &591C2)
In: R0 = Required length
Out: R0 -> Buffer (16 byte aligned)
This SWI is used to allocate a physically contiguous buffer for use with the
mailbox property interface.
BCMSupport_FreePropertyBuffer
(SWI &591C3)
In: R0 -> Buffer
Out: All registers preserved
This SWI is used to free a buffer that was previously allocated by
BCMSupport_AllocPropertyBuffer.
BCMSupport_SendPropertyBuffer
(SWI &591C4)
In: R0 -> Buffer (16 byte aligned)
R1 = Flags:
Bit 0:
0 -> Block and wait for response
1 -> Don't block, call callback specified in R2-R4 on receipt of
response
Bit 1:
0 -> Block if the send buffer is full
1 -> Return error if the send buffer is full
If R1 bit 0 is set:
R2 = Response callback R0
R3 = Response callback R12
R4 = Response callback function pointer
Out: All registers preserved
Error on failure to send
This SWI is used to send a buffer via the mailbox property interface. The
buffer must be filled with a sequence of tagged messages as described here:
https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface
The buffer pointer must be at least 16 byte aligned, and located in a region of
non-cacheable, physically contiguous memory. BCMSupport_AllocPropertyBuffer can
be used to allocate a buffer which meets these requirements. There is no
requirement for the caller to use memory barriers after writing the request to
the buffer or before reading the response; BCMSupport will perform the correct
barrier operations on your behalf.
Care must be taken to ensure that the buffer is not freed or used for another
purpose while the GPU is processing the contents.
Callback functions are called as follows:
In: R0 = R2 value from send SWI
R1 = Buffer pointer (R0 value from send SWI)
R12 = R3 value from send SWI
CPU in privileged mode
Interrupts disabled
Out: R0-R3, R12 can be corrupted
Unlike BCMSupport_SendMBMessage, it is not possible to request an async
operation with a null callback. This is because there will be no way for you to
know when the buffer is free for re-use. It is also recommended that once an
operation has completed you check the buffer for any received error codes.
Callback functions are called directly from the mailbox interrupt handler, and
so must be quick and must not enable interrupts. Sending further messages from
within a callback is discouraged (non-blocking sends are possible via making
sure bits 0 and 1 are set, but because there is no guarantee the send will
succeed the ability is of limited use)
BCMSupport_SendTempPropertyBuffer
(SWI &591C5)
In: R0 -> Input buffer
R1 -> Output buffer
R2 = Flags (reserved)
Out: All registers preserved
Error on failure to send
This is a simplified version of BCMSupport_SendPropertyBuffer that does not
require the caller to preallocate a physically contiguous buffer. Instead,
ordinary RAM can be used for the input and output. BCMSupport will take care of
copying the input to a temporary buffer, sending the message, and then copying
the output to your output buffer. In most cases a pre-allocated temporary
buffer will be used, but if the tag sequence is too long or all the
preallocated buffers are in use then an attempt will be made to (temporarily)
allocate a new buffer from the heap.
There is no async option with this call, and it will always block until there
is enough mailbox space available.
The input and output buffer pointers must be at least 4 byte aligned. The same
buffer can be used for both input and output.
# Makefile for BCMSupport
COMPONENT = BCMSupport
DEBUG ?= FALSE
ifeq ($(DEBUG),TRUE)
CFLAGS += -DDEBUGLIB
CMHGFLAGS += -DDEBUGLIB
LIBS = ${DEBUGLIBS} ${NET5LIBS}
endif
# By default, the shared makefiles do a lot of the work of installing a
# Mesages file in ResourceFS. This is how to overrides it:
RES_OBJ =
LIBS += ${SYNCLIB}
CINCLUDES += -Itbox:
COMPONENT = BCMSupport
# By default, the shared makefiles assume you want to use CMHG to create
# your module header. This is how to override it:
CMHGFILE =
RES_OBJ = messages
# Header export phase
#ASMHDRS =
#ASMCHDRS =
ASMHDRS = BCMSupport
ASMCHDRS = BCMSupport
HDRS =
# CModule is equally useful for assembler modules. Its advantages over the
# AAmModule makefile are that you can use multiple source files (permitting
# more encapsulation, which is good programing practice) and it allows you
# to use non-postion-independent code, provided you do BL __RelocCode early
# in module initialisation.
include CModule
CMHGDEPENDS = BCMSupport mailbox cpuclock
OBJS = BCMSupport asm mailbox errors cpuclock
OBJS = BCMSupport
include CModule
# Dynamic dependencies:
Dir <Obey$Dir>
amu_machine export %*0
# By default, the shared makefiles assume any module has a Messages file.
# If you don't want this, set RES_OBJ to an empty string in the Makefile.
MBFull:Mailbox full
......@@ -11,13 +11,13 @@
GBLS Module_HelpVersion
GBLS Module_ComponentName
GBLS Module_ComponentPath
Module_MajorVersion SETS "0.02"
Module_Version SETA 2
Module_MajorVersion SETS "0.03"
Module_Version SETA 3
Module_MinorVersion SETS ""
Module_Date SETS "23 Jul 2012"
Module_ApplicationDate SETS "23-Jul-12"
Module_Date SETS "25 Mar 2016"
Module_ApplicationDate SETS "25-Mar-16"
Module_ComponentName SETS "BCMSupport"
Module_ComponentPath SETS "mixed/RiscOS/Sources/HWSupport/BCMSupport"
Module_FullVersion SETS "0.02"
Module_HelpVersion SETS "0.02 (23 Jul 2012)"
Module_FullVersion SETS "0.03"
Module_HelpVersion SETS "0.03 (25 Mar 2016)"
END
/* (0.02)
/* (0.03)
*
* This file is automatically maintained by srccommit, do not edit manually.
* Last processed by srccommit version: 1.1.
*
*/
#define Module_MajorVersion_CMHG 0.02
#define Module_MajorVersion_CMHG 0.03
#define Module_MinorVersion_CMHG
#define Module_Date_CMHG 23 Jul 2012
#define Module_Date_CMHG 25 Mar 2016
#define Module_MajorVersion "0.02"
#define Module_Version 2
#define Module_MajorVersion "0.03"
#define Module_Version 3
#define Module_MinorVersion ""
#define Module_Date "23 Jul 2012"
#define Module_Date "25 Mar 2016"
#define Module_ApplicationDate "23-Jul-12"
#define Module_ApplicationDate "25-Mar-16"
#define Module_ComponentName "BCMSupport"
#define Module_ComponentPath "mixed/RiscOS/Sources/HWSupport/BCMSupport"
#define Module_FullVersion "0.02"
#define Module_HelpVersion "0.02 (23 Jul 2012)"
#define Module_LibraryVersionInfo "0:2"
#define Module_FullVersion "0.03"
#define Module_HelpVersion "0.03 (25 Mar 2016)"
#define Module_LibraryVersionInfo "0:3"
/*
*Copyright(c)2016, RISC OS Open Ltd
*Allrightsreserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of RISC OS Open Ltd nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "BCMSupportHdr.h"
#include "swis.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include "Global/HALEntries.h"
#include "Global/Services.h"
#include "globals.h"
#include "mailbox.h"
#include "errors.h"
#include "cpuclock.h"
void *private_word;
int instance;
MessagesFD messages;
void *hal_sb;
void *hal_irqenable;
void *hal_irqdisable;
void *hal_irqclear;
retryfn retry;
#ifdef STANDALONE
extern void* Resources(void);
#endif
#define TEMP_BUFFER_SIZE 1024
static void *temp_buffer;
static uint32_t temp_buffer_phys;
static volatile uint32_t temp_buffer_lock = 0; /* 0 = unlocked, 1 = locked */
_kernel_oserror* module_init(const char *cmd_tail, int podule_base, void *pw)
{
(void) cmd_tail;
_kernel_oserror* e = NULL;
synclib_init();
/* set up debugging */
debug_initialise(Module_Title, "", "");
debug_set_device(HAL_OUTPUT);
debug_set_unbuffered_files(TRUE);
instance = podule_base;
private_word = pw;
/* Get HAL entries */
e = _swix(OS_Hardware, _INR(8,9)|_OUTR(0,1), OSHW_LookupRoutine, EntryNo_HAL_IRQEnable, &hal_irqenable, &hal_sb);
if (!e) e = _swix(OS_Hardware, _INR(8,9)|_OUT(0), OSHW_LookupRoutine, EntryNo_HAL_IRQDisable, &hal_irqdisable);
if (!e) e = _swix(OS_Hardware, _INR(8,9)|_OUT(0), OSHW_LookupRoutine, EntryNo_HAL_IRQClear, &hal_irqclear);
if (e)
{
return e;
}
/* Set up messages */
#ifdef STANDALONE
e = _swix(ResourceFS_RegisterFiles,_IN(0),Resources());
if(e)
goto error;
#endif
e = _swix(MessageTrans_OpenFile,_INR(0,2),&messages,"Resources:$.Resources.BCMSupport.Messages",0);
if(e)
goto error1;
/* Claim the temp buffer */
e = _swix(PCI_RAMAlloc,_INR(0,2)|_OUTR(0,1),TEMP_BUFFER_SIZE,16,0,&temp_buffer,&temp_buffer_phys);
if(e)
goto error2;
/* Set up the correct retry function pointer
On ARM11 we have to use CP15 WFI
Newer Pi's are assumed to all be multi-core, so use WFE */
uint32_t midr;
__asm("MRC p15,0,midr,c0,c0,0");
if ((midr & 0xfff0) == 0xb760)
{
dprintf(("","Using retry_cp15\n"));
retry = retry_cp15;
}
else
{
dprintf(("","Using retry_wfe\n"));
retry = retry_wfe;
}
e = mailbox_init();
if(!e)
{
return NULL;
}
_swix(PCI_RAMFree,_IN(0),temp_buffer);
error2:
_swix(MessageTrans_CloseFile,_IN(0),&messages);
error1:
#ifdef STANDALONE
_swix(ResourceFS_DeregisterFiles,_IN(0),Resources());
error:
#endif
return e;
}
_kernel_oserror *module_final(int fatal, int podule, void *pw)
{
(void) podule;
(void) fatal;
(void) pw;
_kernel_oserror *e = cpuclock_shutdown();
if (e)
{
return e;
}
mailbox_shutdown();
_swix(PCI_RAMFree,_IN(0),temp_buffer);
/* Close messages */
_swix(MessageTrans_CloseFile,_IN(0),&messages);
#ifdef STANDALONE
_swix(ResourceFS_DeregisterFiles,_IN(0),Resources());
#endif
return NULL;
}
void module_service(int service_number, _kernel_swi_regs *r, void *pw)
{
(void) pw;
if ((service_number == Service_ModulePostInit) && !strcmp((const char *) r->r[2], Module_Title))
{
cpuclock_init();
}
}
typedef struct
{
volatile uint32_t done;
volatile uint32_t response;
} myresponse_t;
static void blocking_callback(uint32_t r0, uint32_t response)
{
myresponse_t *resp = (myresponse_t *) r0;
resp->response = response;
barrier();
resp->done = 1;
}
static bool blocking_done(myresponse_t *resp)
{
barrier();
return resp->done;
}
/* Temp buffer alloc/free routines
At the moment we just have one buffer (temp_buffer) which we allow one client to use at a time. If the buffer is already in use, or the requested length is too long, we fall back to PCI_RAMAlloc.
This should be sufficient for most purposes - if the user wants to avoid the possibility of heap calls, they should be using preallocated buffers of their own! */
static _kernel_oserror *gettempbuffer(uint32_t len, void **log, uint32_t *phys)
{
if ((len <= TEMP_BUFFER_SIZE) && !atomic_update(1,&temp_buffer_lock))
{
barrier(); /* Sync with contents of buffer */
*log = temp_buffer;
*phys = temp_buffer_phys;
return NULL;
}
return _swix(PCI_RAMAlloc, _INR(0,2)|_OUTR(0,1), len, 16, 0, log, phys);
}
static void freetempbuffer(void *log)
{
if (log == temp_buffer)
{
barrier(); /* Sync contents of buffer */
temp_buffer_lock = 0;
}
else
{
_swix(PCI_RAMFree, _IN(0), log);
}
}
static _kernel_oserror *send_core(uint32_t value, uint32_t flags, MBResponse_t *resp, uint32_t *response)
{
if (flags & ~SendMBMessage_ValidFlags)
{
return geterror(Error_BadParm);
}
myresponse_t myresponse;
bool block_for_response = !(flags & SendMBMessage_Async);
if (!resp->mask)
{
resp = NULL;
block_for_response = false;
}
/* Substitute our own callback function if necessary
n.b. in SMP this assumes the stack is shareable memory */
else if (block_for_response)
{
resp->callback.r0 = (uint32_t) &myresponse;
/* n.b. r12 not required since the callback is entered with our C environment still set up */
resp->callback.pc = (void *) blocking_callback;
myresponse.done = 0;
}
/* Queue message */
if (flags & SendMBMessage_DontWaitForSpace)
{
if (!mailbox_send(value, resp))
{
return geterror(Error_MBFull);
}
}
else
{
/* N.B. we can't get IRQs when space becomes available in the send mailbox, so if we're out of send space this will end up sleeping longer than necessary.
However the fact that the USB controller generates an interrupt every microframe should help us to avoid any significant amounts of oversleeping */
retry((void *) value, resp, (bool (*)(void *, void *)) mailbox_send);
}
if (block_for_response)
{
/* Wait for response */
retry(&myresponse, NULL, (bool (*)(void *, void *)) blocking_done);
barrier();
dprintf(("","blocking resp %08x\n",myresponse.response));
if (response)
{
*response = myresponse.response;
}
}
return NULL;
}
#define SWINO(x) (BCMSupport_##x - BCMSupport_00)
_kernel_oserror *module_swi(int swi_offset, _kernel_swi_regs *r, void *pw)
{
(void) pw;
switch(swi_offset)
{
case SWINO(SendMBMessage):
{
MBResponse_t resp = {0};
resp.mask = r->r[2];
resp.value = r->r[3];
resp.callback.r0 = r->r[4];
resp.callback.r1 = NULL;
resp.callback.r12 = (void *) r->r[5];
resp.callback.pc = (void *) r->r[6];
return send_core(r->r[0], r->r[1], &resp, (uint32_t *) &r->r[0]);
}
case SWINO(MBSync):
{
if ((r->r[0] & ~MBSync_ValidFlags) || !r->r[0])
{
return geterror(Error_BadParm);
}
MBResponse_t mask = {0};
mask.mask = r->r[1];