This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Problem passing pointer argument to reentrant function

Hi All,

TARGET
I am using C51 tools to compile a simple app for an ST uPSD33xx.

OUTLINE
The problem I am experiencing maifests itself as an incorrect value in a pointer passed as an argument to a reentrant function.

DETAILS
OK, I am writing a serial driver. Each channel of the serial driver has a struct to hold pointer to a data buffer, number of bytes, etc, etc. Each struct is static in XDATA as is the data buffer for each channel:

typedef struct serial_data_buffer_t
{
        int8 xdata *pBuffer;
        uint16 read;
        uint16 write;
        uint16 length;
        uint16 bytesInBuffer;
#ifdef SERIAL_HW_FLOW_CONTROL
        uint16 lowWater;
        uint16 highWater;
#endif
} SERIAL_DATA_BUFFER_T;

typedef struct serial_channel_t
{
        uint8 channelNumber;
        int32 baudRate;
        bool flowControlActive;
        bool txRunning;
        SERIAL_DATA_BUFFER_T txBuffer;
        SERIAL_DATA_BUFFER_T rxBuffer;
} SERIAL_CHANNEL_T;

#ifdef USE_SERIAL_PORT1
static int8 xdata serial1TxDataBuffer[SERIAL_PORT1_TX_BUFFER_SIZE];
static int8 xdata serial1RxDataBuffer[SERIAL_PORT1_RX_BUFFER_SIZE];
static SERIAL_CHANNEL_T xdata serialChannel1;
#endif /* #ifdef USE_SERIAL_PORT1 */

#if (defined USE_SERIAL_PORT0) || (defined USE_SERIAL_PORT1)
static void recieveByte (SERIAL_CHANNEL_T *pChannel) reentrant;
static void sendByte (SERIAL_CHANNEL_T *pChannel) reentrant;
static uint16 _writeSerial (char* buffer, uint16 numBytes, SERIAL_CHANNEL_T *pChannel);
#endif /* #if (defined USE_SERIAL_PORT0) || defined (USE_SERIAL_PORT1) */

The pBuffer pointer is initialised at run time using straight forward assignment:

serialChannel1.txBuffer.pBuffer = (int8 xdata*) &serial1TxDataBuffer[0];

When a client wishes to transmit some data the following call chain takes place:

writeSerial1 ("Hello World!\n", strlen ("Hello World!\n"));

calls

bytesSent = _writeSerial (buffer, numBytes, &serialChannel1);

calls

sendByte (pChannel);

The routine sendByte is declared reentrant as it is called from background (as just described) but also from the ISR (to contiune the transmission of the contents of the buffer).

The problem (first) occurs when sendByte is called from _writeSerial. The pChannel argument passed into sendByte is OK prior to the call (as viewed with the debugger), but once inside sendByte the pChannel argument contains a crazy value.

What could be causing this problem? Is there some fundamental problem or rule I am breaking here to cause this problem?

Any help appreciated

Cheers

Andy

Parents
  • As you say, it's an efficiency concern.

    The 8051 architecture is poorly suited to maintaining a stack. It just doesn't have many pointer registers or good indexed addressing modes to take advantage of them if it did. So, the Keil compiler does some fairly clever compile-time analysis to assign parameters and locals to fixed memory locations. The code generator can then generate direct access to the data. The compiler and linker analyze the call tree to know which functions cannot be active at the same time, and thus which memory can be reused in different functions.

    For this purpose, every context is a different call tree. main() has one call tree, and each ISR has a different one. A function shared between main and an ISR, or two ISRs, can't participate in the overlay scheme, because you might be in both copies of the function at once.

    To avoid this problem, you need to make the functions reentrant. As you note, there are special Keil keywords just for this. However, this means that the code generator starts putting all those parametes and locals on a "software stack", maintaining a stack pointer and dragging the data back and forth through the DPTR. The tools make the C pretty painless, but you execute many (many) more instructions and generate larger code for the convenience.

    Some people thus prefer to duplicate functions for ISRs. Usually the idea is to make the ISR faster, but sometimes it might even be smaller code once you eliminate all the stack simulation overhead.

    It's not always a bad idea to make functions reentrant and share them, but true reentrancy is expensive enough on an 8051 that it's worth thinking about.

Reply
  • As you say, it's an efficiency concern.

    The 8051 architecture is poorly suited to maintaining a stack. It just doesn't have many pointer registers or good indexed addressing modes to take advantage of them if it did. So, the Keil compiler does some fairly clever compile-time analysis to assign parameters and locals to fixed memory locations. The code generator can then generate direct access to the data. The compiler and linker analyze the call tree to know which functions cannot be active at the same time, and thus which memory can be reused in different functions.

    For this purpose, every context is a different call tree. main() has one call tree, and each ISR has a different one. A function shared between main and an ISR, or two ISRs, can't participate in the overlay scheme, because you might be in both copies of the function at once.

    To avoid this problem, you need to make the functions reentrant. As you note, there are special Keil keywords just for this. However, this means that the code generator starts putting all those parametes and locals on a "software stack", maintaining a stack pointer and dragging the data back and forth through the DPTR. The tools make the C pretty painless, but you execute many (many) more instructions and generate larger code for the convenience.

    Some people thus prefer to duplicate functions for ISRs. Usually the idea is to make the ISR faster, but sometimes it might even be smaller code once you eliminate all the stack simulation overhead.

    It's not always a bad idea to make functions reentrant and share them, but true reentrancy is expensive enough on an 8051 that it's worth thinking about.

Children
No data