Beaglebone: remoteproc “Hello, world!”

 

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

5/29/2017

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

There were some big changes in the latest image after November when I started this.  Type uname -a.  I’m updating this for 4.4.30-ti-r64 4.4.54-ti-r93.

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.  Which is already at 4.4.41 (1/15/2017) 4.4.68 (5/29/2017)!!!! I’m not sure how to take this, but, it seems that development is moving forward at hyperspeed.  If you look in the drivers sub-directory, you find a folder remoteproc and another folder rpmsg.  These are the two 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.

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.

The Makefile in the examples gives a hint at how to compile for the PRU.  Here 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/include/arm-linux-gnueabihf/.  The header files for rpmsg are located in /usr/share/ti/cgt-pru/include /usr/lib/ti/pru-software-support-package/include/.  the only thing that seems to be needed in arm-linux-gnueabihf are the stdint cdefs specific to the arm.

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/.


The structure of communications using INTC can be seen in the Technical Reference Manual, Table 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.

Table 4-21 demonstrates the Interrupt number and connection to a pin. Pins 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.

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) …


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 gen0 and gen1 in your code directory for this example to work.

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


The Makefile:

 

hello: hello.c
	gcc hello.c -o hello

PRU_RPMSG_ROOT:= /usr/lib/ti/pru-software-support-package/
PRU_INCLUDE:= --include_path=/usr/include/arm-linux-gnueabihf/ --include_path=$(PRU_RPMSG_ROOT)include/ --include_path=$(PRU_RPMSG_ROOT)include/am335x/
CODE_ROOT:=/root/hello/
PRU0_ROOT:= $(CODE_ROOT)pru0/
PRU1_ROOT:= $(CODE_ROOT)pru1/
LINKER_CMD_FILE:= hello.cmd
PRU_TOOLS:=/usr/bin/

CFLAGS=-v3 -O2 --endian=little --hardware_mac=on

LDFLAGS+= -L.

am335x-pru0-fw: $(CODE_ROOT)main.c
	$(PRU_TOOLS)clpru $(CFLAGS) $(PRU_INCLUDE) -ppd -ppa -fe $(PRU0_ROOT)/main0.object $(CODE_ROOT)main.c -D PRU0
	$(PRU_TOOLS)clpru -z $(LINKER_CMD_FILE) -o $(PRU0_ROOT)am335x-pru0-fw $(PRU0_ROOT)/main0.object -l$(PRU_RPMSG_ROOT)lib/rpmsg_lib.lib

am335x-pru1-fw: $(CODE_ROOT)main.c
	$(PRU_TOOLS)clpru $(CFLAGS) $(PRU_INCLUDE) -ppd -ppa -fe $(PRU1_ROOT)/main1.object $(CODE_ROOT)main.c -D PRU1
	$(PRU_TOOLS)clpru -z $(LINKER_CMD_FILE) -o $(PRU1_ROOT)am335x-pru1-fw $(PRU1_ROOT)/main1.object -l$(PRU_RPMSG_ROOT)lib/rpmsg_lib.lib

install: am335x-pru0-fw am335x-pru1-fw
	cp $(PRU0_ROOT)/am335x-pru0-fw /lib/firmware
	cp $(PRU1_ROOT)/am335x-pru1-fw /lib/firmware
	rmmod -f pru_rproc
	modprobe pru_rproc

clean:
	rm $(PRU0_ROOT)/am335x-pru0-fw
	rm $(PRU1_ROOT)/am335x-pru1-fw
	rm $(PRU0_ROOT)/*.object
	rm $(PRU1_ROOT)/*.object
	rm *.pp


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.

resource_table.h (modified from the resource_table supplied by TI, see copyright statement at the bottom of this blog).

#ifndef _RSC_TABLE_PRU_H_
#define _RSC_TABLE_PRU_H_

#include <stddef.h> //needed for offset_of
#include <rsc_types.h> 
#include "pru_virtio_ids.h"

#define PRU_RPMSG_VQ0_SIZE	16
#define PRU_RPMSG_VQ1_SIZE	16
#define VIRTIO_RPMSG_F_NS	0
#define RPMSG_PRU_C0_FEATURES	(1 << VIRTIO_RPMSG_F_NS)
#define HOST_UNUSED		255

#ifdef PRU0
#define TO_ARM_HOST			16
#define FROM_ARM_HOST			17
#define PRU0_TO_ARM_CHANNEL  2
#define PRU0_FROM_ARM_CHANNEL 0
#define PRU1_TO_ARM_CHANNEL  HOST_UNUSED
#define PRU1_FROM_ARM_CHANNEL HOST_UNUSED
#define HOST_INT			0x40000000
#define CHAN_NAME			"rpmsg-pru"
#define CHAN_DESC			"Channel 30"
#define CHAN_PORT			30
#define TO_ARM_CHANNEL PRU0_TO_ARM_CHANNEL
#define FROM_ARM_CHANNEL PRU0_FROM_ARM_CHANNEL
#endif
#ifdef PRU1
#define TO_ARM_HOST			18
#define FROM_ARM_HOST			19
#define PRU1_TO_ARM_CHANNEL  3
#define PRU1_FROM_ARM_CHANNEL 1
#define PRU0_TO_ARM_CHANNEL  HOST_UNUSED
#define PRU0_FROM_ARM_CHANNEL HOST_UNUSED
#define HOST_INT			0x80000000
#define CHAN_NAME			"rpmsg-pru"
#define CHAN_DESC			"Channel 31"
#define CHAN_PORT			31
#define TO_ARM_CHANNEL PRU1_TO_ARM_CHANNEL
#define FROM_ARM_CHANNEL PRU1_FROM_ARM_CHANNEL
#endif

struct ch_map pru_intc_map[] = { {TO_ARM_HOST, TO_ARM_CHANNEL},
				 {FROM_ARM_HOST, FROM_ARM_CHANNEL},
};

struct my_resource_table {
	struct resource_table base;
	uint32_t offset[2];
	struct fw_rsc_vdev rpmsg_vdev;
	struct fw_rsc_vdev_vring rpmsg_vring0;
	struct fw_rsc_vdev_vring rpmsg_vring1;
	struct fw_rsc_custom pru_ints;
};

#pragma DATA_SECTION(resourceTable, ".resource_table")
#pragma RETAIN(resourceTable)
struct my_resource_table resourceTable = {
	1,	/* Resource table version: only version 1 is supported by the current driver */
	2,	/* number of entries in the table */
	0, 0,	/* reserved, must be zero */
	{
		offsetof(struct my_resource_table, rpmsg_vdev),
		offsetof(struct my_resource_table, pru_ints),
	},

	{
		(unsigned)TYPE_VDEV,                    //type
		(unsigned)VIRTIO_ID_RPMSG,              //id
		(unsigned)0,                            //notifyid
		(unsigned)RPMSG_PRU_C0_FEATURES,	//dfeatures
		(unsigned)0,                            //gfeatures
		(unsigned)0,                            //config_len
		(unsigned char)0,                             //status
		(unsigned char)2,                             //num_of_vrings, only two is supported
		{ (unsigned char)0, (unsigned char)0 },             //reserved
	},
	{
		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),
		{
			0x0000,
			PRU0_FROM_ARM_CHANNEL, PRU1_FROM_ARM_CHANNEL, PRU0_TO_ARM_CHANNEL, PRU1_TO_ARM_CHANNEL, HOST_UNUSED,
			HOST_UNUSED, HOST_UNUSED, HOST_UNUSED, HOST_UNUSED, HOST_UNUSED,
			(sizeof(pru_intc_map) / sizeof(struct ch_map)),
			pru_intc_map,
		},
	},
};

#endif


 

main.c

#include <stdint.h>
#include <pru_cfg.h>
#include <pru_intc.h>
#include <pru_rpmsg.h>
#include "resource_table.h"

volatile register unsigned __R31;

#define VIRTIO_CONFIG_S_DRIVER_OK	4

unsigned char payload[RPMSG_BUF_SIZE];


void main(void)
{
	struct pru_rpmsg_transport transport;
	unsigned short src, dst, len;
	volatile unsigned char *status;

	CT_CFG.SYSCFG_bit.STANDBY_INIT = 0;
	CT_INTC.SICR_bit.STS_CLR_IDX = FROM_ARM_HOST;


	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) {
	        
		
			if (pru_rpmsg_receive(&transport, &src, &dst, payload, &len) == PRU_RPMSG_SUCCESS) {
			
#ifdef PRU0
			  pru_rpmsg_send(&transport,dst, src, "PRU0 responding\n", 17);
#endif
#ifdef PRU1
			  pru_rpmsg_send(&transport,dst, src, "PRU1 responding\n", 17);
#endif
			  pru_rpmsg_send(&transport, dst, src, payload, len);
			} else{
			  CT_INTC.SICR_bit.STS_CLR_IDX = FROM_ARM_HOST;

			}
		}
	}
}


 

There are two places in this code 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 controller 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.

Both of these lines will be fixed when the code for a real time loop is instituted (see … insert reference here).

Linker Command File, hello.cmd

-cr								/* Link using C conventions */
-stack 0x100
-heap 0x100

/* Specify the System Memory Map */
MEMORY
{
      PAGE 0:
	  PRU_IMEM		: org = 0x00000000 len = 0x00002000  /* 8kB PRU0 Instruction RAM */

      PAGE 1:
	  PRU_DMEM_0_1	: org = 0x00000000 len = 0x00002000             CREGISTER=24 /* 8kB PRU Data RAM 0_1 */
	  PRU_DMEM_1_0	: org = 0x00002000 len = 0x00002000	        CREGISTER=25 /* 8kB PRU Data RAM 1_0 */

      PAGE 2:
	  PRU_SHAREDMEM	: org = 0x00010000 len = 0x00003000 CREGISTER=28 /* 12kB Shared RAM */

	DDR			: org = 0x80000000 len = 0x00000100	CREGISTER=31
	L3OCMC			: org = 0x40000000 len = 0x00010000	CREGISTER=30


	/* Peripherals */

	PRU_INTC		: org = 0x00020000 len = 0x00001504	CREGISTER=0
	PRU_CFG			: org = 0x00026000 len = 0x00000044	CREGISTER=4 //From the PRU constants table, 4-9 in TRM
	GEMAC			: org = 0x4A100000 len = 0x0000128C	CREGISTER=9
	MBX0			: org = 0x480C8000 len = 0x00000140	CREGISTER=22

	DMTIMER2		: org = 0x48040000 len = 0x0000005C	CREGISTER=1
	I2C1			: org = 0x4802A000 len = 0x000000D8	CREGISTER=2
	PRU_ECAP		: org = 0x00030000 len = 0x00000060	CREGISTER=3
	MMCHS0			: org = 0x48060000 len = 0x00000300	CREGISTER=5
	MCSPI0			: org = 0x48030000 len = 0x000001A4	CREGISTER=6
	PRU_UART		: org = 0x00028000 len = 0x00000038	CREGISTER=7
	MCASP0_DMA		: org = 0x46000000 len = 0x00000100	CREGISTER=8
	RSVD10			: org = 0x48318000 len = 0x00000100	CREGISTER=10
	UART1			: org = 0x48022000 len = 0x00000088	CREGISTER=11
	UART2			: org = 0x48024000 len = 0x00000088	CREGISTER=12
	RSVD13			: org = 0x48310000 len = 0x00000100	CREGISTER=13
	DCAN0			: org = 0x481CC000 len = 0x000001E8	CREGISTER=14
	DCAN1			: org = 0x481D0000 len = 0x000001E8	CREGISTER=15
	MCSPI1			: org = 0x481A0000 len = 0x000001A4	CREGISTER=16
	I2C2			: org = 0x4819C000 len = 0x000000D8	CREGISTER=17
	PWMSS0			: org = 0x48300000 len = 0x000002C4	CREGISTER=18
	PWMSS1			: org = 0x48302000 len = 0x000002C4	CREGISTER=19
	PWMSS2			: org = 0x48304000 len = 0x000002C4	CREGISTER=20
	RSVD21			: org = 0x00032400 len = 0x00000100	CREGISTER=21
	SPINLOCK		: org = 0x480CA000 len = 0x00000880	CREGISTER=23

	PRU_IEP			: org = 0x0002E000 len = 0x0000031C	CREGISTER=26
	RSVD27			: org = 0x00032000 len = 0x00000100	CREGISTER=27

        TPCC			: org = 0x49000000 len = 0x00001098	CREGISTER=29

}

/* Specify the sections allocation into memory */
SECTIONS {
	/* Forces _c_int00 to the start of PRU IRAM. Not necessary when loading
	   an ELF file, but useful when loading a binary */
	.text:_c_int00*	>  0x0, PAGE 0

	.text		>  PRU_IMEM, PAGE 0
	.stack		>  PRU_DMEM_0_1, PAGE 1
	.bss		>  PRU_DMEM_0_1, PAGE 1
	.cio		>  PRU_DMEM_0_1, PAGE 1
	.data		>  PRU_DMEM_0_1, PAGE 1
	.switch		>  PRU_DMEM_0_1, PAGE 1
	.sysmem		>  PRU_DMEM_0_1, PAGE 1
	.cinit		>  PRU_DMEM_0_1, PAGE 1
	.rodata		>  PRU_DMEM_0_1, PAGE 1
	.rofardata	>  PRU_DMEM_0_1, PAGE 1
	.farbss		>  PRU_DMEM_0_1, PAGE 1
	.fardata	>  PRU_DMEM_0_1, PAGE 1

	.resource_table > PRU_DMEM_0_1, PAGE 1
}


There are two instructions in the linker command file 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.

TI copyright statement

/*
 * Copyright (C) 2016 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.
 */

 


Once the executable file has been compiled, the method of loading onto the pru is a bit convoluted.

The executable must be copied to the /lib/firmware directory and placed in one of two very specific locations:

/lib/firmware/am335x-pru0-fw

/lib/firmware/am335x-pru1-fw

Remoteproc will load those files into the appropriate pru (0 or 1) at the appropriate moment.

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.  In order to load new code, the module has to be removed and reinserted.  Although this is awkward in development, it makes sense for production where this would happen at boot time.

rmmod -f pru_rproc

will remove the module.

modprobe pru_rproc

will load the firmware into the appropriate pru.

Run ‘dmesg’ to see if everything went smoothly.

The Makefile has 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.