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.
After saving the received byte I optionally loop back until the inputThis sounds like the normal/proper way to do things.
buffer is empty (more modern PCs had a 16-byte buffer you could enable).
If you are interested I can see if I can locate my source code...Thanks, but I already have source code for a traditional
at this point I re-enable all other interruptsActually, that made me rethink - maybe it is more straightforward to:
/ 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
The first time through, you hlt with interrupts enabled, so will wake upI'm not expecting that behavior. I'm expecting the first
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 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.
It was thus said that the Great Paul Edwards <muta...@nospicedham.gmail.com> once stated:My solution is in-between those two extremes.
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
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.
I committed code that I expected to work:This was the main problem.
So I need to begin debugging.
add %esp, 12
uartEnableTBE(&uart);I have found that I need this in order to get an
uartTxCh(&uart, ch);But I was surprised to find that I didn't need this.
So far I have confirmed that after I have issued thisuartEnableTBE(&uart);
I finally realized that since I'm getting an interrupt fromNote that it is working (and previously failing) under
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.
On Saturday, April 15, 2023 at 10:36:37rC>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.
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;
}
How do you guarantee that the interrupt is directed to your threadThis is single-threading PDOS/386.
that's sitting in a HLT state?
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
Why are you installing, then uninstalling, the interrupt handler for each character?So that I can see an understandable sequence until I
And I think you are making this out to be more complicated thatIt is more complicated for an assembler programmer, but
it should be.
As I wrote before, the interrupt handler for the UART can be
as simple as:
pause:
hlt ; halt CPU
bne pause ; if not, keep waitingAnd I can't move this code out into C, due to this requiring
You need to tell both the 8259 and the UART that the interrupt has been handled.I believe my code does that.
And now I realize there may be a problem with theI have now managed to test on real hardware.
current code.
| Sysop: | Amessyroom |
|---|---|
| Location: | Fayetteville, NC |
| Users: | 65 |
| Nodes: | 6 (0 / 6) |
| Uptime: | 06:15:49 |
| Calls: | 862 |
| Files: | 1,311 |
| D/L today: |
921 files (14,318M bytes) |
| Messages: | 264,699 |