Couldn't wait for my Pyra, so I bought a OMAP5432 devboard


This is my main.c code, with some include statements and comments removed for brevity:

I think my next step is to either get an RPMsg demo up and running or start poking the TWL6037 PMIC. Not sure which one is easiest :) Maybe I'll start with simply flipping a GPIO pin?
Maybe you could try to send I2C commands to the LED controllers and make them fancy blinking :)
Or read out the Fuel Gauge chip.
What I have no idea is how to prevent the kernel on the A15 cores from trying the same at the same moment...
 
Maybe you could try to send I2C commands to the LED controllers and make them fancy blinking :)
Or read out the Fuel Gauge chip.
What I have no idea is how to prevent the kernel on the A15 cores from trying the same at the same moment...
The OMAP4 introduce some kind of HW spinlock device for synchronization between processors, bit I haven't looked into it much. This article seem to indicate that the I2C driver is already using this synchronization: https://lwn.net/Articles/425638/
 
Did some more experimenting today, but got stuck. The plan was to be able to run one of TI's examples, but no matter what I tried, it died on an assertion in early boot. I got the exact same issue on both TI example code (modified for OMAP5432) and their verification test code (written specifically for OMAP5432) :mad:

The error log goes something like this:
Code:
# tail -F /sys/kernel/debug/remoteproc/remoteproc1/trace0
[0][      0.000] 16 Resource entries at 0x3000
[0][      0.000] [t=0x00024cdf] xdc.runtime.Main: --> main:
[0][      0.000] [t=0x0003d19b] ti.ipc.transports.TransportRpmsgSetup: TransportRpmsgSetup_attach: procId=0
[0][      0.000] [t=0x00054c65] ti.ipc.transports.TransportRpmsg: TransportRpmsg_Instance_init: remoteProc: 0
[0][      0.000]
[0][      0.000] [t=0x0006ca9f] ti.ipc.rpmsg.RPMessage: --> RPMessage_init: (remoteProcId=0)
[0][      0.000] [t=0x00082145] ti.ipc.family.omap54xx.VirtQueue: VirtQueue_init: Initialized!
[0][      0.000]
[0][      0.000] [t=0x000996bb] ti.ipc.family.omap54xx.VirtQueue: vring: 0 0x0 (0x3000)
[0][      0.000]
[0][      0.000] [t=0x000b0b47] ti.ipc.family.omap54xx.VirtQueue: VirtQueue_init: Initialized!
[0][      0.000]
[0][      0.000] [t=0x000c7531] ti.ipc.family.omap54xx.VirtQueue: vring: 1 0x0 (0x3000)
[0][      0.000]
[0][      0.000] [t=0x000dfd43] ti.ipc.family.omap54xx.VirtQueue: VirtQueue_startup: VDEV status: 0x0
[0][      0.000]
[0][      0.000] [t=0x000f4659] ti.ipc.family.omap54xx.VirtQueue: VirtQueue_startup: Polling VDEV status...
[0][      0.000]
[0][      0.000] [t=0x002066df] ti.ipc.family.omap54xx.VirtQueue: VirtQueue_startup: VDEV status: 0x7
[0][      0.000]
[0][      0.000] [t=0x002203af] ti.ipc.family.omap54xx.VirtQueue: Passed VirtQueue_startup
[0][      0.000]
[0][      0.000] [t=0x00233b35] ti.ipc.rpmsg.RPMessage: <-- RPMessage_init
[0][      0.000] registering rpmsg-proto:rpmsg-proto service on 61 with HOST
[0][      0.000] [t=0x0024fc5d] xdc.runtime.Main: NameMap_sendMessage: HOST 53, port=61
[0][      0.000] [t=0x00261aeb] ti.ipc.rpmsg.RPMessage: --> RPMessage_send: (dstProc=0, dstEndpt=53, srcEndpt=61, data=0x8006df64, len=72
[0][      0.000] [t=0x0027e997] ti.ipc.family.omap54xx.VirtQueue: getAvailBuf vq: 0x800610d8 0 0 256 0x800610e8 0x1000
[0][      0.000]
[0][      0.000] [t=0x0029acc7] ti.sysbios.knl.Semaphore: ERROR: line 202: assertion failure: A_badContext: bad calling context. Must be called from a Task.
[0][      0.000] ti.sysbios.knl.Semaphore: line 202: assertion failure: A_badContext: bad calling context. Must be called from a Task.
[0][      0.000] xdc.runtime.Error.raise: terminating execution

From what I've been able to figure out by crawling through the IPC code base is that this is the piece of code that is triggering the assert (in ipc_3_40_01_08/packages/ti/ipc/rpmsg/RPMessage.c)
C:
/* Send to remote processor: */
do {
    token = VirtQueue_getAvailBuf(transport.virtQueue_toHost,
            (Void **)&msg, &length);
} while (token < 0 && Semaphore_pend(transport.semHandle_toHost,
                                     BIOS_WAIT_FOREVER));

I don't think there's a problem with this code in itself. I believe there's a configuration issue somewhere. I've analyzed the VirtQueue code a bit and, from the logs above, it looks like it expects to find its virtio vring queue at address 0x0! From ipc_3_40_01_08/packages/ti/ipc/family/omap54xx/VirtQueue.c:
C:
switch (vq->id) {
    /* IPC transport vrings */
    case ID_SELF_TO_HOST:
    case ID_HOST_TO_SELF:
        vq->basePa = (UInt32)Resource_getVringDA(vq->id);
        Assert_isTrue(vq->basePa != NULL, NULL);

        result = Resource_physToVirt(vq->basePa, &(vq->baseVa));
        Assert_isTrue(result == Resource_S_SUCCESS, (Assert_Id)NULL);

        vringAddr = (Void *)vq->baseVa;
        break;
    default:
        GateHwi_delete(&vq->gateH);
        Memory_free(NULL, vq, sizeof(VirtQueue_Object));
        return (NULL);
}   

Log_print3(Diags_USER1,
        "vring: %d 0x%x (0x%x)\n", vq->id, (IArg)vringAddr,
        RP_MSG_RING_SIZE);
These Resource_*() functions are for parsing the resource table I've been talking about in my earlier posts. I did a quick experiment and switched Resource_physToVirt() with Resource_virtToPhys() and it got me a bit further, but eventually hit another assert. Not sure if that's a correct fix or I just got lucky that time. There might have been some changes on the Linux side that made this old IPC version incompatible.

I'll have to dig deeper another day. I'm off now for another week :) Just wanted to write down some of the progress at least.

PS.
The TI code infrastructure is a beast! You've got C code generation based on java classes and templates, with some kind of homemade inheritance written/generated in C! Makes following the code a real pain :mad:
DS.
 
Last edited:
Okay, did some last minute testing before vacation travels. Looks like the ping_rpmsg test firmware doesn't crash. However, now I'm stuck fixing their userspace application instead. Their build system didn't pick up the headers from my kernel and got the wrong value for AF_RPMSG. Luckily, their build system also allowed to override the value at build time :) That got me past the socket() call, right into a failed connect() xD

Now I really have to get moving if I wanna catch my train! See you in a week or so :)
 
Well this is almost embarrassing o_O how could this have gone through QA?

Here's a patch that fixes their MessageQ example code:
Diff:
diff -Naur ipc_3_40_01_08/packages/ti/ipc/family/omap54xx/VirtQueue.c{.orig,}
--- ipc_3_40_01_08/packages/ti/ipc/family/omap54xx/VirtQueue.c.orig    2022-07-25 22:44:49.029767723 +0200
+++ ipc_3_40_01_08/packages/ti/ipc/family/omap54xx/VirtQueue.c    2022-07-25 23:15:06.877920740 +0200
@@ -448,10 +448,10 @@
         /* IPC transport vrings */
         case ID_SELF_TO_HOST:
         case ID_HOST_TO_SELF:
-            vq->basePa = (UInt32)Resource_getVringDA(vq->id);
-            Assert_isTrue(vq->basePa != NULL, NULL);
+            vq->baseVa = (UInt32)Resource_getVringDA(vq->id);
+            Assert_isTrue(vq->baseVa != NULL, NULL);
 
-            result = Resource_physToVirt(vq->basePa, &(vq->baseVa));
+            result = Resource_virtToPhys(vq->baseVa, &(vq->basePa));
             Assert_isTrue(result == Resource_S_SUCCESS, (Assert_Id)NULL);
 
             vringAddr = (Void *)vq->baseVa;

I'll continue with a more scaled down, pure RPMSG example next. But that'll have to wait a few days - maybe a week
 
Okay, some progress! :D

I got the TI example project ping_rpmsg working, which means I can send RPMSG between the two processors.

On the Linux side, it works basically like network connections:
  1. Create a socket()
  2. For sending:
    1. connect() to a remote address+processor ID
    2. send()
  3. For receiving:
    1. bind() to an address+remote processor ID where you expect data from
    2. Use your favorite select()/poll()/epoll() to wait for data
    3. recvfrom()
I had to apply this changeset to my version of TI IPC (3.40.1.08) to make it work properly: https://git.ti.com/gitweb/?p=ipc/ipcdev.git;a=commit;h=d8e7b6ecb9b97522d58f7e6f5a1662793ed5f9ca

I've uploaded my test project for the Cortex-M4 here: https://github.com/Risca/hello_rpmsg

I think my next step would be to either wiggle a GPIO pin or go straight for the I2C peripheral. It would also be nice to experiment with the inter-processor hardware semaphores (Spinlock module in the TRM) to avoid future race conditions.
What do you guys think?

EDIT:
Some more changes I had to make to properly build ipc_3_40_01_08/linux/src/tests/ping_rpmsg.c:
Diff:
--- ipc_3_40_01_08-vanilla/linux/src/tests/ping_rpmsg.c    2015-09-11 20:39:10.000000000 +0200
+++ ipc_3_40_01_08/linux/src/tests/ping_rpmsg.c    2022-08-17 00:24:14.334741133 +0200
@@ -47,7 +47,12 @@
 #include <time.h>
 
 /* Ipc Socket Protocol Family */
-#include <net/rpmsg.h>
+#include <linux/rpmsg_socket.h>
+//#include <net/rpmsg.h>
+
+#if AF_RPMSG != 45
+#error AF_RPMSG != 45!
+#endif
Without this patch, the code will not find the definition of AF_RPMSG from the kernel I'm currently using (5.10.120) and fall back to an (outdated) net/rpmsg.h header that is included in the TI IPC package!
I also had to change CORE_ID_DFLT from 0 to 1 or simply run the command with 2 parameters:
Code:
# ping_rpmsg 2 1
AF_RPMSG: 45
Connecting to address 0x33 on vprocId 1
Our address: socket family: 45, proc id = -1, addr = 1024
Binding to address 0x33 on vprodId 1
Our address: recv_sock: socket family: 45, proc id = -1, addr = 51
Sending "Ping!" in a loop.
0: Received msg: Pong!, from: 51
1: Received msg: Pong!, from: 51
Avg time: 2933 usecs over 2 iterations
#
 
Last edited:
What do you guys think?
I'm as nervous as if you are asking me "do I cut the blue or the red wire". I'm a software man, tinkering with hardware makes me nervous. I think it started(*) when I blew up my monitor using software (iirc by setting the colordepth to 12 instead of 8, 16 or 24). I still remember the white mushroom cloud of smoke generated. And the feeling of "goodbye" that my trusty monitor gave me. See, now you made me cry! Bad Risca! ;)

I also short-circuited my folks house when I connected an XT powersupply wrongly (it was a 50-50% chance, and I took it). I also learned the hard way that you don't take out C64 cartridges while the C64 is on, or you risk frying the SID chip. And then I couldn't stop a friend from doing that to my machine.

So... wiggle the GPIO pin, unless it's blue. Don't wiggle blue.
 
I vote for locking semaphores if it needs done still. A proper project on the M4's gonna need some RAM and message passing so you've some idea of what it's doing, and I'd have thought all that needs semaphores to work.
 
I vote for locking semaphores if it needs done still. A proper project on the M4's gonna need some RAM and message passing so you've some idea of what it's doing, and I'd have thought all that needs semaphores to work.
The message passing mechanism RPMSG should already handle synchronization. IIRC, rpmsg is built on two shared RAM areas - one in each direction - for messages and then interrupts to notify the other side a new message has arrived. Synchronization is part of the specs.

My use case is the few select drivers which would compete with e.g. the i2c bus when talking to a device. When I first glanced at the TWL6037 driver, it even looked like reading the ADC requires multiple i2c transaction. What happens if the Cortex-M4 reads the ADC at the same time as Linux is changing some other setting in the PMIC, and we only synchronize individual i2c transactions? This can of course be worked around by using rpmsg to tell the M4 to "stop hogging the i2c - I'm using it now!", but I think hw semaphores are simpler and more elegant.

Of course, for testing I can ignore synchronization altogether and just live with some occasional conflict and weird behavior; or simply use a GPIO pin that nobody else is using. In the end, I need to learn how to talk to peripherals and how to use hw semaphores, unless someone comes up with another solution that avoids these race conditions altogether. Suggestions?
 
Last edited:
This can of course be worked around by using rpmsg to tell the M4 to "stop hogging the i2c - I'm using it now!",
Wouldn't that need be synchronised as well? Either the recipient gives you real time guaranties, then you'd have to wait the interval and could be sure the message was processed. Or you'd have to wait for the ACK. ?

I don't know the first thing about i2c protocols, but I'd expect low level collision detection - one sender stops sending in the event. Maybe the participants could listen to messages not meant for them and then know when not to do what and for how long or until which other event. ?
 
Wouldn't that need be synchronised as well? Either the recipient gives you real time guaranties, then you'd have to wait the interval and could be sure the message was processed. Or you'd have to wait for the ACK. ?
Yes, it would be a horrible solution. What if the M4 has crashed and is unable to send an ack? I think hw semaphores is the way to go.

I don't know the first thing about i2c protocols, but I'd expect low level collision detection - one sender stops sending in the event. Maybe the participants could listen to messages not meant for them and then know when not to do what and for how long or until which other event. ?
The problem is that it's not multiple senders, it's two processors using the same peripheral. The device we're talking to has no idea which of the processors initiated the communication. In the TWL6037 ADC case, communication goes a little like this:
  1. "Hey TWL6037, start an ADC conversion!"
  2. TWL6037 finishes and raises an interrupt
  3. "Hey TWL6037, please give me the values"
What if my code in the M4 would tell the TWL6037 to start a new conversion right after step 2 above? :mad:
 
over my head, well the details are i get the general gist of what you guys are talking about. I think it was in the early 80's i last programmed at a hardware level...

Going by your example, does the requset on the I2C bus contain any info of what asked for the conversion to start? I would assume if you were to ask for a second conversion before the first was completed it would be ignored? or queued?

A quick google on i2c-bus . org / multimaster https://www.i2c-bus.org/multimaster/ but you guys probably already know this so
 
Yes i2c is a four wire bus as I recall, just ground, +V, date and clock. There's nothing to say hold this bus, or bus in use or any of that nice to have, so either you have to take this multimaster approach of sitting and monitoring the bus and guessing that it's free, or you do something more robust and performant using an external communications.
 
Yes i2c is a four wire bus as I recall, just ground, +V, date and clock. There's nothing to say hold this bus, or bus in use or any of that nice to have, so either you have to take this multimaster approach of sitting and monitoring the bus and guessing that it's free, or you do something more robust and performant using an external communications.
The OMAP5432 I2C HW supports multi master mode natively, which means that two masters on the same bus wouldn't cause an issue. However, this is not a multi master situation. Both processors will be accessing the very same peripheral:
Code:
              +---------+
Cortex-A15 ---|  L3/L4  |
              |  Inter- +--- I2C HW --- TWL6037
Cortex-M4  ---| connect |
              +---------+
In fact, a multi master situation would probably be preferable :) Then the M4 processor could have an entire peripheral to itself instead of sharing it:
Code:
Cortex-A15 --- I2C HW1 ---+
                          |
                          +--- TWL6037
                          |
Cortex-M4  --- I2C HW2 ---+
But that requires HW changes... :oops:

And it still doesn't solve the underlying problem. Even if each individual i2c transaction is properly synchronized, there's nothing preventing one processor giving commands to the TWL6037, changing it's state, and confusing the other processor.

Going by your example, does the requset on the I2C bus contain any info of what asked for the conversion to start? I would assume if you were to ask for a second conversion before the first was completed it would be ignored? or queued?
Unfortunately, I don't have the datasheet for the TWL6037 so I don't know what happens. It might restart the conversion :oops: I also don't know what happens in this situation:
  1. CPU1 tells it to start a conversion
  2. TWL6037 completes the conversion and signals this
  3. CPU2 tells it to start a conversion
  4. CPU1 reads the value

All this is mostly hypothetical right now. Testing is required!
 
Last edited:
I don't think periherals usually carry a conversation over i2c that's more compicated than question->answer. i2c is usually used to connect to things like temperature sensors, so the only thing you can ask them is what's the temperature? and they reply with the temperature in whatever units they use. There are more complex sensors attached that have status registers, but I'm having trouble coming up with a use case for that.
 
I don't think periherals usually carry a conversation over i2c that's more compicated than question->answer. i2c is usually used to connect to things like temperature sensors, so the only thing you can ask them is what's the temperature? and they reply with the temperature in whatever units they use. There are more complex sensors attached that have status registers, but I'm having trouble coming up with a use case for that.
There are definitely more complicated devices, with internal state, that can be connected to i2c. The majority of i2c devices are simple though, and more complex devices usually end up connected to SPI or some other bus instead.

Just by reading the TWL6037 ADC driver, there are at least 2 transactions needed to do an ADC reading. Some other driver I found did it in 3 transactions, but the mainline driver optimized it to only two. And that's only the driver that handles ADC values from the WL6037. There is another driver for controlling its GPIO pins and yet another for controlling its power outputs. The circuit is quite complex and definitely has multiple internal states that the Linux drivers keep track of.

I wonder if it's possible to put the ADC in "freerunning" mode, i.e. continuously reading, and then reading the value would always only be 1 transaction? :) A datasheet would really help...
 
Back
Top