Beaglebone: “Hello, world!” (remoteproc version)

Andrew B. Wright, S. M. ’88, Ph. D.

9/9/2018

Previous Blog: Beaglebone: Robotics using the Beaglebone Black
Next Blog: Beaglebone: Toggling a bit with the PRU (remoteproc version)


CODE:

The code, including the Makefile, is packed up in this little archive.

You should create a subdirectory under your $(HOME) called hello (e.g., /root/hello) and put your code files and the Makefile in that directory.  You will have to create subdirectories pru0 and pru1 in your code directory for this example to work.

You should set the variable, CODE_ROOT, in the Makefile to the directory containing the code.  The archive (below) is set for /root/code/hello.

Download the archive to a directory (/root/code if you want the code to work out of the box) and unpack it using the command “tar -xzf ‘archive name’ “.  This will create an extra directory /root/code/hello in which the files will be located.  The archive should create the pru0 and pru1 subdirectories as well.  If you go to the directory, typing “make hello” and “make install” will create the executable files.  Typing “./hello” should give you the output (10 iterations of “PRU1 Responding: Hello, world!” echoed back from PRU1).

I put the communication routines in arm_comm.c (for the ARM processor code, hello.c) and pru_comm.c (for the PRU side processor code, main.c).  On both sides, rpmsg_init is used to initialize the communication layer.  These routines were set up as a state machine, which is designed to work in a loop.  So, as each set up step is completed, the state advances to the next set up step.  This allows the ARM side to be interrupted and allows the PRU side to insert code that runs every cycle.

On the ARM side, rpmsg_init is located before the ARM starts to do anything.  When the state reaches Initialized, execution exits the loop and the rest of the code can begin.  If the initialization fails, then the user can interrupt with ctrl-c.  In the future, when the ARM side code is running other processes, this initialization should be inserted into the main loop, as it is done in the PRU.

On the PRU side, rpmsg_init is checked every loop in the real time cycle.  This allows code to be run in the real time cycle to control motors and read sensors.  It is not desirable on a processor that is ensuring the state of a device, such as a robot, to wait until it can talk to the host processor before making sure that motors are in a safe state.

The routine, listen, monitors incoming traffic on the rpmsg channel, and places the received data into a buffer (pbuffer) of length, plen.  A routine must be written to drain the buffer.  In this example, this function is coded in main.  However, the routine, parse_the_message, is provided to both drain the buffer and parse command codes passed between the ARM and PRU.  That routine will be described in the next blog.

 


There were some big changes in the latest image after November when I started this.  Type uname -a.  I’m updating this for 4.14.49-ti-r54.  There have been a lot of iterations since I started this project.  With each iteration, the code gets more stable and usable; however, some modifications are usually required with each update.

If you’re going to use the archive, check your version.  If the version is different from the above and the code doesn’t work, wait a bit.  I’ll get to the updates eventually.


With the latest beaglebone debian kernel, the method of communicating with the pru is the remoteproc system.  There is an overview of the structure here.

Here’s the debian project page, from which you can link to the source code.  Carry on from here and you get to the kernel source.  If you look in the drivers sub-directory, you find a folder remoteproc and another folder rpmsg.  These are the pieces of the kernel that interact with the pru.  In remoteproc, you find a few files (pru_rproc.c, pru_rproc.h, pruss_intc.c, pruss.c, pruss.h) that directly interact with the pru.  The good news is that this development is yielding an easier to understand, easier to use remoteproc system.  When this is all done, this will be a great system.

If you perform a lsmod, you should see the following drivers running (pru_rproc, pruss_intc, pruss, rpmsg_pru, rpmsg_core, virtio_rpmsg_bus).  These are the drivers which implement ARM:PRU communication.

The header files for the code have been located in /usr/share/ti/cgt-pru/include.  This directory includes some standard header files, so the normal /usr/include is not included in the search path.

Important variables that you need to know:

maximum number of messages (32)

size of vring (512)

The hand’s on training from TI does have some useful information in conjunction with the code in /opt/source/… /usr/lib/ti/pru-software-support-package.  And, while the labs are useful, the place to dig is the “examples/am335x” directory.  This contains completed code that can be tested and adapted quickly.  The programs that follow in this blog are adapted from the code in PRU_Halt, PRU_RPMsg_Echo_Interrupt0, PRU_RPMsg_Echo_Interrupt1. If those files are not present, then “apt-get install pru-software-support-package”.

Other functions that look interesting for exploration are PRU_Direct_Connect0, PRU_Direct_Connect1, PruArm_to_Pru_Interrupt, and Pru_toPruArm_Interrupt.   This should give a full suite of Arm-Pru0-Pru1 interconnects.  With that enabled, debugging and interaction across all devices should be possible.

In the latest kernel, there is an entry in /sys/class called remoteproc which allows the pru to be stopped and started by writing to the file /sys/class/remoteproc/remoteprocX/state, where X=1 will talk to PRU0 and X=2 will talk to PRU1.

The removes the need to load/unload the rpmsg module (which was a horrible hack FWIW).


The Makefile in the examples gives a hint at how to compile for the PRU.  The archive is a stripped down version for the “Hello, world!” example given in the examples.  I have set it up so that both pru0 and pru1 are involved, so adaptions can be made relative to either unit.

On the latest debian, the PRU compiler, clpru, is installed by default.  The compiler is located in /usr/bin.

The libc-dev header files are located in /usr/share/ti/cgt-pru/include (PRU_CGT_ROOT in the Makefile).

Communication with the remoteproc kernel driver from the pru requires that pru-code is linked with the library, rpmsg_lib.lib. The header files  are located in /usr/lib/ti/pru-software-support-package/include/ (PRU_RPMSG_ROOT in the Makefile).


The structure of communications using INTC can be seen in the Technical Reference Manual, Figure 4-17.

PRU0 is connected to Host-0 using R31 bit 30.

PRU1 is connected to Host-1 using R31 bit 31.

System events (SYS_EVT) are connected to one of the ten channels (0..9).  This channel connection is done through the resource table.

Figure 4-21 demonstrates the Interrupt number and connection to a pin. Interrupt number 16, 17, 18, and 19 are connected to signals pr1_pru_mst_intr[0]_intr_req, pr1_pru_mst_intr[1]_intr_req, pr1_pru_mst_intr[2]_intr_req,  and pr1_pru_mst_intr[3]_intr_req.

So, the numbers for TO_ARM_HOST and FROM_ARM_HOST correspond to these interrupts and pins.  When the INTC controller is initialized, the information in the resource_table is loaded into the registers in the INTC controller that allow it to establish the appropriate interrupt connections.

The registers where this is stored are Channel Map Registers (CMR0, CMR1, CMR2 ,… CMR15) and Host Map Registers (HRM0, HRM1, HRM2) in the PRU_ICSS_INTC (see Table 4-102 of the TRM).  If you’re using the remoteproc driver, you really, really, really do not want to write anything to these registers.

As an aside, the PRU is not interruptible.  This is part of its charm.  So, the INTC ‘interrupt’ is not really an interrupt from the PRU side of things.  In order to see if the ARM has placed information in the buffer, the PRU has to know to check.  To tell the PRU that it’s done something, the ARM sets a bit in R31.  The PRU has to poll this bit to determine if something has happened.

This allows the PRU to run in a very deterministic manner and provide an extremely repeatable timed loop.

Unless you code an infinite loop (see below) …


PRU side responses (from pru_rpmsg):

PRU_RPMSG_INVALID_EVENT (pru_rpmsg_init) – means that to_arm_event or from_arm_event is not within MIN_VALID_EVENT or MAX_VALID_EVENT

PRU_RPMSG_SUCCESS (pru_rpmsg_init, pru_rpmsg_send, pru_rpmsg_receive) – function returned successfully

PRU_RPMSG_NO_BUF_AVAILABLE (pru_rpmsg_send, pru_rpmsg_receive) – response from pru_virtqueue_get_avail_buf was less than zero, which occurs if vq->last_avail_idx == avail->idx.

PRU_RPMSG_BUF_TOO_SMALL (pru_rpmsg_send) – the size of the data + header is greater than RPMSG_BUF_SIZE (512)

PRU_RPMSG_INVALID_HEAD (pru_rpmsg_send, pru_rpmsg_receive) – the result of pru_virtqueue_add_used_buf was less than zero, which occurs if head > vq->vring.num.

PRU_RPMSG_NO_KICK (pru_virtqueue_kick) means that no kick was sent (the VRING_AVAIL_F_NO_INTERRUPT bit is set in vq->vring.avail->flags).


ARM side responses (available through dev_err -> dmesg) obtained from kernel source rpmsg_pru.c driver code

Message length table is full – MAX_FIFO_MSG (32) has been exceeded

Unable to allocate fifo for the rpmsg_pru device – MAX_FIFO_MSG * FIFO_MSG_SIZE bytes are not available in rpmsg_pru_probe

Device already open – the PRU is locked (mutex_lock)

Data too large for RPMsg Buffer (rpmsg_pru_write) – same as PRU_RPMSG_BUF_TOO_SMALL, but from the ARM side

Error copying buffer from user space – not sure what gives rise to this, but, it’s not good.

rpmsg_send failed – what it says.  Not sure where rpmsg_send is defined or why it would fail.

Failed to get a minor number for the rpmsg_pru device

Unable to add cdev for the rpmsg_pru device

Unable to create the rpmsg_pru device


In the Makefile, the compiler directive, -D PRU0 and -D PRU1, works with the #ifdef statements in the  resource_table file to provide the different sys_evt -> channel -> host definitions required by rpmsg_pru30 and rpmsg_pru31 devices.

There are two places in main.c that would be undesirable in an embedded, real-time application.  They’re fine in a test like this.

The first,

while (!(*status & VIRTIO_CONFIG_S_DRIVER_OK));

could lock up the PRU forever, waiting until a system resource becomes available. It would be better to check this status and then make sure that all the PRU controlled resources remain in a safe holding pattern state.  Imagine if the PRU were controlling motors.  Ideally, you would want the system to make sure those motors were in the ‘safe’ state while awaiting the ARM to PRU communication channel to start up, and to keep making sure they stayed in that state.

The second line is

while (pru_rpmsg_channel(RPMSG_NS_CREATE, &transport, CHAN_NAME, CHAN_DESC, CHAN_PORT) != PRU_RPMSG_SUCCESS);

The problem is the same as with the first line; however, this one could really stall for a while if all the buffers had been taken by another process.

There are two instructions in the linker command file, hello.cmd, and in the header files, pru_cfg.h and pru_intc.h, which interact with the linker allocations and symbol definitions.  The header files are located in /usr/lib/ti/pru-software-support-package/include.

PRU_INTC		: org = 0x00020000 len = 0x00001504	CREGISTER=0

volatile __far pruIntc CT_INTC __attribute__((cregister(“PRU_INTC”, far), peripheral));

PRU_CFG			: org = 0x00026000 len = 0x00000044	CREGISTER=4

volatile __far pruCfg CT_CFG __attribute__((cregister(“PRU_CFG”, near), peripheral));

The statement, cregister (“PRU_INTC” … , causes the linker to look for a symbol definition, PRU_INTC … CREGISTER=0.  There is a section on cregisters in the Technical Reference Manual which documents all the PRU registers.  Access is faster using this method.  The __far keywords in this example make sure that 32 bit pointers are used.

The volatile and peripheral keywords are also necessary.  From the PRU C/C++ Language Implementation Guide:

“Any variable which might be modified by something external to the obvious control flow of the program must be declared volatile. This tells the compiler that a function might modify the value at any time, so the compiler should not perform optimizations which will change the number or order of accesses of that variable. This is the primary purpose of the volatile keyword.”

“The peripheral attribute can only be used with the cregister attribute and has two effects. First, it puts the object in a section that will not be loaded onto the device. This prevents initialization of the peripheral at runtime. Second, it allows the same object to be defined in multiple source files without a linker error. The intent is that peripherals can be completely described in a header file.”

The near and far keywords in the cregister statements mean that the compiler will use either 16 bit or 32 bit offsets relative to the cregister.

Clearing STANDBY_INIT in pruCfg initializes the OCP controller, which is necessary for communicating with L3 and L4 interconnects.

The ARM-side code to communicate with the PRU is called hello.c.


Once the executable file has been compiled, the executable must be copied to the /lib/firmware directory.

The name of the firmware (for instance, am335x-pru0-fw) must be placed into /sys/class/remoteproc/remoteprocX/firmware (X is either 1 or 2 depending on whether the processor is pru0 or pru1) file.

The convention is to use the names am335x-pru0-fw and am335x-pru1-fw, although those names are no longer mandatory.

Remoteproc will load those files into the appropriate pru (0 or 1) when you send ‘start’ to /sys/class/remoteproc/remoteprocX/state.

In the latest debian, remoteproc should be enabled by default.  Check by running:

lsmod | grep pru

If pru_rproc shows up, then remoteproc is actively communicating with the pru.  

The Makefile and hello.c have all of these steps automated.  Once a program has been compiled, type make install and the steps will be executed in sequence.

Next Blog: Beaglebone: Toggling a bit with the PRU


What follows below the line is old stuff that is increasingly out of date.


The “Hello, world!” code is called main0.c.  The resource table is contained in a header file, resource_table0.h.

//hello.new
#include <stdint.h>
#include <stdio.h>
#include <stddef.h>
#include "../abw_include/pruCFG.h"
#include "../abw_include/pruINTC.h"
#include "../abw_include/rscTYPES.h"
#include "pru_virtqueue.h"
#include "pru_rpmsg.h"
#include "../abw_include/sysMAILBOX.h"
#include "resource_table0.h"

volatile __far pruCfg C4 __attribute__((cregister("PRU_CFG",near),peripheral));
volatile __far pruIntc C0 __attribute__((cregister("PRU_INTC",far),peripheral));
volatile __far sysMailbox C22 __attribute__((cregister("MBX0",far),peripheral));

volatile register uint32_t __R31;

uint8_t payload[RPMSG_BUF_SIZE];

void init_RPMSG(struct pru_rpmsg_transport *transport){
 volatile uint8_t *status;
 C22.IRQ[MB_USER].ENABLE_SET |= 1 << (MB_FROM_ARM_HOST*2); //enable interrupt
 status = &resourceTable.rpmsg_vdev.status;
 while (!(*status & VIRTIO_CONFIG_S_DRIVER_OK)); //are Linux drivers ready for RPMSG? ... this could cause a hang.
 /* Initialize pru_virtqueue corresponding to vring0 (PRU to ARM Host direction) */
 pru_virtqueue_init(&transport->virtqueue0,&resourceTable.rpmsg_vring0,&C22.MESSAGE[MB_TO_ARM_HOST],&C22.MESSAGE[MB_FROM_ARM_HOST]);
 /* Initialize pru_virtqueue corresponding to vring1 (ARM Host to PRU direction) */
 pru_virtqueue_init(&transport->virtqueue1,&resourceTable.rpmsg_vring1,&C22.MESSAGE[MB_TO_ARM_HOST],&C22.MESSAGE[MB_FROM_ARM_HOST]);
 /* Create the RPMsg channel between the PRU and ARM user space using the transport structure. */
 while (pru_rpmsg_channel(RPMSG_NS_CREATE, transport, CHAN_NAME, CHAN_DESC, CHAN_PORT) != PRU_RPMSG_SUCCESS); //this could cause a hang.
}


void wait_for_arm_host(struct pru_rpmsg_transport *transport,uint16_t *src, uint16_t *dst){
 uint16_t len;
 int flag;

 for(flag=1;flag;){
 if (__R31 & HOST_INT) {
 /* Clear the mailbox interrupt */
 C22.IRQ[MB_USER].STATUS_CLR |= 1 << (MB_FROM_ARM_HOST *2);
 /* Clear the event status, event MB_INT_NUMBER corresponds to the mailbox interrupt */
 C0.SICR_bit.STS_CLR_IDX = MB_INT_NUMBER;
 /* Use a while loop to read all of the current messages in the mailbox */
 while(C22.MSGSTATUS_bit[MB_FROM_ARM_HOST].NBOFMSG > 0){
 /* Check to see if the message corresponds to a receive event for the PRU */
 // It is possible that messages are: 0 = mbox ready, 1 = mbox receive, 2 = mbox crash, 3 = mbox echo request, 
 // 4 = mbox echo replay, 5 = mbox abort request.
 if(C22.MESSAGE[MB_FROM_ARM_HOST] == 1){
 /* Receive the message */
 if (pru_rpmsg_receive(transport, src, dst, payload, &len) == PRU_RPMSG_SUCCESS) {
 flag=0;
 }
 }
 }
 }
 }


}

void main(void)
{
 struct pru_rpmsg_transport transport;
 uint16_t src, dst;

 C4.SYSCFG_bit.STANDBY_INIT = 0;
 init_RPMSG(&transport);
 wait_for_arm_host(&transport,&src,&dst);
 pru_rpmsg_send(&transport, dst, src, "Hello, world from pru0", 23);
}

The resource table (resource_table0.h) is:

#include <stdint.h>
#include <stddef.h>
#include "../abw_include/rscTYPES.h"
#include "../abw_include/pruINCS.h"

// Mapping sysevts to a channel. Each pair contains a sysevt, channel
#define PRU_SYSEVT 60 //pru0, Mbox0 - mail_u1_irq, from Table 4-21 in TRM
#define PRU_CHANNEL 0 //pru0

#define MB_USER 1 //pru0 (mailbox module user 1)

#define MB_INT_NUMBER PRU_SYSEVT

#define HOST_INT 0x40000000 //pru0, Host-0 Interrupt sets bit 30 in register R31 Table 4-10 in TRM

// The mailboxes used for RPMsg are defined in the Linux device tree
#define MB_TO_ARM_HOST 3 //pru0, uses mailboxes 2 (From ARM) and 3 (To ARM)
#define MB_FROM_ARM_HOST 2 //pru0

// PRU0 uses channel 30, PRU1 uses channel 31
#define CHAN_NAME "rpmsg-pru"
#define CHAN_DESC "Channel 30" //pru0
#define CHAN_PORT 30 //pru0

struct my_resource_table {
 uint32_t ver;
 uint32_t num;
 uint32_t reserved[2];
 uint32_t offset[2]; /* Should match 'num' in actual definition */

 /* rpmsg vdev entry */
 struct fw_rsc_vdev rpmsg_vdev;
 struct fw_rsc_vdev_vring rpmsg_vring0;
 struct fw_rsc_vdev_vring rpmsg_vring1;

 /* intc definition */
 struct fw_rsc_custom pru_ints;
};

#pragma DATA_SECTION(resourceTable, ".resource_table")
#pragma RETAIN(resourceTable)
struct ch_map pru_intc_map[] = { {PRU_SYSEVT, PRU_CHANNEL}};

struct my_resource_table resourceTable = {1, 2, 0, 0,
 /* offsets to entries */
 {offsetof(struct my_resource_table, rpmsg_vdev),offsetof(struct my_resource_table, pru_ints),},

 /* rpmsg vdev entry */
 {
 (uint32_t)TYPE_VDEV, //type
 (uint32_t)VIRTIO_ID_RPMSG, //id
 (uint32_t)0, //notifyid
 (uint32_t)RPMSG_PRU_C0_FEATURES, //dfeatures
 (uint32_t)0, //gfeatures
 (uint32_t)0, //config_len
 (uint8_t)0, //status
 (uint8_t)2, //num_of_vrings, only two is supported
 { (uint8_t)0, (uint8_t)0 }, //reserve
 },
 /* the two vrings */
 {
 0, //da, will be populated by host, can't pass it in
 16, //align (bytes),
 PRU_RPMSG_VQ0_SIZE, //num of descriptors
 0, //notifyid, will be populated, can't pass right now
 0 //reserved
 },
 {
 0, //da, will be populated by host, can't pass it in
 16, //align (bytes),
 PRU_RPMSG_VQ1_SIZE, //num of descriptors
 0, //notifyid, will be populated, can't pass right now
 0 //reserved
 },

 {
 TYPE_CUSTOM, TYPE_PRU_INTS,
 sizeof(struct fw_rsc_custom_ints),
 { /* PRU_INTS version */
 0x0000,
 /* Channel-to-host mapping, 255 for unused
 * Mapping Channel-0 to Host-0 (PRU0/1 R31 bit 30)
 * */
 0,HOST_UNUSED, HOST_UNUSED, HOST_UNUSED, HOST_UNUSED,
 HOST_UNUSED, HOST_UNUSED, HOST_UNUSED, HOST_UNUSED, HOST_UNUSED,
 /* Number of evts being mapped to channels */
 (sizeof(pru_intc_map) / sizeof(struct ch_map)),
 /* Pointer to the structure containing mapped events */
 pru_intc_map,
 },
 },
};


The linker command file, HELLO.cmd, defines the location of resources:

/******************************************************************************/
/*    HELLO.cmd                                                               */
/*    Description: This linker command file specifies the memory layout for   */
/*    the PRU on an AM335x system.  This is a good place to map the           */
/*    peripherals that might be used by a PRU program (for instance, the      */
/*    pwms).  It can be used to allocate sections (data, code)  and regions   */
/*    of shared memory (for instance between PRU0 and PRU1 and the ARM).      */
/*    It will work with the TI PRU compiler (clpru) using the -z option.      */
/*    This file was created by modifying the stock linker command file,       */
/*    AM3359_PRU.cmd supplied with the examples in the TI compiler.           */
/******************************************************************************/

-cr
-stack 0x100
-heap 0x100

MEMORY
{
    PAGE 0:
      PRUIMEM:   o = 0x00000000  l = 0x00002000  /* 8kB PRU0 Instruction RAM */
    PAGE 1:
      PRUDMEM0: o=0x00000000 l=0x00002000 CREGISTER=24 //8kB PRU Data RAM 0
      PRUDMEM1: o=0x00002000 l=0x00002000 CREGISTER=25 //8kB PRU Data RAM 1
    PAGE 2:
      PRU_CFG : o = 0x00026000 l = 0x00000044 CREGISTER=4 //From the PRU constants table, 4-9 in TRM
      PRU_INTC : o=0x00020000 l = 0x00001504    CREGISTER=0 //From the PRU constants table
      PRU_SHAREDMEM: o = 0x00010000 l= 0x00003000 CREGISTER=28 /*12kB PRU0 Shared Memory 0x00nnnn00 ... set nnnn through PRU CTRL reg*/

      GEMAC: o=0x4A100000 l=0x0000128C CREGISTER=9 //I'm not sure what this is, but it is required by rproc
      MBX0: o=0x480C8000 l=0x00000140 CREGISTER=22 //mailbox registers
}

SECTIONS
{
    .text:_c_int00* > 0x0, PAGE 0
    .text           >  PRUIMEM, PAGE 0
    .stack          >  PRUDMEM0, PAGE 1
    .bss            >  PRUDMEM0, PAGE 1
    .cio            >  PRUDMEM0, PAGE 1
    .const          >  PRUDMEM0, PAGE 1
    .data           >  PRUDMEM0, PAGE 1
    .switch         >  PRUDMEM0, PAGE 1
    .sysmem         >  PRUDMEM0, PAGE 1
    .cinit          >  PRUDMEM0, PAGE 1
    .rodata         >  PRUDMEM0, PAGE 1
    .rofardata      >  PRUDMEM0, PAGE 1
    .resource_table > PRUDMEM0, PAGE 1
}

The code for pru1 is called main1.c (adapted from TI’s code):

//hello.new
#include <stdint.h>
#include <stdio.h>
#include <stddef.h>
#include "../abw_include/pruCFG.h"
#include "../abw_include/pruINTC.h"
#include "../abw_include/rscTYPES.h"
#include "pru_virtqueue.h"
#include "pru_rpmsg.h"
#include "../abw_include/sysMAILBOX.h"
#include "resource_table1.h"

volatile __far pruCfg C4 __attribute__((cregister("PRU_CFG",near),peripheral));
volatile __far pruIntc C0 __attribute__((cregister("PRU_INTC",far),peripheral));
volatile __far sysMailbox C22 __attribute__((cregister("MBX0",far),peripheral));

volatile register uint32_t __R31;

uint8_t payload[RPMSG_BUF_SIZE];

void init_RPMSG(struct pru_rpmsg_transport *transport){
 volatile uint8_t *status;
 C22.IRQ[MB_USER].ENABLE_SET |= 1 << (MB_FROM_ARM_HOST*2); //enable interrupt
 status = &resourceTable.rpmsg_vdev.status;
 while (!(*status & VIRTIO_CONFIG_S_DRIVER_OK)); //are Linux drivers ready for RPMSG? ... this could cause a hang.
 /* Initialize pru_virtqueue corresponding to vring0 (PRU to ARM Host direction) */
 pru_virtqueue_init(&transport->virtqueue0,&resourceTable.rpmsg_vring0,&C22.MESSAGE[MB_TO_ARM_HOST],&C22.MESSAGE[MB_FROM_ARM_HOST]);
 /* Initialize pru_virtqueue corresponding to vring1 (ARM Host to PRU direction) */
 pru_virtqueue_init(&transport->virtqueue1,&resourceTable.rpmsg_vring1,&C22.MESSAGE[MB_TO_ARM_HOST],&C22.MESSAGE[MB_FROM_ARM_HOST]);
 /* Create the RPMsg channel between the PRU and ARM user space using the transport structure. */
 while (pru_rpmsg_channel(RPMSG_NS_CREATE, transport, CHAN_NAME, CHAN_DESC, CHAN_PORT) != PRU_RPMSG_SUCCESS); //this could cause a hang.
}


void wait_for_arm_host(struct pru_rpmsg_transport *transport,uint16_t *src, uint16_t *dst){
 uint16_t len;
 int flag;

 for(flag=1;flag;){
 if (__R31 & HOST_INT) {
 /* Clear the mailbox interrupt */
 C22.IRQ[MB_USER].STATUS_CLR |= 1 << (MB_FROM_ARM_HOST *2);
 /* Clear the event status, event MB_INT_NUMBER corresponds to the mailbox interrupt */
 C0.SICR_bit.STS_CLR_IDX = MB_INT_NUMBER;
 /* Use a while loop to read all of the current messages in the mailbox */
 while(C22.MSGSTATUS_bit[MB_FROM_ARM_HOST].NBOFMSG > 0){
 /* Check to see if the message corresponds to a receive event for the PRU */
 // It is possible that messages are: 0 = mbox ready, 1 = mbox receive, 2 = mbox crash, 3 = mbox echo request, 
 // 4 = mbox echo replay, 5 = mbox abort request.
 if(C22.MESSAGE[MB_FROM_ARM_HOST] == 1){
 /* Receive the message */
 if (pru_rpmsg_receive(transport, src, dst, payload, &len) == PRU_RPMSG_SUCCESS) {
 flag=0;
 }
 }
 }
 }
 }


}

void main(void)
{
 struct pru_rpmsg_transport transport;
 uint16_t src, dst;

 C4.SYSCFG_bit.STANDBY_INIT = 0;
 init_RPMSG(&transport);
 wait_for_arm_host(&transport,&src,&dst);
 pru_rpmsg_send(&transport, dst, src, "Hello, world from pru1", 23);
}

and its resource table is located in resource_table1.h.

#include <stdint.h>
#include <stddef.h>
#include "../abw_include/rscTYPES.h"
#include "../abw_include/pruINCS.h"

// Mapping sysevts to a channel. Each pair contains a sysevt, channe
#define PRU_SYSEVT 59 //pru1, Mbox0 - mail_u2_irq, from Table 4-21 in TRM
#define PRU_CHANNEL 1 //pru1

#define MB_USER 2 //pru1 (mailbox modules user 2)

#define MB_INT_NUMBER PRU_SYSEVT


#define HOST_INT 0x80000000 //pru1, Host-1 Interrupt sets bit 31 in register R31, Table 4-10 in TRM

// The mailboxes used for RPMsg are defined in the Linux device tree
#define MB_TO_ARM_HOST 5 //pru1, uses mailboxes 4 (From ARM) and 5 (To ARM)
#define MB_FROM_ARM_HOST 4 //pru1

// PRU0 uses channel 30, PRU1 uses channel 31
#define CHAN_NAME "rpmsg-pru"
#define CHAN_DESC "Channel 31" //pru1
#define CHAN_PORT 31 //pru1

struct my_resource_table {
 uint32_t ver;
 uint32_t num;
 uint32_t reserved[2];
 uint32_t offset[2]; /* Should match 'num' in actual definition */

 /* rpmsg vdev entry */
 struct fw_rsc_vdev rpmsg_vdev;
 struct fw_rsc_vdev_vring rpmsg_vring0;
 struct fw_rsc_vdev_vring rpmsg_vring1;

 /* intc definition */
 struct fw_rsc_custom pru_ints;
};

#pragma DATA_SECTION(resourceTable, ".resource_table")
#pragma RETAIN(resourceTable)
struct ch_map pru_intc_map[] = { {PRU_SYSEVT, PRU_CHANNEL}};

struct my_resource_table resourceTable = {1, 2, 0, 0,
 /* offsets to entries */
 {offsetof(struct my_resource_table, rpmsg_vdev),offsetof(struct my_resource_table, pru_ints),},

 /* rpmsg vdev entry */
 {
 (uint32_t)TYPE_VDEV, //type
 (uint32_t)VIRTIO_ID_RPMSG, //id
 (uint32_t)0, //notifyid
 (uint32_t)RPMSG_PRU_C0_FEATURES, //dfeatures
 (uint32_t)0, //gfeatures
 (uint32_t)0, //config_len
 (uint8_t)0, //status
 (uint8_t)2, //num_of_vrings, only two is supported
 { (uint8_t)0, (uint8_t)0 }, //reserve
 },
 /* the two vrings */
 {
 0, //da, will be populated by host, can't pass it in
 16, //align (bytes),
 16, //num of descriptors
 0, //notifyid, will be populated, can't pass right now
 0 //reserved
 },
 {
 0, //da, will be populated by host, can't pass it in
 16, //align (bytes),
 16, //num of descriptors
 0, //notifyid, will be populated, can't pass right now
 0 //reserved
 },

 {
 TYPE_CUSTOM, TYPE_PRU_INTS,
 sizeof(struct fw_rsc_custom_ints),
 { /* PRU_INTS version */
 0x0000,
 /* Channel-to-host mapping, 255 for unused
 * Mapping Channel-0 to Host-0 (PRU0/1 R31 bit 30)
 * */
 HOST_UNUSED,1, HOST_UNUSED, HOST_UNUSED, HOST_UNUSED,
 HOST_UNUSED, HOST_UNUSED, HOST_UNUSED, HOST_UNUSED, HOST_UNUSED,
 /* Number of evts being mapped to channels */
 (sizeof(pru_intc_map) / sizeof(struct ch_map)),
 /* Pointer to the structure containing mapped events */
 pru_intc_map,
 },
 },
};

The code to listen to the pru is called hello.c (adapted from TI’s rpmsg_pru_user_space_echo.c) and runs on the arm processor.

/*
 * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/
 *
 *
 * 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 Texas Instruments Incorporated 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
 * OWNER 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 <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/poll.h>

#define MAX_BUFFER_SIZE 512
char readBuf[MAX_BUFFER_SIZE];
#define DEVICE_NAME0 "/dev/rpmsg_pru30"
#define DEVICE_NAME1 "/dev/rpmsg_pru31"
int main(void)
{
 struct pollfd pollfds[2];
 int result = 0;

 /* Open the rpmsg_pru character device file */
 pollfds[0].fd = open(DEVICE_NAME0, O_RDWR);
 pollfds[1].fd = open(DEVICE_NAME1, O_RDWR);
 /*
 * If the RPMsg channel doesn't exist yet the character device
 * won't either.
 * Make sure the PRU firmware is loaded and that the rpmsg_pru
 * module is inserted.
 */
 if (pollfds[0].fd < 0) {
 printf("Failed to open %s\n", DEVICE_NAME0);
 return -1;
 }
 if (pollfds[1].fd < 0) {
 printf("Failed to open %s\n",DEVICE_NAME1);
 return -1;
 }

 /* Send 'hello world!' to the PRU through the RPMsg channel */
 result = write(pollfds[0].fd, "hello world!", 13); 
 result = write(pollfds[1].fd, "hello world!",13);
 /* Poll until we receive a message from the PRU and then print it */
 result = read(pollfds[0].fd, readBuf, MAX_BUFFER_SIZE);
 if (result > 0)
 printf("Message received from PRU: %s\n\n",readBuf);

 /* Close the rpmsg_pru character device file */
 close(pollfds[0].fd);
 result = read(pollfds[1].fd,readBuf,MAX_BUFFER_SIZE);
 if(result > 0) printf(" message received from PRU: %s\n",readBuf);
 close(pollfds[1].fd);
 return 0;
}

Compile all three files using the Makefile:

make hello.out

make am335x-pru0-fw

make am335x-pru1-fw

make install

Run (from the arm command line) using

./hello.out

You should see “Hello, world from pru0!”  and “Hello, world from pru1!” at the command prompt.


Something to investigate further related to assigning pins (related to device tree overlay) is the utility:

config-pin P9.30 pruout


Another useful linux command … grep -r “pattern” … lets you see pattern within any files in the current directory.

A potentially useful command … dtc -I fs /sys/firmware/devicetree/base


6/3/2017

Just trying out something new.

Look to see if you have /sys/kernel/debug.

If not, try “mount -t debugfs none /sys/kernel/debug”.

There is a subdirectory, remoteproc,

More importantly, there is dynamic_debug/control, which allows you to control the info that is dumped from a driver on load.

For instance, check “cat /sys/kernel/debug/dynamic_debug/control | grep pru_rproc” to see what is being dumped by rproc.

These messages appear in the dmesg ringbuffer and can be viewed by typing “dmesg.”

In order to view a clean set of messages after an operation, type “dmesg –clear” to clear the ringbuffer and then run the command (e.g. modprobe pru_rproc).  Then, dmesg will show only the messages that were logged after that command.

For pru_rproc, the function pru_rproc_probe has a few dev_dbg statements:

For instance, to add the dev_dbg circa line 786, type “echo ‘file pru_rproc.c line 786 +p’ > /sys/kernel/debug/dynamic_debug/control”

To get the dynamic info, need to invoke “modprobe pru_rproc dyndbg==p”  where the p is the same as the p in the line in control above.

There are other flags (plftm). 


The following is a bizarre twist that occurred when TI was switching between pruss and rproc.  As of the latest kernel, it appears that both options coexist without the need for this kludge.


Earlier implementation of the remoteproc PRU-Arm interface used the sys_mailbox.  The current implementation uses INTC interrupt flags directly to pass events back-and-forth.

Old archive, using the old remoteproc system.

Here’s a document from another person who is trying to keep track of the remoteproc updating.  This has a couple of extra steps from the standard internet descriptions that you need to do before getting things to work.

 

If pru_rproc doesn’t show up (as it won’t in 4.4 and later), you probably need to activate it. (NOT TESTED)

Here’s a page with some instructions on enabling rproc.

Change to /opt/source/dtb-4.4-ti.

Presumably, you want the version of dtb that goes along with your version.

Edit src/arm/arm335x-boneblack-emmc-overlay.dtb and uncomment the line /*#include “am33xx-pruss-rproc.dtsi”*/.

From the command line, make, make install.

Blacklist uio_pruss by echo “blacklist uio_pruss” > /etc/modprobe.d/pruss-blacklist.conf

Reboot and try again.

I had to manually run “modprobe pru_rproc” to get the pru_rproc working the first time.  There is almost certainly a way to get it to run automatically on boot.

As of right now, I can get pru_rproc to load, but it is not automatically loading the fw for the pru.  More to come.


 

Posted in: Robotics
Read more about:

Comments are closed.