
It looks like if I’m going to get the AHCI SATA access I want, then I’m going to have to learn how to interact with the PCI bus. And I wanted to interact with the bus from Forth itself, rather than having to debug C code. First baby step: to see if I could pull any information from a PCI bus configuration register.
This seems to be fairly simple in principle: configuration access is port-based, with a 32 bit address memory location (CONFIG ADDRESS), and a 32 bit data memory location (CONFIG DATA). Put the correct request information in CA, and pull the data from CD. https://wiki.osdev.org/PCI#Configuration_Space_Access_Mechanism_.231.
The tricky part: since this is I/O memory, you have to access it using the Pentium INL and OUTL instructions. BYOK had implemented INB and OUTB, but these are not quite the instructions we wanted, and also they weren’t mapped to Forth words. But thankfully nice examples are available in a gcc library. So, I expanded BYOK’s io.h to…
static inline unsigned char inportb (uint16_t port)
{
unsigned char rv;
__asm__ __volatile__ ("inb %1, %0" : "=a" (rv) : "dN" (port));
return rv;
}
static inline void outportb (uint16_t port, unsigned char data)
{
__asm__ __volatile__ ("outb %1, %0" : : "dN" (port), "a" (data));
}
/* inport, outportl added by Christopher Howard 2020 */
static inline unsigned int inportl (unsigned short int port)
{
unsigned int rv;
__asm__ __volatile__ ("inl %w1, %0" : "=a" (rv) : "dN" (port));
return rv;
}
static inline void outportl (unsigned int value, unsigned short int port)
{
__asm__ __volatile__ ("outl %0, %w1" : : "a" (value), "dN" (port));
}
So, then I need to map this to Forth words, and I wasn’t sure where to do that. So, I added them to BYOK’s primitive word list. I wasn’t sure if the author of BYOK would have approved of that particular approach, but better to ask forgiveness later. Inserted the following into io.c:
state_t __INL(context_t *ctx)
{
unsigned int port;
if (popnum(ctx->ds, &port))
{
port = inportl (port);
pushnum(ctx->ds, port);
return OK;
}
else
{
return stack_underflow(ctx);
}
}
state_t __OUTL(context_t *ctx)
{
unsigned short int port;
unsigned int value;
if (popnum(ctx->ds, &port) && popnum(ctx->ds, &value))
{
outportl (value, port);
return OK;
}
else
{
return stack_underflow(ctx);
}
}
void init_io_words(context_t *ctx)
{
hashtable_t *htbl = ctx->exe_tok;
add_primitive(htbl, ".", __DOT, "( n -- )", "convert signed number n to string of digits, and output.");
<...snip...>
add_primitive(htbl, "INL", __INL, "( port -- )", "x86 32-bit read from i/o port");
add_primitive(htbl, "OUTL", __OUTL, "( value port -- )", "x86 32-bit output to i/o port");
}
To my surprise, the code compiled. Now, I needed a Forth function to figure out what is the correct data to put into CA. This is the part I currently am manually typing into the laptop after boot:
hex
cf8 constant CA
cfc constant CD
: pci-cfg-word ( bus slot func offset -- u )
-80000000
swap fc and or
swap 8 lshift or
swap 11 lshift or
swap 16 lshift or ;
decimal
In the laptop terminal I entered the following to get the (hopefully) correct address for CA on the stack, which is intended to pull the vendor code from the PCI bus:
0 0 0 0 pci-cfg-word
ok...
Then load and read:
ca outl
ok
cd inl
ok.
Result in hexadecimal:
hex .s
27a08086
Is that information, or garbage? I found the PCI ID Repository on the Internet. If I make the reasonable assumption that the vendor code is in the 16 LSBs, that gives vendor code 0x8086, which matches to “Intel Corporation”. That sounds plausible!

Fun stuff!
Dang dude!!! It does look fun. I’d love to not just learn this but be able to understand what it all means. Lol
LikeLike
Great work! Way to venture fourth and pioneer a new trail!
LikeLike
Forth, not fourth.
LikeLike