Port I/O
86Box handles the x86 port I/O space through I/O handlers. These handlers can be added with the io_sethandler function and removed with the io_removehandler function, both provided by 86box/io.h.
Parameter |
Description |
|---|---|
|
First I/O port (0x0000-0xffff) covered by this handler. |
|
Amount of I/O ports (1-65536) covered by this handler, starting at |
|
I/O read operation callback functions. Can be
|
|
|
|
|
|
I/O write operation callback functions. Can be
|
|
|
|
|
|
Opaque pointer passed to this handler’s read/write operation callbacks, usually a pointer to a device’s state structure. |
I/O handlers can be added or removed at any time, although io_removehandler must be called with the exact same parameters that io_sethandler was originally called with. For non-Plug and Play devices, you might want to add handlers in the init callback; for ISA Plug and Play devices, you’d add and/or remove handlers on the config_changed callback; for PCI devices, you’d do the same whenever the Command register or Base Address (BAR) registers are written to; and so on.
Note
There is no need to call io_removehandler on the device’s close callback, since a hard reset already removes all I/O handlers.
Callback fallbacks
When an I/O handler receives an operation with a width for which it has no callback, the operation will automatically fall back to a lower width for which there is a callback. For example, if an inl operation falls on a handler which has no inl callback, 86Box will break the operation down to inw or inb callbacks on successive port numbers, then combine their return values:
inlcallback present:uint32_t val = inl(port);
inlcallback not present, butinwcallback present:uint32_t val = inw(port); val |= (inw(port + 2) << 16);
inlandinwcallbacks not present, butinbcallback present:uint32_t val = inb(port); val |= (inb(port + 1) << 8); val |= (inb(port + 2) << 16); val |= (inb(port + 3) << 24);
inl,inwandinbcallbacks not present:uint32_t val = 0xffffffff; /* don't care */
The same rule applies to write callbacks:
outlcallback present:uint32_t val = /* ... */; outl(port, val);
outlcallback not present, butoutwcallback present:uint32_t val = /* ... */; outw(port, val & 0xffff); outw(port + 2, (val >> 16) & 0xffff);
outlandoutwcallbacks not present, butoutbcallback present:uint32_t val = /* ... */; outb(port, val & 0xff); outb(port + 1, (val >> 8) & 0xff); outb(port + 2, (val >> 16) & 0xff); outb(port + 3, (val >> 24) & 0xff);
outl,outwandoutbcallbacks not present:Don’t care, no operation performed.
Note
Each broken-down operation triggers the I/O handlers for its respective port number, no matter which handlers are responsible for the starting port number. A handler will never receive callbacks for ports outside its base and size boundaries.
This feature’s main use cases are devices which store registers that are 8-bit wide but may be accessed with 16- or 32-bit operations:
Code example: inb handler for reading 8-bit registers
typedef struct {
uint8_t regs[256];
} foo_t;
static uint8_t
foo_io_inb(uint16_t port, void *priv)
{
foo_t *dev = (foo_t *) priv;
return dev->regs[port & 0xff]; /* register index = I/O port's least significant byte */
}
/* No foo_io_inw, so a 16-bit read will read two 8-bit registers in succession.
No foo_io_inl, so a 32-bit read will read four 8-bit registers in succession. */
Multiple I/O handlers
Any given I/O port can have an unlimited amount of I/O handlers, such that:
when a read operation occurs, all read callbacks will be called, and their return values will be logically ANDed together;
when a write operation occurs, all write callbacks will be called with the same written value.
Read callbacks can effectively return “don’t care” (without interfering with other handlers) by returning a value with all bits set: 0xff for inb, 0xffff for inw or 0xffffffff for inl.
Note
The same callback fallback rules specified above also apply with multiple handlers. Handlers without callbacks for the operation’s type and (same or lower) width are automatically skipped.
I/O traps
A second type of I/O handler, I/O traps allow a device (usually System Management Mode on chipsets or legacy compatibility mechanisms on PCI sound cards) to act upon a read/write operation to an I/O port operation without affecting its result.
Code example: I/O trap on ports 0x220-0x22f
typedef struct {
void *trap_220;
} foo_t;
static void
foo_trap_220(int size, uint16_t port, uint8_t write, uint8_t val, void *priv)
{
/* Get the device state structure. */
foo_t *dev = (foo_t *) priv;
/* Do whatever you want. */
pclog("Foo: Trapped I/O %s to port %04X, size %d\n",
write ? "write" : "read", port, size);
if (write)
pclog("Foo: Written value: %02X\n", val);
}
static void *
foo_init(const device_t *info)
{
/* Allocate the device state structure. */
foo_t *dev = /* ... */
/* Add I/O trap. */
dev->trap_220 = io_trap_add(foo_trap_220, dev);
/* Map I/O trap to 16 ports starting at 0x220. */
io_trap_remap(dev->trap_220, 1, 0x220, 16);
return dev;
}
static void
foo_close(void *priv)
{
/* Get the device state structure. */
foo_t *dev = (foo_t *) priv;
/* Remove I/O trap before deallocating the device state structure. */
io_trap_remove(dev->trap_220);
free(dev);
}
const device_t foo4321_device = {
/* ... */
.init = foo_init,
.close = foo_close,
/* ... */
};
Parameter |
Description |
|---|---|
|
Function called whenever an I/O operation of any type or size is performed to the trap’s I/O address range. Takes the form of:
|
|
Opaque pointer passed to the |
Return value |
Opaque ( |
Parameter |
Description |
|---|---|
|
Opaque pointer representing the I/O trap to remap. |
|
|
|
First I/O port (0x0000-0xffff) covered by this trap. |
|
Amount of I/O ports (1-65536) covered by this trap. |