cobaltowl

We'll cross that bridge when we find it

Beaglebone PRU remoteproc/UIO information

29-08-2023


This is a compilation of useful information regarding moving from UIO to remoteproc and related matters, with all sources linked in their respective relevant tidbits.

Code Composer Studio

Install dependencies

The installation script for Code Composer Studio will not install all dependencies required by the program on Linux, something which you need to do yourself.

These are the required dependencies, as per TI’s instructions:

libc6:i386 libusb-0.1-4 libgconf-2-4 libncurses5 libpython2.7 libtinfo5

After that, you should be ready to begin.

The actual installation process

Download the installation files from TI, extract them to a convenient location and run ccs_setup_XXXX.run

After the dependency check confirms you have everything set, pick a location (the default location will install the compiler in your home directory). After that, everything is straightforward.

To finish up, if you need drivers for debugging or other integrations with the boards you’re developing for, go to your installation directory and execute the script in ccs/install_scripts/install_drivers.sh.

Depending on your distribution, permissions or how your installation process went, you won’t be able to see the shortcut. You can find it in your installation directory, simply copy it from there

Installing the PRU compiler

CCS does not come with a PRU compiler as well.

You need to download the most recent version of the compiler from TI’s website and install that version. Installing from the app center or from the help menu may result in outdated versions of the compiler, even for the newest version of the CCS IDE.

When the installer asks for a location, pick the ccs/tools/compiler/ folder in your install location, or just move the output of the installer there.

If CCS remained open while you installed it, the compiler will not be available yet. You’ll need to refresh your compiler listing by clicking “More”, then “Refresh” on the “New Project” window, after selecting the PRU tab.

Adding necessary includes

CCS does not come with what you need to properly compile your project.

Download the PRU software support package from TI’s website and add the folders include/ and include/am335x from the downloaded files to your include paths in the IDE (open the Build Settings/Properties window by selecting Project->Show Build Settings in the top menubar):

Assembly

Moving from pasm to clpru

Moving from pasm to clpru presents a few syntax/styling changes, which can be summed up in this handy table found in the Embedded Linux Wiki and created by Mark. A. Yoder:

pasm clpru Notes
Comments \ ; Start comments with ;
Loading constants MOV r0, 0x0000 LDI r0, 0x0000 pasm generalized MOV to not only move registers (MOV r0, r1), but also moving immediate values (MOV r0, 0x0). The MOV in clpru is only for moving registers.
Loading 32-bit constants MOV r0, 0xffffffff LDI32 r0, 0xffffffff If you are loading more than 16 bits, use LDI32.
Load/store byte burst LBBO r2, r1, 0, 4 LBBO &r2, r1, 0, 4 An & is needed before the first register of LBBO, SBBO, LBCO or SBCO.
Substitution Symbols #define CH1BIT r30.t8 .asg r30.t8, CH1BIT .define r30.t8, CH1BIT Be sure the . isn’t in the first column.

However, some extra unexpected problems appeared (and apparently, won’t get fixed soon). These are documented below:

QBBC/QBBS and related pseudo ops do not work with format 2 from TIs own documentation (page 42). Instead, you are expected to only use format 1, so please replace all code that looks like QBBC somewhere, r31.t3 with: QBBC somewhere, r31, 3

Attempting to use Assembly and nothing else for the firmware is uncharted territory. However, you can call ASM code from C with minimal performance impact, which is described below:

Detecting main CPU “kicks” (new remoteproc message)

Just poll bit 30 of register 31. An example of how that can be done:

QBBS something, r31.b3, 7 ; Branch if message was received

C

Calling ASM code from C

As described by Mark in PRUCookbook, the process is fairly straightforward:

  • Create an ASM file
my_delay_cycles:
delay:
    sub     r14,   r14, 1   ; The first argument is passed in r14
    qbne    delay, r14, 0

    jmp     r3.w2           ; r3 contains the return address
  • Declare it in your C file with the following line:
extern void my_delay_cycles(uint32_t);

Message transmission with remoteproc (firmware)

A lot of the code in this section belongs to RoSchmi’s RPMesg “Hello World” and TI’s own examples.

The following example demonstrates a very basic “echo” program, with the “magic” parts boldened out. All it does is initialize the RP Message transport channel and poll for new messages by checking if bit 30 of register 31 is clear, which represents a “kick” from the main CPU. When a new message arrives, the PRU sends a message back.

volatile register uint32_t __R31;

#define HOST_INT            ((uint32_t) 1 << 30)

/* The PRU-ICSS system events used for RPMsg are defined in the Linux device tree
 * PRU0 uses system event 16 (To ARM) and 17 (From ARM)
 * PRU1 uses system event 18 (To ARM) and 19 (From ARM)
 */

#define TO_ARM_HOST          16
#define FROM_ARM_HOST        17
#define CHAN_NAME            "rpmsg-pru"
#define CHAN_DESC            "Channel 30"
#define CHAN_PORT            30
#define VIRTIO_CONFIG_S_DRIVER_OK    4

uint8_t payload[RPMSG_BUF_SIZE];

void main(void)

{
    struct pru_rpmsg_transport transport;
    uint16_t src, dst, len;
    volatile uint8_t *status;

    /* Allow OCP master port access by the PRU so the PRU can read external memories */

    CT_CFG.SYSCFG_bit.STANDBY_INIT = 0;

    /* Clear the status of the PRU-ICSS system event that the ARM will use to 'kick' us */
    CT_INTC.SICR_bit.STS_CLR_IDX = FROM_ARM_HOST;

    /* Make sure the Linux drivers are ready for RPMsg communication */
    status = &resourceTable.rpmsg_vdev.status;
    while (!(*status & VIRTIO_CONFIG_S_DRIVER_OK));
    pru_rpmsg_init(&transport, &resourceTable.rpmsg_vring0, &resourceTable.rpmsg_vring1, TO_ARM_HOST, FROM_ARM_HOST);
    while (pru_rpmsg_channel(RPMSG_NS_CREATE, &transport, CHAN_NAME, CHAN_DESC, CHAN_PORT) != PRU_RPMSG_SUCCESS);

    while (1) {
        if (__R31 & HOST_INT) {
            /* Clear the event status */
            CT_INTC.SICR_bit.STS_CLR_IDX = FROM_ARM_HOST;
            /* Receive all available messages, multiple messages can be sent per kick */
            while (pru_rpmsg_receive(&transport, &src, &dst, payload, &len) == PRU_RPMSG_SUCCESS) {
                /* Echo the message back to the same address from which we just received */
                pru_rpmsg_send(&transport, dst, src, payload, len);
            }
        }
    }
}

Message transmission with remoteproc (userspace)

This one is fairly standard. All you need to do is to write to /dev/rpmsg_pru30 or /dev/rpmsg_pru31, depending on your channel. This can be done easily in most programming languages, here’s how to do it in C:

pollfd pfd;
pfd.fd = open("/dev/rpmsg_pru30", O_RDWR);
write(pfd.fd,"hello!", 7);

Post-compiling

Pre-running

If you’re running kernel 4.19 or older, you’ll need to enable the remoteproc PRU device tree in /boot/uEnv.txt. Depending on which kernel you use, you must uncomment the line describing your specific kernel, which should be a TI kernel (not a Bone one).

For exemple, if you’re using 4.19, uncomment the following line:

uboot_overlay_pru=/lib/firmware/AM335X-PRU-RPROC-4-19-TI-00A0.dtbo

If you’re running 5.4 or later, you won’t need to load any device tree. However, PSSP (PRU Software Support Package) 6.0.0 or later won’t support with any kernel older than 5.10, as it introduces a number of breaking changes, such as the way it handles vring events.

Running

Send the compiled files to the Beaglebone via your preferred method, then copy the binary to /lib/firmware.

After that, you must indicate which file contains the firmware by echo’ing the filename to /sys/class/remoteproc/remoteproc1/firmware or /sys/class/remoteproc/remoteproc2/firmware.

remoteproc1 refers to PRU0 and remoteproc2 refers to PRU1, as remoteproc0 is the ARM CPU.

echo "firmware.out" > /sys/class/remoteproc/remoteproc1/firmware

To run the code, just echo “start” to /sys/class/remoteproc1/state or /sys/class/remoteproc2/state

echo start > /sys/class/remoteproc/remoteproc1/state

Stopping

Echo “stop” to /sys/class/remoteproc1/state or /sys/class/remoteproc2/state

echo  stop > /sys/class/remoteproc/remoteproc1/state