Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

serial port

73 views
Skip to first unread message

Paul Edwards

unread,
Apr 11, 2023, 7:24:37 AM4/11/23
to
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

unread,
Apr 11, 2023, 3:25:01 PM4/11/23
to
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
--
- <Terje.Mathisen at tmsw.no>
"almost all programming can be viewed as an exercise in caching"

Paul Edwards

unread,
Apr 11, 2023, 5:10:08 PM4/11/23
to
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.

Paul Edwards

unread,
Apr 11, 2023, 9:40:23 PM4/11/23
to
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.

Paul Edwards

unread,
Apr 13, 2023, 7:11:58 AM4/13/23
to
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


Andrew Cooper

unread,
Apr 13, 2023, 8:27:03 AM4/13/23
to
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

Paul Edwards

unread,
Apr 13, 2023, 12:27:17 PM4/13/23
to
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

se...@nospicedham.conman.org

unread,
Apr 13, 2023, 9:33:57 PM4/13/23
to
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

Paul Edwards

unread,
Apr 13, 2023, 11:04:18 PM4/13/23
to
My solution is in-between those two extremes.

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

se...@nospicedham.conman.org

unread,
Apr 14, 2023, 3:19:30 AM4/14/23
to
It was thus said that the Great Paul Edwards <muta...@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

Paul Edwards

unread,
Apr 14, 2023, 10:20:57 PM4/14/23
to
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

Paul Edwards

unread,
Apr 15, 2023, 5:36:19 AM4/15/23
to
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.

Paul Edwards

unread,
Apr 15, 2023, 8:51:30 AM4/15/23
to
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.

Paul Edwards

unread,
Apr 15, 2023, 10:36:37 AM4/15/23
to
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.
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();
}


Paul Edwards

unread,
Apr 15, 2023, 5:21:57 PM4/15/23
to
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.

Terje Mathisen

unread,
Apr 15, 2023, 5:52:00 PM4/15/23
to
How do you guarantee that the interrupt is directed to your thread
that's sitting in a HLT state?

Terje

se...@nospicedham.conman.org

unread,
Apr 15, 2023, 7:37:08 PM4/15/23
to
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 <muta...@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

Paul Edwards

unread,
Apr 15, 2023, 9:22:17 PM4/15/23
to
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.

Paul Edwards

unread,
Apr 15, 2023, 9:22:20 PM4/15/23
to
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.

Paul Edwards

unread,
Apr 16, 2023, 4:37:56 AM4/16/23
to
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.
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();

Paul Edwards

unread,
Apr 16, 2023, 8:53:43 PM4/16/23
to
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;
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);
}

0 new messages