• serial port

    From Paul Edwards@21:1/5 to All on Tue Apr 11 04:18:14 2023
    Hi.

    As far as I can tell from testing, the BIOS INT 14H doesn't
    work on most real hardware I have tried (computers made
    maybe 10 years ago that still have a serial port).

    So I am gearing up to replace that with 80386 PM32 code.

    I have some comms routines for MSDOS (pdcomm) that I
    wrote decades ago, but I want to do it a different way this time.

    Less sophisticated, but more straightforward.

    And designed for a single-tasking system with only one
    CPU enabled (PDOS/386).

    Most of the code can be done in C, but the last bit I want
    to do in assembler.

    Prior to hitting the assembler, I will have installed the
    new interrupt address (which is assembler code, gotint,
    below).

    I'm just trying to confirm the sequence in the final assembler.

    outb port1, transmit_byte
    outb port2, tbemask ; enable transmit buffer empty (only)
    xxx:
    hlt ; this could get interrupted by timer interrupts and
    ; then processing continues so we need a jmp
    jmp xxx
    gotint: ; this is the interrupt address installed by C caller
    outb port2, oldmask ; restore previous interrupt mask (everything disabled)
    add esp, 12 ; we don't return to the previous instruction, which was hlt
    ; instead we skip over the return address, segment and flags

    ret ; return to C caller which will do the EOI or whatever
    ; via separate calls to individual simple assembler functions
    ; like outportb()

    Note that this was inspired by something similar I wrote
    for S/370.

    It's basically quite minimal assembler and straightforward.

    There is a loop in the assembler, which I didn't have in my
    old routines, but it's not really processing logic.

    After transmit is working I'll try a variation of the above for receive.

    I especially don't know if these two need to be swapped:

    outb port1, transmit_byte
    outb port2, tbemask ; enable transmit buffer empty (only)

    I don't want to miss an interrupt. I want the order to
    guarantee that I will get the interrupt. ie if I have already
    attempted to write the transmit byte, will the interrupt
    pend until I enable it, or will it be skipped?

    Or is the other way around? If I haven't enabled interrupts
    will it just transmit the byte and not bother interrupting?

    Assume that the transmit is very fast, or the CPU is very
    slow, so that there is a gap between the two outb
    instructions where a decision is made on whether to
    interrupt or not.

    Thanks. Paul.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Terje Mathisen@21:1/5 to Paul Edwards on Tue Apr 11 21:09:45 2023
    I last looked at my RS232 drivers at least 30+ years ago, but from
    memory what I had to do was to get the data from the serial port chip,
    at this point I re-enable all other interrupts except the one I am
    servicing.

    After saving the received byte I optionally loop back until the input
    buffer is empty (more modern PCs had a 16-byte buffer you could enable).

    Before returning from the interrupt with IRET, I would reenable the
    serial port IRQ channel (stil with interrupts disabled), then IRET back
    to the interrupted process.

    If you are interested I can see if I can locate my source code...

    Terje

    Paul Edwards wrote:
    Hi.

    As far as I can tell from testing, the BIOS INT 14H doesn't
    work on most real hardware I have tried (computers made
    maybe 10 years ago that still have a serial port).

    So I am gearing up to replace that with 80386 PM32 code.

    I have some comms routines for MSDOS (pdcomm) that I
    wrote decades ago, but I want to do it a different way this time.

    Less sophisticated, but more straightforward.

    And designed for a single-tasking system with only one
    CPU enabled (PDOS/386).

    Most of the code can be done in C, but the last bit I want
    to do in assembler.

    Prior to hitting the assembler, I will have installed the
    new interrupt address (which is assembler code, gotint,
    below).

    I'm just trying to confirm the sequence in the final assembler.

    outb port1, transmit_byte
    outb port2, tbemask ; enable transmit buffer empty (only)
    xxx:
    hlt ; this could get interrupted by timer interrupts and
    ; then processing continues so we need a jmp
    jmp xxx
    gotint: ; this is the interrupt address installed by C caller
    outb port2, oldmask ; restore previous interrupt mask (everything disabled) add esp, 12 ; we don't return to the previous instruction, which was hlt
    ; instead we skip over the return address, segment and flags

    ret ; return to C caller which will do the EOI or whatever
    ; via separate calls to individual simple assembler functions
    ; like outportb()

    Note that this was inspired by something similar I wrote
    for S/370.

    It's basically quite minimal assembler and straightforward.

    There is a loop in the assembler, which I didn't have in my
    old routines, but it's not really processing logic.

    After transmit is working I'll try a variation of the above for receive.

    I especially don't know if these two need to be swapped:

    outb port1, transmit_byte
    outb port2, tbemask ; enable transmit buffer empty (only)

    I don't want to miss an interrupt. I want the order to
    guarantee that I will get the interrupt. ie if I have already
    attempted to write the transmit byte, will the interrupt
    pend until I enable it, or will it be skipped?

    Or is the other way around? If I haven't enabled interrupts
    will it just transmit the byte and not bother interrupting?

    Assume that the transmit is very fast, or the CPU is very
    slow, so that there is a gap between the two outb
    instructions where a decision is made on whether to
    interrupt or not.

    Thanks. Paul.



    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paul Edwards@21:1/5 to Terje Mathisen on Tue Apr 11 13:57:26 2023
    On Wednesday, April 12, 2023 at 3:25:01 AM UTC+8, Terje Mathisen wrote:

    After saving the received byte I optionally loop back until the input
    buffer is empty (more modern PCs had a 16-byte buffer you could enable).

    This sounds like the normal/proper way to do things.

    I did that with a proper buffer too:

    https://sourceforge.net/projects/pdos/files/pdcomm/PDCOMM%202.50/

    But now I would like to do things differently (simpler).

    If you are interested I can see if I can locate my source code...

    Thanks, but I already have source code for a traditional
    implementation. Note that I've never used "hlt" ever - I
    just heard about it, and the code I wrote above is what
    I picked up by osmosis.

    So I'm unsure whether the logic is correct or not. Bypassing
    the iret too is new. On return to the C caller I might need to
    reenable interrupts because I bypassed the iret.

    BFN. Paul.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paul Edwards@21:1/5 to Terje Mathisen on Tue Apr 11 18:29:04 2023
    On Wednesday, April 12, 2023 at 3:25:01 AM UTC+8, Terje Mathisen wrote:

    at this point I re-enable all other interrupts

    Actually, that made me rethink - maybe it is more straightforward to:

    Do this from C code, to signal that I am interested in TBE:

    outb port2, tbemask ; enable transmit buffer empty (only)

    Disable all interrupts (cli) from C code as well

    Do this from C code:

    outb port1, transmit_byte

    And at this point, if the byte is transmitted very quickly,
    the UART knows that it needs to do an interrupt, but
    interrupts are temporarily disabled, so it knows that it
    needs to queue the interrupt, not discard it.

    And then I call this assembler:

    xxx:
    hlt ; this could get interrupted by timer interrupts and
    ; then processing continues so we need a jmp
    jmp xxx

    And actually, even that can go into a function called halt_loop(),
    called by C.

    And then I can have this generic assembler routine:

    gotint: ; this is the interrupt address installed by C caller
    add esp, 12 ; we don't return to the previous instruction, which was hlt
    ; instead we skip over the return address, segment and flags

    Actually, no I can't. They need to be combined.

    sti
    xxx:
    hlt ; this could get interrupted by timer interrupts and
    ; then processing continues so we need a jmp
    jmp xxx

    gotint: ; this is the interrupt address installed by C caller
    add esp, 12 ; we don't return to the previous instruction, which was hlt
    ; instead we skip over the return address, segment and flags
    sti ; interrupts will have been disabled automatically. need to reenable
    ret

    Possibly take that sti out and do it from C, as C will have
    done a call to disable interrupts, so this would be a good match.

    And this is put back into C code:

    outb port2, oldmask ; restore previous interrupt mask (everything disabled)

    So down to just 5 assembler instructions.

    That's probably neat. And yes, I know it is inefficient.

    BFN. Paul.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paul Edwards@21:1/5 to All on Thu Apr 13 04:05:42 2023
    I committed code that I expected to work:

    https://sourceforge.net/p/pdos/gitcode/ci/3260d2aabe9f133f89bae3f9148070c188eb42b1/

    But I am only getting the first character transmitted,
    and then it never returns, thus never reboots.

    So I need to begin debugging.

    BFN. Paul.



    #ifdef __32BIT__
    static void writecomm(int port, int ch)
    {
    UART uart;
    unsigned long old1;
    unsigned long old2;
    unsigned long intdesc1;
    unsigned long intdesc2;
    unsigned long intaddr;
    int xch;
    int intno = 4;
    int imr = 0x21;

    uartInit(&uart);
    uartAddress(&uart, 0x3f8);
    uartDisableInts(&uart);
    /* IRQs 0-7 are at 0xb0 instead of 8 now */
    /* we are using IRQ 4 for COM1 */
    old1 = G_intloc[(intno + 0xb0) * 2];
    old2 = G_intloc[(intno + 0xb0) * 2 + 1];
    intaddr = (unsigned long)hltinthit;

    /* we are interested in this interrupt */
    xch = PREADB(imr);
    xch &= ~(1 << (intno % 8));
    PWRITEB(imr, xch);

    uartEnableGPO2(&uart);

    uartEnableTBE(&uart);
    intdesc1 = (0x8 << 16) | (intaddr & 0xffff);
    intdesc2 = (intaddr & 0xffff0000)
    | (1 << 15)
    | (0 << 13)
    | (0x0e << 8);
    disable();
    G_intloc[(intno + 0xb0) * 2] = intdesc1;
    G_intloc[(intno + 0xb0) * 2 + 1] = intdesc2;
    uartTxCh(&uart, ch);
    hltintgo();
    G_intloc[(intno + 0xb0) * 2] = old1;
    G_intloc[(intno + 0xb0) * 2 + 1] = old2;
    enable();
    PosReboot();
    uartReset(&uart);
    }
    #endif


    / enable interrupts and then halt until interrupt hit
    _hltintgo:
    sti
    hloop:
    / I believe hlt will be interrupted by other interrupts, like
    / the timer interrupt, so we need to do it in a loop
    hlt
    jmp hloop
    _hltinthit:
    / remove return address, segment and flags from the stack as we
    / do not intend to return to the jmp following the hlt instruction
    / that was likely interrupted
    add %esp, 12
    / note that interrupts will be disabled again (I think) by virtue
    / of the fact that an interrupt occurred. The caller would have
    / disabled interrupts already, so we are returning to the same
    / disabled state.
    ret

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Andrew Cooper@21:1/5 to Paul Edwards on Thu Apr 13 13:17:34 2023
    On 13/04/2023 12:05 pm, Paul Edwards wrote:
    / enable interrupts and then halt until interrupt hit
    _hltintgo:
    sti
    hloop:
    / I believe hlt will be interrupted by other interrupts, like
    / the timer interrupt, so we need to do it in a loop
    hlt
    jmp hloop
    _hltinthit:
    / remove return address, segment and flags from the stack as we
    / do not intend to return to the jmp following the hlt instruction
    / that was likely interrupted
    add %esp, 12
    / note that interrupts will be disabled again (I think) by virtue
    / of the fact that an interrupt occurred. The caller would have
    / disabled interrupts already, so we are returning to the same
    / disabled state.
    ret

    This is why things get stuck.

    The first time through, you hlt with interrupts enabled, so will wake up
    on the fist interrupt.

    But when you loop, you'll hlt again, this time with interrupts disabled,
    and will never ever wake up again (other than for an NMI).

    Your loop needs to read:

    _hltintgo:
    sti
    hlt
    cli
    jmp _hltintgo
    _hltinthit:
    ...

    to make things work as you intend.

    sti;hlt as a pair is important for getting the blocked-by-STI shadow to
    cover you into the hlt state, but it only works on the rising edge of
    IF, so you need to explicitly clear interrupts in order to make the
    sti;hlt on the second loop iteration work.

    ~Andrew

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paul Edwards@21:1/5 to Andrew Cooper on Thu Apr 13 09:26:54 2023
    On Thursday, April 13, 2023 at 8:27:03 PM UTC+8, Andrew Cooper wrote:

    Hi Andrew. Thanks for your reply.

    The first time through, you hlt with interrupts enabled, so will wake up
    on the fist interrupt.

    But when you loop, you'll hlt again, this time with interrupts disabled,
    and will never ever wake up again (other than for an NMI).

    I'm not expecting that behavior. I'm expecting the first
    interrupt to move me to the hltinthit location and I never
    return to the jmp.

    Only a timer interrupt is expected to interfere with that,
    and I would have expected that to be unlikely. And also
    I expect the timer interrupt to do a rti and restore the
    interrupt status to enabled.

    I made the change anyway:

    https://sourceforge.net/p/pdos/gitcode/ci/cdd5921434e5a827ad29d0024d456b059d9188b4/

    But it still hangs.

    I assume I haven't set up the interrupt properly.

    BFN. Paul.


    / enable interrupts and then halt until interrupt hit
    _hltintgo:
    hloop:
    / I believe hlt will be interrupted by other interrupts, like
    / the timer interrupt, so we need to do it in a loop
    sti
    hlt
    cli
    jmp hloop
    _hltinthit:
    / remove return address, segment and flags from the stack as we
    / do not intend to return to the jmp following the hlt instruction
    / that was likely interrupted
    add %esp, 12
    / note that interrupts will be disabled again (I think) by virtue
    / of the fact that an interrupt occurred. The caller would have
    / disabled interrupts already, so we are returning to the same
    / disabled state.
    ret

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From sean@nospicedham.conman.org@21:1/5 to It was thus on Fri Apr 14 00:12:01 2023
    It was thus said that the Great Paul Edwards <mutazilah@nospicedham.gmail.com> once stated:
    I committed code that I expected to work:

    https://sourceforge.net/p/pdos/gitcode/ci/3260d2aabe9f133f89bae3f9148070c188eb42b1/

    But I am only getting the first character transmitted,
    and then it never returns, thus never reboots.

    So I need to begin debugging.

    So if you are writing code that only runs one program at a time, why do
    you even need to mess with interrupts in the first place? Assuming a C
    funcion inb() to read a byte from an IO port, and outb() to write a byte to
    an IO port, I would think this would work:

    /* Assuming UART has been initialized, but its IRQ has been disabled */
    static void writecomm(int c)
    {
    /* read line status register to detect if the xmit buffer */
    /* is ready to send. */

    while (inb(0x3f8+5) & 0x20 == 0)
    {
    /* do nothing but wait */
    }

    /* we can now write the data */
    outb(0x3f8,c);
    }

    It seems simpler to me than trying to muck with interrupts and adjusting
    the return stack and all that.

    -spc

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paul Edwards@21:1/5 to se...@nospicedham.conman.org on Thu Apr 13 19:59:08 2023
    On Friday, April 14, 2023 at 9:33:57 AM UTC+8, se...@nospicedham.conman.org wrote:
    It was thus said that the Great Paul Edwards <muta...@nospicedham.gmail.com> once stated:
    I committed code that I expected to work:

    https://sourceforge.net/p/pdos/gitcode/ci/3260d2aabe9f133f89bae3f9148070c188eb42b1/

    But I am only getting the first character transmitted,
    and then it never returns, thus never reboots.

    So I need to begin debugging.
    So if you are writing code that only runs one program at a time, why do
    you even need to mess with interrupts in the first place? Assuming a C funcion inb() to read a byte from an IO port, and outb() to write a byte to an IO port, I would think this would work:

    /* Assuming UART has been initialized, but its IRQ has been disabled */ static void writecomm(int c)
    {
    /* read line status register to detect if the xmit buffer */
    /* is ready to send. */

    while (inb(0x3f8+5) & 0x20 == 0)
    {
    /* do nothing but wait */
    }

    /* we can now write the data */
    outb(0x3f8,c);
    }

    It seems simpler to me than trying to muck with interrupts and adjusting
    the return stack and all that.

    -spc

    My solution is in-between those two extremes.

    I only support a single task but I don't run hot polling.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From sean@nospicedham.conman.org@21:1/5 to It was thus on Fri Apr 14 07:11:13 2023
    It was thus said that the Great Paul Edwards <mutazilah@nospicedham.gmail.com> once stated:
    So if you are writing code that only runs one program at a time, why do
    you even need to mess with interrupts in the first place? Assuming a C
    funcion inb() to read a byte from an IO port, and outb() to write a byte to >> an IO port, I would think this would work:

    /* Assuming UART has been initialized, but its IRQ has been disabled */
    static void writecomm(int c)
    {
    /* read line status register to detect if the xmit buffer */
    /* is ready to send. */

    while (inb(0x3f8+5) & 0x20 == 0)
    {
    /* do nothing but wait */
    }

    /* we can now write the data */
    outb(0x3f8,c);
    }

    It seems simpler to me than trying to muck with interrupts and adjusting
    the return stack and all that.

    My solution is in-between those two extremes.

    I only support a single task but I don't run hot polling.

    Okay. If this is on a x86 system (an assumption on my part), and you
    don't want to poll, then this method might work. The interrupt handler does little more than sending the EOI (end of interrupt) to the 8259 PIC:

    uart_irq_handler:
    push ax ; eax if 32 bit
    mov al,20h
    out 020h,al ; signal end of interrupt
    pop ax
    iret

    and then your transmit routine (in assembly):

    ; assume character to transmit in AL
    ; also assume that the transmitter
    ; register is empty

    mov dx,[port] ; get port address
    out dx,al ; write character
    inc dx ; point to interrupt enable reg
    in al,dx
    or al,2 ; enable transmit
    out dx,al
    inc dx ; now point to interrupt ID reg

    pause:
    hlt ; halt CPU

    ; read interrupt ID reg; this also clears the IRQ on the serial chip
    ; and checks to see if we're the cause of the IRQ, and not something
    ; else like the keyboard.

    in al,dx
    and al,0eh ; isolate interrupt ID bits
    cmp al,2 ; transmitter empty bit IRQ?
    bne pause ; we're not the cause, keep waiting
    dec dx ; point back to interrupt enable reg
    in al,dx
    and al,0FDh ; disable transmit empty IRQ
    out dx,al

    ; and we're done with transmitting the character

    I think you may not have been telling the 8259 that the IRQ has been
    handled. But you also need to clear the IRQ from the UART itself as well, which you might not have been doing.

    -spc

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paul Edwards@21:1/5 to Paul Edwards on Fri Apr 14 09:55:12 2023
    On Thursday, April 13, 2023 at 7:11:58 PM UTC+8, Paul Edwards wrote:
    I committed code that I expected to work:

    So I need to begin debugging.

    add %esp, 12

    This was the main problem.

    Should have been add $12, %esp

    Current status is that it is outputting the first character,
    then it is printing "id is 2" (TBE) then "id is 0" (no pending),
    and then no further screen output, but there is another
    character that goes to the com port, but not the expected
    character, so I would expect that means it is transmitting
    instead of waiting for the interrupt, but I don't see how it
    can get ahead of itself.

    I've tried lots of things, still don't understand what is happening.

    I'll try debugging again when I wake up.

    BFN. Paul.


    / enable interrupts and then halt until interrupt hit
    _hltintgo:
    hloop:
    / I believe hlt will be interrupted by other interrupts, like
    / the timer interrupt, so we need to do it in a loop
    sti
    hlt
    cli
    jmp hloop
    _hltinthit:
    / remove return address, segment and flags from the stack as we
    / do not intend to return to the jmp following the hlt instruction
    / that was likely interrupted
    add $12, %esp
    / note that interrupts will be disabled again (I think) by virtue
    / of the fact that an interrupt occurred. The caller would have
    / disabled interrupts already, so we are returning to the same
    / disabled state.
    ret



    #ifdef __32BIT__
    static void writecomm(int port, int ch)
    {
    UART uart;
    unsigned long old1;
    unsigned long old2;
    unsigned long intdesc1;
    unsigned long intdesc2;
    unsigned long intaddr;
    int xch;
    int intno = 4;
    int a8259 = 0x20;
    int imr = 0x21;
    int id;

    uartInit(&uart);
    uartAddress(&uart, 0x3f8);
    PREADB(a8259); /* we don't use the result of this */
    uartDisableInts(&uart);
    /* IRQs 0-7 are at 0xb0 instead of 8 now */
    /* we are using IRQ 4 for COM1 */
    old1 = G_intloc[(intno + 0xb0) * 2];
    old2 = G_intloc[(intno + 0xb0) * 2 + 1];
    intaddr = (unsigned long)hltinthit;

    /* we are interested in this interrupt */
    xch = PREADB(imr);
    xch &= ~(1 << (intno % 8));
    PWRITEB(imr, xch);

    uartEnableGPO2(&uart);

    uartEnableTBE(&uart);
    /* uartEnableModem(&uart); */
    /* uartRaiseDTR(&uart); */
    /* uartRaiseRTS(&uart); */
    /* uartCTS(&uart); */
    intdesc1 = (0x8 << 16) | (intaddr & 0xffff);
    intdesc2 = (intaddr & 0xffff0000)
    | (1 << 15)
    | (0 << 13)
    | (0x0e << 8);
    disable();
    G_intloc[(intno + 0xb0) * 2] = intdesc1;
    G_intloc[(intno + 0xb0) * 2 + 1] = intdesc2;
    uartTxCh(&uart, ch);
    hltintgo();
    enable();
    do
    {
    id = uartGetIntType(&uart);
    printf("id is %d\n", id);
    } while (id != UART_NO_PENDING);
    PWRITEB(0x20, 0x20);
    uartDisableInts(&uart);
    uartDisableGPO2(&uart);

    xch = PREADB(imr);
    xch |= (1 << (intno % 8));
    PWRITEB(imr, xch);

    uartReset(&uart);
    uartTerm(&uart);

    disable();
    G_intloc[(intno + 0xb0) * 2] = old1;
    G_intloc[(intno + 0xb0) * 2 + 1] = old2;
    enable();
    }
    #endif

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paul Edwards@21:1/5 to Paul Edwards on Sat Apr 15 02:29:14 2023
    On Saturday, April 15, 2023 at 10:20:57 AM UTC+8, Paul Edwards wrote:

    uartEnableTBE(&uart);

    I have found that I need this in order to get an
    interrupt, as expected.

    uartTxCh(&uart, ch);

    But I was surprised to find that I didn't need this.

    Basically it appears that TBE wakes up once to let
    me know that it is currently empty, but once I start
    sending it data, and then halting, it doesn't feel the
    need to let me know it is empty again.

    But my whole design is centered around being
    informed of that.

    Maybe what is happening is that interrupts are disabled
    so often/sometimes the character is transmitted
    immediately and it isn't allowed to interrupt, so it doesn't.

    I then enable interrupts and hlt but the show is already over.

    So maybe what I need to do instead is send the character
    after enabling interrupts.

    I'll try that next. Not as neat as the previous generic solution.

    BFN. Paul.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paul Edwards@21:1/5 to Paul Edwards on Sat Apr 15 05:44:53 2023
    On Saturday, April 15, 2023 at 5:36:19 PM UTC+8, Paul Edwards wrote:

    uartEnableTBE(&uart);

    So far I have confirmed that after I have issued this
    (but without installing my own interrupt handler),
    I can read the uart to find out what interrupts are
    available and I see that TBE is the only one.

    I think transmit a byte, and I do get an interrupt,
    so I do get an additional interrupt even though
    I've read all the previous ones.

    Then when I go through for the second time, enabling
    TBE does the same thing, and I read the same thing
    from the uart.

    But this time when I try to transmit a byte I either don't
    get an interrupt or I get a hang for some other reason.

    So I don't know what is going on.

    If I can get consistent behavior I can have a simple design.

    BFN. Paul.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paul Edwards@21:1/5 to All on Sat Apr 15 07:24:27 2023
    I finally realized that since I'm getting an interrupt from
    the TBE enable (for unknown reasons), then if I moved
    the disable (cli) before that, then by the time I had
    outputted a byte, the interrupt would still be pending
    and even if I didn't get one for the outputted byte, it
    was enough to get one for the TBE call.

    And now it is working, with the simple design.

    I will look into refinements now that the basics are working.

    static void writecomm(int port, int ch)
    {
    UART uart;
    unsigned long old1;
    unsigned long old2;
    unsigned long intdesc1;
    unsigned long intdesc2;
    unsigned long intaddr;
    int xch;
    int intno = 4;
    int a8259 = 0x20;
    int imr = 0x21;
    int id;

    uartInit(&uart);
    uartAddress(&uart, 0x3f8);
    PREADB(a8259); /* we don't use the result of this */
    uartDisableInts(&uart);
    /* IRQs 0-7 are at 0xb0 instead of 8 now */
    /* we are using IRQ 4 for COM1 */
    old1 = G_intloc[(intno + 0xb0) * 2];
    old2 = G_intloc[(intno + 0xb0) * 2 + 1];
    intaddr = (unsigned long)hltinthit;

    /* we are interested in this interrupt */
    xch = PREADB(imr);
    xch &= ~(1 << (intno % 8));
    PWRITEB(imr, xch);

    disable();
    uartEnableGPO2(&uart);

    uartEnableTBE(&uart);
    /* uartEnableModem(&uart); */
    /* uartRaiseDTR(&uart); */
    /* uartRaiseRTS(&uart); */
    /* uartCTS(&uart); */
    intdesc1 = (0x8 << 16) | (intaddr & 0xffff);
    intdesc2 = (intaddr & 0xffff0000)
    | (1 << 15)
    | (0 << 13)
    | (0x0e << 8);
    G_intloc[(intno + 0xb0) * 2] = intdesc1;
    G_intloc[(intno + 0xb0) * 2 + 1] = intdesc2;
    uartTxCh(&uart, ch);
    hltintgo();
    enable();
    do
    {
    id = uartGetIntType(&uart);
    } while (id != UART_NO_PENDING);
    PWRITEB(0x20, 0x20);
    uartDisableInts(&uart);
    uartDisableGPO2(&uart);

    xch = PREADB(imr);
    xch |= (1 << (intno % 8));
    PWRITEB(imr, xch);

    uartReset(&uart);
    uartTerm(&uart);

    disable();
    G_intloc[(intno + 0xb0) * 2] = old1;
    G_intloc[(intno + 0xb0) * 2 + 1] = old2;
    enable();
    }


    / enable interrupts and then halt until interrupt hit
    _hltintgo:
    hloop:
    / I believe hlt will be interrupted by other interrupts, like
    / the timer interrupt, so we need to do it in a loop
    sti
    hlt
    cli
    jmp hloop
    _hltinthit:
    / remove return address, segment and flags from the stack as we
    / do not intend to return to the jmp following the hlt instruction
    / that was likely interrupted
    add $12, %esp
    / note that interrupts will be disabled again (I think) by virtue
    / of the fact that an interrupt occurred. The caller would have
    / disabled interrupts already, so we are returning to the same
    / disabled state.
    ret

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Terje Mathisen@21:1/5 to Paul Edwards on Sat Apr 15 23:42:17 2023
    Paul Edwards wrote:
    On Saturday, April 15, 2023 at 10:36:37 PM UTC+8, Paul Edwards wrote:

    I finally realized that since I'm getting an interrupt from
    the TBE enable (for unknown reasons), then if I moved
    the disable (cli) before that, then by the time I had
    outputted a byte, the interrupt would still be pending
    and even if I didn't get one for the outputted byte, it
    was enough to get one for the TBE call.

    And now it is working, with the simple design.

    Note that it is working (and previously failing) under
    Bochs. I haven't tried real hardware yet.

    And now I realize there may be a problem with the
    current code.

    Let's say the serial port is slow.

    The sequence I am doing is enabling TBE and then outputting
    a byte.

    Enabling TBE generates an interrupt, but outputting the byte
    only randomly does (could also be a Bochs bug).

    Because I am now relying on the TBE enable interrupt to get
    me out of the HLT loop, I am no longer have the desired
    constraint on the OUT instruction completing.

    Meaning the second time through the loop, the second OUT
    could be executed before the first one has completed.

    If the UART discards the TBE interrupt when it realizes that
    it is no longer the case that the transmit buffer is empty,
    because there has been an OUT instruction issued since
    then, then my current design should work.

    Does anyone know what is happening?

    Thanks. Paul.

    How do you guarantee that the interrupt is directed to your thread
    that's sitting in a HLT state?

    Terje

    --
    - <Terje.Mathisen at tmsw.no>
    "almost all programming can be viewed as an exercise in caching"

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paul Edwards@21:1/5 to Paul Edwards on Sat Apr 15 14:13:50 2023
    On Saturday, April 15, 2023 at 10:36:37 PM UTC+8, Paul Edwards wrote:

    I finally realized that since I'm getting an interrupt from
    the TBE enable (for unknown reasons), then if I moved
    the disable (cli) before that, then by the time I had
    outputted a byte, the interrupt would still be pending
    and even if I didn't get one for the outputted byte, it
    was enough to get one for the TBE call.

    And now it is working, with the simple design.

    Note that it is working (and previously failing) under
    Bochs. I haven't tried real hardware yet.

    And now I realize there may be a problem with the
    current code.

    Let's say the serial port is slow.

    The sequence I am doing is enabling TBE and then outputting
    a byte.

    Enabling TBE generates an interrupt, but outputting the byte
    only randomly does (could also be a Bochs bug).

    Because I am now relying on the TBE enable interrupt to get
    me out of the HLT loop, I am no longer have the desired
    constraint on the OUT instruction completing.

    Meaning the second time through the loop, the second OUT
    could be executed before the first one has completed.

    If the UART discards the TBE interrupt when it realizes that
    it is no longer the case that the transmit buffer is empty,
    because there has been an OUT instruction issued since
    then, then my current design should work.

    Does anyone know what is happening?

    Thanks. Paul.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From sean@nospicedham.conman.org@21:1/5 to It was thus on Sat Apr 15 23:23:22 2023
    I replied once with some code, but it seems you didn't see it, so I'm replying again.

    It was thus said that the Great Paul Edwards <mutazilah@nospicedham.gmail.com> once stated:

    And now it is working, with the simple design.

    I will look into refinements now that the basics are working.

    static void writecomm(int port, int ch)
    {
    ...

    old1 = G_intloc[(intno + 0xb0) * 2];
    old2 = G_intloc[(intno + 0xb0) * 2 + 1];
    intaddr = (unsigned long)hltinthit;

    ...

    intdesc1 = (0x8 << 16) | (intaddr & 0xffff);
    intdesc2 = (intaddr & 0xffff0000)
    | (1 << 15)
    | (0 << 13)
    | (0x0e << 8);
    G_intloc[(intno + 0xb0) * 2] = intdesc1;
    G_intloc[(intno + 0xb0) * 2 + 1] = intdesc2;

    ...

    G_intloc[(intno + 0xb0) * 2] = old1;
    G_intloc[(intno + 0xb0) * 2 + 1] = old2;

    ...

    }

    Why are you installing, then uninstalling, the interrupt handler for each character? And I think you are making this out to be more complicated that
    it should be. As I wrote before, the interrupt handler for the UART can be
    as simple as:

    uart_irq_handler:
    push ax ; or eax if 32-bit
    mov al,20h ; sent end-of-interrupt to 8259 PIC
    out 20h,al
    pop ax
    iret

    That's it. No mucking with return addresses, or CLI/STI instructions or anything like that. Let the interrupt happen, and inform the 8259 it's been handled. Set the vector once, and be done with it.

    And the code to transmit the character, which assumes the UART has been initialized with the baud rate and bit settings:

    ; assume character to transmit is in AL
    ; also assume the transmitter register
    ; is empty

    mov dx,[port] ; get port address
    out dx,al ; send the character
    inc dx ; point to interrupt enable register
    in al,dx ; read current setting
    or al,2 ; set transmitter empty IRQ
    out dx,al ; tell UART
    inc dx ; point to interrupt ID register

    pause:

    hlt ; halt CPU

    ; read interrupt ID reg; this also clears the IRQ on the serial chip
    ; and checks to see if we're the cause of the IRQ, and not something
    ; else like the keyboard.

    in al,dx ; read UART interrupt register
    and al,0eh ; isolate source of interrupt bits
    cmp al,2 ; is it the transmitter empty IRQ?
    bne pause ; if not, keep waiting
    dec dx ; point to interrupt enable register
    in al,dx ; read current setting
    and al,0FDh ; disable transmit empty IRQ
    out dx,al

    ; and we're done with transmitting the character

    You need to tell both the 8259 and the UART that the interrupt has been handled.

    -spc

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paul Edwards@21:1/5 to Terje Mathisen on Sat Apr 15 18:08:23 2023
    On Sunday, April 16, 2023 at 5:52:00 AM UTC+8, Terje Mathisen wrote:

    How do you guarantee that the interrupt is directed to your thread
    that's sitting in a HLT state?

    This is single-threading PDOS/386.

    Simple, and understandable.

    And once you have this system, you can use it to create
    a more complicated system of your own. (And I may
    choose to do that myself one day, but I'm getting
    pretty old and I'm still on the simple system(s)).

    BFN. Paul.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paul Edwards@21:1/5 to se...@nospicedham.conman.org on Sat Apr 15 18:19:12 2023
    On Sunday, April 16, 2023 at 7:37:08 AM UTC+8, se...@nospicedham.conman.org wrote:

    I replied once with some code, but it seems you didn't see it, so I'm replying again.

    Sorry. I wasn't sure what to reply with, and I was still in
    the process of debugging. Yours a different design,
    and assembler-focused. I know this is an assembler group,
    and I am indeed using assembler, but I'm trying to minimize it.
    The required minimal assembler has already changed
    numerous times to try to get this very basic functionality
    working.

    I'm not disputing that it wouldn't work or be better.

    But I have a different design I am trying to get to work.
    (and it should work - and the fact that it only half-works
    is exactly what I want to see and understand).

    Why are you installing, then uninstalling, the interrupt handler for each character?

    So that I can see an understandable sequence until I
    am happy that the sequence is right.

    I can potentially move that later. But I probably won't,
    because the interrupt may be shared. And code
    elsewhere would use the same assembler routine,
    same interrupt number, same simple logic, and still work.

    And I think you are making this out to be more complicated that
    it should be.

    It is more complicated for an assembler programmer, but
    not more complicated for a (or at least, this) C programmer,
    because almost all the logic is in C.

    As I wrote before, the interrupt handler for the UART can be
    as simple as:

    pause:

    hlt ; halt CPU

    ...
    bne pause ; if not, keep waiting

    And I can't move this code out into C, due to this requiring
    the stack pointer isn't changed.

    I don't wish to use inline assembler either - I want my
    C code to be C90-compliant.

    You need to tell both the 8259 and the UART that the interrupt has been handled.

    I believe my code does that.

    BFN. Paul.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paul Edwards@21:1/5 to Paul Edwards on Sun Apr 16 01:37:04 2023
    On Sunday, April 16, 2023 at 5:21:57 AM UTC+8, Paul Edwards wrote:

    And now I realize there may be a problem with the
    current code.

    I have now managed to test on real hardware.

    I was surprised to see that INT 14H to initialize the
    port is apparently working, as I was able to transmit
    characters.

    I was expecting to have to update PDOS to replace
    the INT 14H initialization functionality.

    However, when I copied a file to com1, most characters
    were dropped.

    So that bit of the theory was correct.

    If I just copied a 1-byte file to com1, then copied another
    1-byte file to com1, the characters both went through.

    I then tried moving the disable() below the TBE enable in
    the hope that the old (default) interrupt handler would
    take care of that, and I would get my character. But that
    would have created a timing issue even if it had worked.
    Instead there was no change in behavior.

    And then I figured that what I needed was consistency,
    and that there should be two interrupts.

    So I did a second call to hltintgo.

    And this time it hung.

    Fortunately I realized I need to repeat the read of the uart
    and do the write to 0x20 and it worked on my real hardware.

    And the same code worked on Bochs too.

    So working code now below.

    Thanks everyone for your thoughts.

    BFN. Paul.


    static void writecomm(int port, int ch)
    {
    UART uart;
    unsigned long old1;
    unsigned long old2;
    unsigned long intdesc1;
    unsigned long intdesc2;
    unsigned long intaddr;
    int xch;
    int intno = 4;
    int a8259 = 0x20;
    int imr = 0x21;
    int id;

    uartInit(&uart);
    uartAddress(&uart, 0x3f8);
    PREADB(a8259); /* we don't use the result of this */
    uartDisableInts(&uart);
    /* IRQs 0-7 are at 0xb0 instead of 8 now */
    /* we are using IRQ 4 for COM1 */
    old1 = G_intloc[(intno + 0xb0) * 2];
    old2 = G_intloc[(intno + 0xb0) * 2 + 1];
    intaddr = (unsigned long)hltinthit;

    /* we are interested in this interrupt */
    xch = PREADB(imr);
    xch &= ~(1 << (intno % 8));
    PWRITEB(imr, xch);

    uartEnableGPO2(&uart);

    /* uartEnableModem(&uart); */
    /* uartRaiseDTR(&uart); */
    /* uartRaiseRTS(&uart); */
    /* uartCTS(&uart); */
    intdesc1 = (0x8 << 16) | (intaddr & 0xffff);
    intdesc2 = (intaddr & 0xffff0000)
    | (1 << 15)
    | (0 << 13)
    | (0x0e << 8);
    disable();
    G_intloc[(intno + 0xb0) * 2] = intdesc1;
    G_intloc[(intno + 0xb0) * 2 + 1] = intdesc2;
    /* for some reason just enabling the interrupt causes
    an interrupt. But transmitting a character doesn't
    necessarily generate an interrupt for some reason.
    But by disabling the interrupts while both enabling
    TBE and sending a character, we 'guarantee' that we
    will receive an interrupt from at least one of those
    so that the hlt instruction will be interrupted. */
    uartEnableTBE(&uart);
    hltintgo();
    enable();
    do
    {
    id = uartGetIntType(&uart);
    } while (id != UART_NO_PENDING);
    PWRITEB(0x20, 0x20);
    disable();
    uartTxCh(&uart, ch);
    hltintgo();
    enable();
    do
    {
    id = uartGetIntType(&uart);
    } while (id != UART_NO_PENDING);
    PWRITEB(0x20, 0x20);
    uartDisableInts(&uart);
    uartDisableGPO2(&uart);

    xch = PREADB(imr);
    xch |= (1 << (intno % 8));
    PWRITEB(imr, xch);

    uartReset(&uart);
    uartTerm(&uart);

    disable();
    G_intloc[(intno + 0xb0) * 2] = old1;
    G_intloc[(intno + 0xb0) * 2 + 1] = old2;
    enable();
    }

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paul Edwards@21:1/5 to All on Sun Apr 16 17:44:53 2023
    I have now added read functionality.

    And I was able to reuse my existing assembler code.

    So that's a great result.

    But as per comments below, I did get two surprising
    results attempting to get it to work, with a working
    theory.

    BFN. Paul.


    static int readcomm(int port)
    {
    UART uart;
    unsigned long old1;
    unsigned long old2;
    unsigned long intdesc1;
    unsigned long intdesc2;
    unsigned long intaddr;
    int xch;
    int intno = 4;
    int a8259 = 0x20;
    int imr = 0x21;
    int id;
    int ch;

    uartInit(&uart);
    uartAddress(&uart, 0x3f8);
    PREADB(a8259); /* we don't use the result of this */
    uartDisableInts(&uart);
    /* IRQs 0-7 are at 0xb0 instead of 8 now */
    /* we are using IRQ 4 for COM1 */
    old1 = G_intloc[(intno + 0xb0) * 2];
    old2 = G_intloc[(intno + 0xb0) * 2 + 1];
    intaddr = (unsigned long)hltinthit;

    /* we are interested in this interrupt */
    xch = PREADB(imr);
    xch &= ~(1 << (intno % 8));
    PWRITEB(imr, xch);

    uartEnableGPO2(&uart);

    /* uartEnableModem(&uart); */
    /* uartRaiseDTR(&uart); */
    /* uartRaiseRTS(&uart); */
    /* uartCTS(&uart); */
    intdesc1 = (0x8 << 16) | (intaddr & 0xffff);
    intdesc2 = (intaddr & 0xffff0000)
    | (1 << 15)
    | (0 << 13)
    | (0x0e << 8);
    disable();
    G_intloc[(intno + 0xb0) * 2] = intdesc1;
    G_intloc[(intno + 0xb0) * 2 + 1] = intdesc2;
    uartEnableRxRDY(&uart);
    hltintgo();
    /* if I immediately disable UART interrupts, I can no
    longer read the old pending id of RxRDY.
    If I read the pending ids, RxRDY just gets reasserted,
    presumably because I haven't actually read the
    character yet.
    If I try reading the character, a new character may
    come in and I'll miss it.
    So the safest thing to do is just disable interrupts
    and assume that RxRDY was hit, since that was the only
    thing actually enabled, and I don't bother reading the
    interrupt ids. */
    G_intloc[(intno + 0xb0) * 2] = old1;
    G_intloc[(intno + 0xb0) * 2 + 1] = old2;
    uartDisableInts(&uart);
    enable();
    ch = uartRecCh(&uart);
    PWRITEB(0x20, 0x20);
    uartDisableGPO2(&uart);

    xch = PREADB(imr);
    xch |= (1 << (intno % 8));
    PWRITEB(imr, xch);

    uartReset(&uart);
    uartTerm(&uart);

    return (ch);
    }

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)