Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 66

AUTOSAR Pointer Usage in Communication Middleware

Recommended resources

Hello! Welcome back to our course on C pointers.

In our last lesson, we built a robust inter-process communication system using shared memory and semaphores. We saw how the producer-consumer pattern, protected by synchronization primitives, allows for the safe exchange of data. This fundamental pattern is at the heart of many complex software systems, including the ones you work with every day.

Today, we will bridge that low-level concept to your professional domain. The learning outcome for this lesson is to analyze pointer usage patterns in AUTOSAR communication middleware. You'll see that the entire AUTOSAR communication stack, from the Application Layer down to the drivers, can be viewed as a highly structured, multi-stage producer-consumer system, where pointers are the primary mechanism for passing data between layers.

Given your extensive experience with AUTOSAR middleware, we won't dwell on the high-level concepts. Instead, we'll dive straight into the C-level implementation details, focusing on how pointers make it all work.

1. The Big Picture: Data Flow and the PduInfoType

At every boundary in the AUTOSAR communication stack, data is passed in the form of a Protocol Data Unit (PDU). The "vehicle" for carrying this data between layers is a standard structure defined by AUTOSAR: PduInfoType.

typedef struct {
    uint8* SduDataPtr; // Pointer to the Service Data Unit (the payload)
    PduLengthType SduLength; // Length of the payload
} PduInfoType;

This simple structure is central to our discussion. Notice its key member: SduDataPtr, a pointer to an array of bytes that contains the actual data payload. Almost every function in the communication stack, from Com to PduR to CanIf, will pass data by passing a pointer to a PduInfoType variable.

AUTOSAR Communication Stack Architecture with CAN TP Position
This diagram shows the layered architecture of the AUTOSAR communication stack. Data, encapsulated in PDUs, flows vertically between layers like COM, PduR, and the interface modules (CanIf, LinIf, etc.). Pointers are used to pass these PDUs at each interface.

Let's trace the journey of a signal from an Application Software Component (SWC) all the way to the CAN bus, examining the pointer patterns at each step.


2. The "Write" Path: From Application to Driver

This section will follow the lifecycle of a signal being transmitted. We will see a recurring pattern: data is passed down the stack by passing pointers as function arguments.

The following resource provides a fantastic, concise summary of the function calls involved in transmitting a signal. We will use it as a reference throughout this section.

1/3) 1. Rte_Write_Xxx() Purpose: Used by the Application ...

This document, '1/3) 1. Rte_Write_Xxx() Purpose: Used by the Application ...', provides a clear, step-by-step trace of a signal transmission through the AUTOSAR stack, including the C function prototypes. It's a perfect guide for our analysis.

Read through the four main points in this section, from Rte_Write_Xxx() to CanIf_Transmit(). Pay close attention to the function prototypes and how pointers are used in each call, specifically const DataType* Data, const void* SignalDataPtr, and const PduInfoType* PduInfoPtr.

Let's break down that flow.

Step 1: Application SWC to RTE

  • Function: Std_ReturnType Rte_Write_P_Port_DE_Element(const DataType* Data);
  • Pointer Pattern: The application calls Rte_Write and passes the address of the variable it wants to send (e.g., Rte_Write_SpeedPort_VehicleSpeed(&VehicleSpeed);). This is the classic "pass-by-reference" pattern using a pointer. The RTE can now access the application's data.
  • const Correctness: The const qualifier on const DataType* Data is a contract. It tells the application, "The RTE promises not to modify your original data; it will only read it."

Step 2: RTE to COM

  • Function: Std_ReturnType Com_SendSignal(SignalIdType SignalId, const void* SignalDataPtr);
  • Pointer Pattern: The RTE, having received the data, now passes it to the Communication (COM) module. It again passes a pointer to the data.
  • void* for Genericity: Notice the use of void*. The COM module is generic; it handles signals of all different data types (uint8, uint16, arrays, etc.). A void* allows Com_SendSignal to accept a pointer to any kind of data. The COM module knows the true data type from its configuration (via SignalId) and will perform the necessary type casting internally to handle the data correctly.

Step 3: COM to PDU Router (PduR)

  • Function: Std_ReturnType PduR_ComTransmit(PduIdType PduId, const PduInfoType* PduInfoPtr);
  • Pointer Pattern: This is a key step. The COM module has packed the signal (and others) into a PDU buffer. It populates a PduInfoType struct, setting SduDataPtr to point to this buffer. It then calls PduR_ComTransmit, passing a pointer to the PduInfoType struct.
  • Multi-level Pointers: We now have a pointer (PduInfoPtr) to a struct that itself contains a pointer (SduDataPtr). This is a very common and efficient pattern for passing compound data objects without copying them.

Step 4: PduR to CAN Interface (CanIf)

  • Function: Std_ReturnType CanIf_Transmit(PduIdType Hth, const PduInfoType* PduInfoPtr);
  • Pointer Pattern: The PduR's job is to route. Based on the PduId, it determines that this PDU goes to the CAN bus and calls CanIf_Transmit. Critically, it just forwards the same pointer (PduInfoPtr) it received from COM. No data is copied. The PduR is simply redirecting the flow of information by redirecting the pointer.

From CanIf, the pointer is passed to the CAN Driver, which finally reads the data from the memory location pointed to by PduInfoPtr->SduDataPtr and transmits it over the hardware.


3. The "Read" Path and Function Pointers for Callbacks

The process for receiving data is largely the reverse, but it introduces another critical pointer pattern: function pointers for callbacks.

When a CAN frame arrives, the flow is roughly:

  1. CAN Driver -> CanIf: A hardware interrupt occurs. The driver reads the frame into a buffer.
  2. CanIf -> PduR: CanIf determines the PduId from the CAN ID and notifies the PduR by calling PduR_CanIfRxIndication(PduId, &pduInfo).
  3. PduR -> COM: PduR routes the PDU to COM by calling Com_RxIndication(PduId, &pduInfo).
  4. COM -> RTE -> App SWC: COM unpacks the signals from the PDU buffer. The application can then read the value using Rte_Read_Port_Element(&data).

The ...RxIndication functions are callbacks. The upper layer (e.g., PduR) provides the lower layer (e.g., CanIf) with a function to call when data is ready. In a typical AUTOSAR configuration, these connections are stored in configuration tables as function pointers.

// Simplified example of a CanIf configuration table
const CanIf_RxPduConfigType CanIf_RxPduConfig[] = {
    // ... other PDUs
    {
        .PduId = 10,
        .TargetPduId = 10,
        .UserRxIndication = &PduR_CanIfRxIndication // Function pointer!
    },
    // ...
};

// Inside CanIf, when PDU with ID 10 is received:
PduInfoType receivedPdu = { .SduDataPtr = rxBuffer, .SduLength = 8 };
// Retrieve the function pointer from the config table and call it
CanIf_RxPduConfig[config_index].UserRxIndication(
    CanIf_RxPduConfig[config_index].TargetPduId,
    &receivedPdu
);

This callback mechanism, enabled by function pointers, is fundamental to the event-driven nature of the communication stack.

The following video provides a more detailed look at how the RTE manages the data exchange between SWCs and how these high-level interactions are translated into C code.

Deep Dive into AUTOSAR Run Time Environment

The video 'Deep Dive into AUTOSAR Run Time Environment' explains how the RTE facilitates communication, mapping AUTOSAR concepts to C code. This will help connect the Rte_Write/Rte_Read APIs to the underlying data exchange.

Watch the section from 23:33 to 29:10. Focus on the C code examples for Rte_Write and Rte_Read. Notice how the Write API passes the data to the RTE, and the Read API uses a pointer argument to return the data to the calling SWC.

Test your understanding!

You are debugging a new SWC. The SWC is supposed to read a uint32_t sensor value via Rte_Read_SensorPort_Value(), but the variable in your SWC is never updated and remains zero. A junior colleague wrote the call like this:

Rte_Read_SensorPort_Value(mySensorValue);

What is the likely error, and how would you correct it?

Show answer

The error is that the value of the variable mySensorValue is being passed to the function, not its memory address. This is pass-by-value. The Rte_Read function needs to know where to store the sensor data it has, so it requires a pointer to the variable.

The correction is to use the address-of operator (&) to pass a pointer to the variable:

Rte_Read_SensorPort_Value(&mySensorValue);

This is a classic "pass-by-reference" error. The Rte_Read function's prototype is Std_ReturnType Rte_Read_...(uint32_t* data), and it will write the sensor value to the memory location pointed to by data.


4. Pointers in Gatewaying

Your experience in gateway development makes this a particularly relevant area. From a C implementation perspective, PDU-level gatewaying is an exercise in pointer manipulation.

When the PduR receives a PDU on one bus (e.g., CAN) that needs to be transmitted on another (e.g., Automotive Ethernet), it performs these steps:

  1. The PduR_CanIfRxIndication function is called with a PduInfoType* pointing to the received CAN PDU data.
  2. The PduR's routing logic determines that this PDU must be gatewayed.
  3. The PduR may need to acquire a new buffer for the outgoing Ethernet PDU, as frame formats and sizes can differ. It copies the data from the source buffer (via SduDataPtr) to the destination buffer.
  4. The PduR then calls the transmit function for the target bus, e.g., SoAd_IfTransmit(), with a new PduInfoType* pointing to the outgoing PDU data.

The video below explains the different routing paths within PduR, including gatewaying.

PDUR Module & Communication Gateway configuration in Autosar (AutoEConnect Sessions)

The video 'PDUR Module & Communication Gateway' explains how the PduR handles routing, including the distinction between API forwarding (up/down the stack) and Gateway routing (between different channels).

Watch from 16:06 to 22:20. Focus on the explanation of 'API forwarding' versus 'Gateway routing'. Visualize how in gatewaying, a PDU pointer received from one interface (like CanIf) is processed and then a new PDU pointer is sent to another interface (like Socket Adapter).


5. Adaptive AUTOSAR: Smart Pointers and Zero-Copy

The world is moving towards software-defined vehicles, and with it, Adaptive AUTOSAR (ara::com). Here, the C++ language allows for even more sophisticated pointer patterns to optimize communication, particularly using smart pointers to achieve zero-copy data transfer.

In ara::com, a service provider (Skeleton) can send an event using an "Allocate/Send" pattern:

  1. Allocate(): The provider asks the middleware to allocate memory for the data sample by calling a function like myEvent.Allocate(). This function returns a ara::com::SampleAllocateePtr<SampleType>, which behaves like a std::unique_ptr. Crucially, the middleware can be smart and allocate this memory in a shared memory region accessible by both the provider and the consumer.
  2. Fill Data: The provider uses this smart pointer to fill the allocated memory with data.
  3. Send(): The provider calls myEvent.Send(std::move(mySamplePtr)). The std::move transfers ownership of the smart pointer (and the underlying memory) to the middleware. The provider can no longer access it. The middleware then notifies the consumer that data is available in the shared buffer.

This pattern minimizes data copies. Instead of copying data from the application's heap to a middleware buffer and then to a driver buffer, the data is written once into a shared buffer, and only lightweight pointers and notifications are passed around.

This is a more advanced topic, but it's the modern evolution of the same fundamental problems we've been discussing. The following resource explains this concept in detail.

Explanation of ara::com API

The official 'Explanation of ara::com API' document describes the advanced communication patterns used in Adaptive AUTOSAR. We will focus on the parts that explain the zero-copy mechanism enabled by smart pointers.

Please read the following two sections: Section 6.3.5 Events (from page 78 to 81): Focus on the two Send variants and the Allocate method. Understand why the Allocate/Send pattern with SampleAllocateePtr is provided as an optimization. Section 9.1.1 Zero-Copy implications (page 101): This section explicitly discusses the goal of zero-copy, the role of shared memory, and how serialization needs can sometimes complicate this ideal.


Conclusion

Today we've peeled back the layers of AUTOSAR's communication middleware and found pointers at every turn. They are not just an implementation detail; they are fundamental to the architecture's efficiency and design.

Key Takeaways:

  • Data Transport: The PduInfoType struct, with its SduDataPtr member, is the standard vehicle for data. Pointers to this struct (PduInfoType*) are passed between all layers of the COM stack.
  • API Design: Functions like Rte_Write and Com_SendSignal use pointers to data (const T*) for efficient pass-by-reference semantics, while Rte_Read uses pointers (T*) as "out" parameters.
  • Callbacks: The event-driven nature of the stack relies heavily on function pointers, which are used to register callback functions like PduR_RxIndication in configuration tables.
  • Gatewaying: PDU gatewaying in PduR is fundamentally a process of receiving a pointer to a data buffer from one bus interface and forwarding that data (via another pointer) to a different bus interface.
  • Modern Patterns: Adaptive AUTOSAR (ara::com) evolves these concepts by using C++ smart pointers (SampleAllocateePtr) to manage memory ownership and enable high-performance, zero-copy communication through shared memory.

Preview of the Next Lesson

Understanding these patterns is one thing; debugging them when they go wrong is another. Pointer-related bugs in an embedded, multi-process environment like an ECU can be notoriously difficult to track down.

In our next lesson, we will focus on practice: Debug pointer-related issues in an embedded context using a hardware debugger (e.g., Trace32). We'll discuss common issues like null pointer dereferences, buffer overflows, and corrupted data within PDU buffers, and how to use professional tools to diagnose them.