Welcome, Guest. Please login or register.

Author Topic: Issues with serial communications in C  (Read 3411 times)

Description:

0 Members and 1 Guest are viewing this topic.

Offline cakeisalie5Topic starter

  • Newbie
  • *
  • Join Date: Oct 2024
  • Posts: 2
  • Country: fr
  • Gender: Male
    • Personal website
Issues with serial communications in C
« on: October 02, 2024, 04:38:36 PM »
Hi! I'm trying to complete the port of Cahute, a library and set of communication tools for CASIO calculators from 1991 to this day, to AmigaOS 3.2 and above in the context of this issue.

In this context, I'm trying to implement serial communication using the serial device described in Serial Device, by implementing an "AmigaOS serial medium" for the Cahute link medium interface. I've implemented the following:

- Link medium opening, by opening two message ports for read & write, two IORequest objects, opening the serial device on the first IORequest, and copying it on the second IORequest, then setting the write message port to the second IORequest again.
- Link medium closing.
- Reading from the link medium, with timeout.
- Writing to the link medium.
- Setting the serial settings.

My testing steps are described in issue #26 ("Testing notes" dropdown, with screens), but basically, I start an A1200 in FS-UAE with serial_port defined to my USB/serial device, open Serial monitor on the calculator, then see what both receive in the communication.

From what I have gathered, sending bytes from Cahute to the calculator works, which also means the "serial settings" part works as well. However, when I try to receive bytes from the calculator, two problems arise:

- The serial msgport never sends a signal, which means the function always gets interrupted by either a timeout or a SIGBREAKF_CTRL_C.
- If a timeout occurs, the next receive doesn't wait for the duration provided with the callback, which basically means WaitIO() on the timer request seems to not reset the timer device (which is the same between calls).

Note that these bugs also occurred when I only had one IORequest for both reading and writing, and separating the two was an attempt for me to solve the problem.

I have used the instructions detailed in High speed operation, and added timeouts since it is required by the protocol implementations.

For convenience, the current read implementation is the following:

Code: (c) [Select]
struct timerequest *timer;
struct IOExtSer *io = medium->state.amigaos_serial.read_io;
struct MsgPort *timer_msgport;
struct MsgPort *serial_msgport = medium->state.amigaos_serial.read_msg_port;
cahute_u32 signals = 0;
int has_serial_signal = 0;
size_t unread_bytes;

/* We need to query the number of bytes currently unread.
 * If there is at least one, we read the maximum we can (either
 * limited by the buffer size, or the number of unread bytes).
 * Otherwise, we wait for at least one byte to be available. */
io->IOSer.io_Command = SDCMD_QUERY;
if (DoIO((struct IORequest *)io)) {
    msg(ll_error,
        "Error %d occurred while checking device status.",
        io->IOSer.io_Error);
    return CAHUTE_ERROR_UNKNOWN;
}

bytes_read = 0;
if (!io->IOSer.io_Actual) {
    /* We want to make a read of 1 byte with timeout, until
     * there the byte is provided. */
    err = cahute_get_amiga_timer(&timer_msgport, &timer);
    if (err)
        return err;

    /* Run two operations at once:
     * - Read into the buffer.
     * - Start a timer to the currently requested timeout. */
    io->IOSer.io_Command = CMD_READ;
    io->IOSer.io_Length = 1;
    io->IOSer.io_Data = (APTR)dest;

    SendIO((struct IORequest *)io);
    signals = (1L << serial_msgport->mp_SigBit) | SIGBREAKF_CTRL_C;

    if (timeout > 0) {
        timer->tr_time.tv_secs = timeout / 1000;
        timer->tr_time.tv_micro = timeout % 1000 * 1000;
        timer->tr_node.io_Command = TR_ADDREQUEST;

        SendIO((struct IORequest *)timer);
        signals |= (1L << timer_msgport->mp_SigBit);
    }

    if (CheckIO((struct IORequest *)io)) {
        /* Request has terminated immediately.
         * Note that we may have started a timer request for
         * nothing here, but we want to have this CheckIO() call
         * as close as possible to the Wait() to avoid race
         * conditions as much as possible. */
        signals = 0;
        has_serial_signal = 1;
    } else {
        /* We want to wait only if the request has not finished
            * immediately. */
        signals = Wait(signals);
        has_serial_signal =
            signals & (1L << serial_msgport->mp_SigBit);
    }

    if (timeout > 0) {
        if (!CheckIO((struct IORequest *)timer))
            AbortIO((struct IORequest *)timer);

        WaitIO((struct IORequest *)timer);
    }

    /* Wait for either completion and clearing of serial read, or
        * for cancellation of I/O request. */
    if (!has_serial_signal)
        AbortIO((struct IORequest *)io);

    WaitIO((struct IORequest *)io);

    if (signals & SIGBREAKF_CTRL_C)
        return CAHUTE_ERROR_ABORT;

    /* From here, the serial signal may or may not have been set.
     * If yes, this means a byte has been received, and more may
     * still be available.
     * Otherwise, this means a byte has not been read, but due to
     * a race condition, it is possible that the bytes could have
     * been read between the initial status check and read, so
     * we still want to check the status here.
     *
     * Note that in the first case, the buffer we provide to the
     * system for read later is unaligned (1 past 32-byte
     * alignment guaranteed in linkopen.c). */
    if (has_serial_signal) {
        if (io->IOSer.io_Error) {
            msg(ll_error,
                "Error %d occurred while reading from device.",
                io->IOSer.io_Error);
            return CAHUTE_ERROR_UNKNOWN;
        }

        dest++;
        target_size--;
        bytes_read++;
    }

    io->IOSer.io_Command = SDCMD_QUERY;
    if (DoIO((struct IORequest *)io)) {
        msg(ll_error,
            "Error %d occurred while checking device status.",
            io->IOSer.io_Error);
        return CAHUTE_ERROR_UNKNOWN;
    }

    if (!io->IOSer.io_Actual)
        goto time_out;
}

/* Make a synchronous read of all available bytes.
 * According to the AmigaOS Wiki, this operation is guaranteed
 * to return without waiting. */
unread_bytes = io->IOSer.io_Actual;
if (unread_bytes > target_size)
    unread_bytes = target_size;

io->IOSer.io_Command = CMD_READ;
io->IOSer.io_Length = unread_bytes;
io->IOSer.io_Data = (APTR)dest;

if (DoIO((struct IORequest *)io)) {
    msg(ll_error,
        "Error %d occurred while reading available bytes.",
        io->IOSer.io_Error);
    return CAHUTE_ERROR_UNKNOWN;
}

bytes_read += unread_bytes;

I have run out of ideas on why these bogus behaviours occur, and they block official support of AmigaOS within Cahute. Thanks in advance if you have any idea! :-)
« Last Edit: October 02, 2024, 04:39:41 PM by cakeisalie5 »
BDFL of the Cahute project, for CASIO calculators from 1991 to this day.
 

Offline cakeisalie5Topic starter

  • Newbie
  • *
  • Join Date: Oct 2024
  • Posts: 2
  • Country: fr
  • Gender: Male
    • Personal website
Re: Issues with serial communications in C
« Reply #1 on: June 13, 2025, 09:57:54 AM »
In the end, it was an issue with FS-UAE, the emulator I used. See fs-uae#367 for more information.
BDFL of the Cahute project, for CASIO calculators from 1991 to this day.