Hide API code examples using expand containers by default

This commit is contained in:
RichardG867
2022-03-20 14:31:25 -03:00
parent 7a90860982
commit 5be83357c5
7 changed files with 695 additions and 503 deletions

View File

@@ -54,3 +54,28 @@ div.bit-table > div.wy-table-responsive > table > thead > tr > th:not(.stub), di
min-width: 26px;
padding: 0;
}
/* Toggle containers. */
.toggle {
background: #f3f6f6;
border: 1px solid #e1e4e5;
padding: 1em;
margin: 0 0 24px;
}
.toggle-closed > .toggle-header, .toggle-open > .toggle-header {
cursor: pointer;
}
.toggle > .toggle-header > p {
font-weight: bold;
margin-bottom: 0;
}
.toggle > div:nth-child(2) {
margin-top: 12px;
margin-bottom: 0;
}
.toggle-closed > .toggle-header > p:before {
content: "\0025B6 ";
}
.toggle-open > .toggle-header > p:before {
content: "\0025BC ";
}

25
_static/js/86box.js Normal file
View File

@@ -0,0 +1,25 @@
/* Toggle containers, modified from: https://stackoverflow.com/questions/2454577/sphinx-restructuredtext-show-hide-code-snippets */
$(document).ready(function() {
/* Hide all toggle containers. */
$('.toggle').children().not('.toggle-header').hide();
$('.toggle').toggleClass('toggle-closed');
/* Add click handlers for the header. */
$('.toggle-header').click(function() {
/* Toggle the container. */
$(this).parent().children().not('.toggle-header').toggle(400);
$(this).parent().toggleClass('toggle-open toggle-closed');
});
/* Fix scroll position if a heading is provided in the URL.
Actually hit or miss but I can't think of a better solution. */
if ($('.toggle').length && document.location.hash) {
$(window).on('load', function() {
setTimeout(function() {
var hash = document.location.hash;
document.location.hash = hash + '_';
document.location.hash = hash;
}, 0);
});
}
});

View File

@@ -61,4 +61,8 @@ html_css_files = [
'css/86box.css',
]
html_js_files = [
'js/86box.js',
]
highlight_language = 'c'

View File

@@ -105,60 +105,66 @@ The **device** is the main unit of emulated components in 86Box. Each device is
State structure
---------------
Most devices need a place to store their internal state. We discourage the use of global structures, and instead recommend allocating **state structures** dynamically in the ``init`` callback and freeing them in the ``close`` callback::
Most devices need a place to store their internal state. We discourage the use of global structures, and instead recommend allocating **state structures** dynamically in the ``init`` callback and freeing them in the ``close`` callback:
#include <86box/device.h>
.. container:: toggle
typedef struct {
uint32_t type; /* example: copied from device_t.local */
uint8_t regs[256]; /* example: 256*8-bit registers */
} foo_t;
.. container:: toggle-header
/* ... */
Code example: allocating and freeing a state structure
static void *
foo_init(const device_t *info)
{
/* Allocate a blank state structure. */
foo_t *dev = (foo_t *) malloc(sizeof(foo_t));
memset(dev, 0, sizeof(foo_t));
.. code-block::
/* Do whatever you want. */
dev->type = info->local; /* copy device_t.local value */
#include <86box/device.h>
/* Return a pointer to the state structure. */
return dev;
}
typedef struct {
uint32_t type; /* example: copied from device_t.local */
uint8_t regs[256]; /* example: 256*8-bit registers */
} foo_t;
static void
foo_close(void *priv)
{
/* Get the state structure. */
foo_t *dev = (foo_t *) priv;
static void *
foo_init(const device_t *info)
{
/* Allocate the device state structure. */
foo_t *dev = (foo_t *) malloc(sizeof(foo_t));
memset(dev, 0, sizeof(foo_t)); /* blank structure */
/* Do whatever you want, then deallocate the state structure. */
free(dev);
}
/* Do whatever you want. */
dev->type = info->local; /* copy device_t.local value */
const device_t foo1234_device = {
.name = "Foo-1234",
.internal_name = "foo1234",
.flags = DEVICE_AT, /* 16-bit ISA */
.local = 1234,
.init = foo_init,
.close = foo_close,
/* ... */
};
/* Return a pointer to the state structure. */
return dev;
}
const device_t foo4321_device = {
.name = "Foo-4321",
.internal_name = "foo4321",
.flags = DEVICE_PCI, /* 32-bit PCI */
.local = 4321, /* different device subtype */
.init = foo_init,
.close = foo_close,
/* ... */
};
static void
foo_close(void *priv)
{
/* Get the state structure. */
foo_t *dev = (foo_t *) priv;
/* Do whatever you want, then deallocate the state structure. */
free(dev);
}
const device_t foo1234_device = {
.name = "Foo-1234",
.internal_name = "foo1234",
.flags = DEVICE_AT, /* 16-bit ISA */
.local = 1234,
.init = foo_init,
.close = foo_close,
/* ... */
};
const device_t foo4321_device = {
.name = "Foo-4321",
.internal_name = "foo4321",
.flags = DEVICE_PCI, /* 32-bit PCI */
.local = 4321, /* different device subtype */
.init = foo_init,
.close = foo_close,
/* ... */
};
Registration
------------
@@ -186,26 +192,30 @@ A device will be **available** for selection by the user if these criteria are m
2. The selected machine has any of the expansion buses specified in the device's ``flags``;
3. The device's ``available`` callback returns ``1`` to indicate the device is available (this will always be true if the ``available`` callback function is ``NULL``).
The ``available`` callback can be used to verify the presence of requisite ROMs, for example::
The ``available`` callback can be used to verify the presence of ROM files if any ROMs are required by the device:
#include <86box/device.h>
#include <86box/rom.h>
.. container:: toggle
/* ... */
.. container:: toggle-header
static int
foo1234_available()
{
return rom_present("roms/scsi/foo/foo1234.bin");
}
Code example: ``available`` checking for the presence of a ROM
/* ... */
.. code-block::
const device_t foo1234_device = {
/* ... */
{ .available = foo1234_available }, /* must have brackets due to the union */
/* ... */
};
#include <86box/device.h>
#include <86box/rom.h>
static int
foo1234_available()
{
return rom_present("roms/scsi/foo/foo1234.bin");
}
const device_t foo1234_device = {
/* ... */
{ .available = foo1234_available }, /* must have brackets due to the union */
/* ... */
};
Configuration
-------------
@@ -230,51 +240,57 @@ These options are stored in the emulated machine's configuration file, in a sect
midi_in = 0
Configuration options can be specified in the ``config`` member of ``device_t``, as a pointer to a ``const`` array of ``device_config_t`` objects terminated by an object of ``type`` ``-1``::
Configuration options can be specified in the ``config`` member of ``device_t``, as a pointer to a ``const`` array of ``device_config_t`` objects terminated by an object of ``type`` ``-1``:
#include <86box/device.h>
.. container:: toggle
/* ... */
.. container:: toggle-header
static const device_config_t foo_config[] = {
{ "selection", "Selection", CONFIG_SELECTION, "", 5, "", { 0 },
{
{ "IRQ 5", 5 },
{ "IRQ 7", 7 },
{ "" }
}
},
{ "hex16", "16-bit hex", CONFIG_HEX16, "", 0x220, "", { 0 },
{
{ "0x220", 0x220 },
{ "0x330", 0x330 },
{ "" }
}
},
{ "hex20", "20-bit hex", CONFIG_HEX20, "", 0xd8000, "", { 0 },
{
/* While the memory *segment* is displayed to the user, we store the
*linear* (segment << 4) base address in the configuration file. */
{ "D800h", 0xd8000 },
{ "DC00h", 0xdc000 },
{ "" }
}
},
{ "string", "String", CONFIG_STRING, "Default" },
{ "fname", "Filename", CONFIG_FNAME, "", 0, "File type (*.foo)|*.foo|Another file type (*.bar)|*.bar" },
{ "binary", "Binary", CONFIG_BINARY, "", 1 /* checked by default */ },
{ "int", "Integer", CONFIG_INT, "", 1234 },
{ "spinner", "Spinner", CONFIG_SPINNER, "", 1234, "", { 1204, 1294, 10 } },
{ "mac", "MAC address", CONFIG_MAC, "", 0 }
{ "midi_out", "MIDI output", CONFIG_MIDI_OUT, "", 0 },
{ "midi_in", "MIDI input", CONFIG_MIDI_IN, "", 0 },
{ "", "", -1 }
};
Code example: device configuration options
const device_t foo_device = {
/* ... */
.config = foo_config
};
.. code-block::
#include <86box/device.h>
static const device_config_t foo_config[] = {
{ "selection", "Selection", CONFIG_SELECTION, "", 5, "", { 0 },
{
{ "IRQ 5", 5 },
{ "IRQ 7", 7 },
{ "" }
}
},
{ "hex16", "16-bit hex", CONFIG_HEX16, "", 0x220, "", { 0 },
{
{ "0x220", 0x220 },
{ "0x330", 0x330 },
{ "" }
}
},
{ "hex20", "20-bit hex", CONFIG_HEX20, "", 0xd8000, "", { 0 },
{
/* While the memory *segment* is displayed to the user, we store the
*linear* (segment << 4) base address in the configuration file. */
{ "D800h", 0xd8000 },
{ "DC00h", 0xdc000 },
{ "" }
}
},
{ "string", "String", CONFIG_STRING, "Default" },
{ "fname", "Filename", CONFIG_FNAME, "", 0, "File type (*.foo)|*.foo|Another file type (*.bar)|*.bar" },
{ "binary", "Binary", CONFIG_BINARY, "", 1 /* checked by default */ },
{ "int", "Integer", CONFIG_INT, "", 1234 },
{ "spinner", "Spinner", CONFIG_SPINNER, "", 1234, "", { 1204, 1294, 10 } },
{ "mac", "MAC address", CONFIG_MAC, "", 0 }
{ "midi_out", "MIDI output", CONFIG_MIDI_OUT, "", 0 },
{ "midi_in", "MIDI input", CONFIG_MIDI_IN, "", 0 },
{ "", "", -1 }
};
const device_t foo_device = {
/* ... */
.config = foo_config
};
.. flat-table:: device_config_t
:header-rows: 1

View File

@@ -104,21 +104,29 @@ The same rule applies to write callbacks:
.. 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::
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:
typedef struct {
uint8_t regs[256];
} foo_t;
.. container:: toggle
static uint8_t
foo_io_inb(uint16_t addr, void *priv)
{
foo_t *dev = (foo_t *) priv;
return dev->regs[addr & 0xff]; /* example: register index = I/O port's least significant byte */
}
.. container:: toggle-header
/* 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. */
Code example: ``inb`` handler for reading 8-bit registers
.. code-block::
typedef struct {
uint8_t regs[256];
} foo_t;
static uint8_t
foo_io_inb(uint16_t addr, void *priv)
{
foo_t *dev = (foo_t *) priv;
return dev->regs[addr & 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
---------------------

View File

@@ -6,7 +6,74 @@ PCI
Adding a device
---------------
PCI devices can be added with the ``pci_add_card`` function. A slot is automatically selected according to the ``add_type``; if the emulated machine runs out of PCI slots, a **DEC 21150** PCI-PCI bridge is automatically deployed to add 9 more slots.
PCI devices can be added with the ``pci_add_card`` function in the device's ``init`` callback. A slot is :ref:`automatically selected <dev/api/pci:Slot types>` according to the ``add_type``; if the emulated machine runs out of PCI slots, a **DEC 21150** PCI-PCI bridge is automatically deployed to add 9 more slots.
.. container:: toggle
.. container:: toggle-header
Code example: adding a PCI device
.. code-block::
#include <86box/device.h>
#include <86box/pci.h>
#define FOO_ONBOARD 0x80000000 /* most significant bit set = on-board */
typedef struct {
uint8_t pci_regs[256];
int slot;
} foo_t;
static uint8_t
foo_pci_read(int func, int addr, void *priv)
{
/* Get the device state structure. */
foo_t *dev = (foo_t *) priv;
/* Ignore unknown functions. */
if (func > 0)
return 0xff;
/* Read configuration space register. */
return dev->pci_regs[addr];
}
static void
foo_pci_write(int func, int addr, uint8_t val, void *priv)
{
/* Get the device state structure. */
foo_t *dev = (foo_t *) priv;
/* Ignore unknown functions. */
if (func > 0)
return;
/* Write configuration space register. */
dev->pci_regs[addr] = val;
}
static void *
foo_init(const device_t *info)
{
/* Allocate the device state structure. */
foo_t *dev = /* ... */
/* Add PCI device. */
dev->slot = pci_add_card(PCI_ADD_NORMAL, foo_pci_read, foo_pci_write, dev);
return dev;
}
const device_t foo4321_device = {
.name = "Foo-4321",
.internal_name = "foo4321",
.flags = DEVICE_PCI,
.local = 4321,
.init = foo_init,
/* ... */
};
.. flat-table:: pci_add_card
:header-rows: 1
@@ -43,7 +110,7 @@ PCI devices can be added with the ``pci_add_card`` function. A slot is automatic
Usually a pointer to a device's :ref:`state structure <dev/api/device:State structure>`.
* - **Return value**
- ``int`` value (subject to change in the future) which uniquely identifies the newly-added device.
- ``int`` value (subject to change in the future) representing the newly-added device.
Slot types
----------
@@ -59,45 +126,56 @@ A machine may declare **special PCI slots** for specific purposes, such as on-bo
* ``PCI_ADD_NETWORK``: on-board network controller;
* ``PCI_ADD_NORTHBRIDGE``, ``PCI_ADD_AGPBRIDGE``, ``PCI_ADD_SOUTHBRIDGE``: reserved for the chipset.
A device available both as a discrete card and as an on-board device should have different ``device_t`` objects with unique ``local`` values to set both variants apart::
A device available both as a discrete card and as an on-board device should have different ``device_t`` objects with unique ``local`` values to set both variants apart.
#include <86box/device.h>
#include <86box/pci.h>
.. container:: toggle
#define FOO_ONBOARD 0x80000000 /* example: most significant bit set = on-board */
.. container:: toggle-header
/* ... */
Code example: device available as both discrete and on-board
static void *
foo_init(const device_t *info)
{
foo_t *dev = /* ... */
.. code-block::
/* Add PCI device. The normal variant goes in any normal slot,
and the on-board variant goes in the on-board SCSI "slot". */
pci_add_slot((info->local & FOO_ONBOARD) ? PCI_ADD_SCSI : PCI_ADD_NORMAL,
foo_pci_read, foo_pci_write, dev);
#include <86box/device.h>
#include <86box/pci.h>
/* ... */
}
#define FOO_ONBOARD 0x80000000 /* most significant bit set = on-board */
const device_t foo1234_device = {
.name = "Foo-1234",
.internal_name = "foo1234",
.flags = DEVICE_PCI,
.local = 1234, /* on-board bit not set */
.init = foo_init,
/* ... */
};
typedef struct {
int slot;
} foo_t;
const device_t foo1234_onboard_device = {
.name = "Foo-1234 (On-Board)",
.internal_name = "foo1234_onboard",
.flags = DEVICE_PCI,
.local = 1234 | FOO_ONBOARD, /* on-board bit set */
.init = foo_init,
/* ... */
};
static void *
foo_init(const device_t *info)
{
/* Allocate the device state structure. */
foo_t *dev = /* ... */
/* Add PCI device. The normal variant goes in any normal slot,
and the on-board variant goes in the on-board SCSI "slot". */
dev->slot = pci_add_card((info->local & FOO_ONBOARD) ? PCI_ADD_SCSI : PCI_ADD_NORMAL,
foo_pci_read, foo_pci_write, dev);
return dev;
}
const device_t foo4321_device = {
.name = "Foo-4321",
.internal_name = "foo4321",
.flags = DEVICE_PCI,
.local = 4321, /* on-board bit not set */
.init = foo_init,
/* ... */
};
const device_t foo4321_onboard_device = {
.name = "Foo-4321 (On-Board)",
.internal_name = "foo4321_onboard",
.flags = DEVICE_PCI,
.local = 4321 | FOO_ONBOARD, /* on-board bit set */
.init = foo_init,
/* ... */
};
Configuration space
-------------------
@@ -188,69 +266,80 @@ The ``func`` parameter passed to a device's configuration space read/write callb
1. The first function (function ``0``) must have bit 7 (``0x80``) of the Header Type (``0x0e``) register set;
2. Unused functions must return ``0xff`` on all configuration register reads and should ignore writes.
.. code-block::
typedef struct {
/* ... */
uint8_t pci_regs[2][256];
} foo_t;
.. container:: toggle
/* ... */
.. container:: toggle-header
static uint8_t
foo_pci_read(int func, int addr, void *priv)
{
/* Get the device state structure. */
foo_t *dev = (foo_t *) dev;
Code example: device with two functions
/* Read from a register on the given function. */
switch (func) {
case 0: /* function 0 */
return dev->pci_regs[0][addr];
.. code-block::
case 1: /* function 1 */
return dev->pci_regs[1][addr];
typedef struct {
uint8_t pci_regs[2][256]; /* two 256*8-bit register arrays, one for each function */
} foo_t;
default: /* out of range */
return 0xff;
static uint8_t
foo_pci_read(int func, int addr, void *priv)
{
/* Get the device state structure. */
foo_t *dev = (foo_t *) priv;
/* Read configuration space register on the given function. */
switch (func) {
case 0: /* function 0 */
return dev->pci_regs[0][addr];
case 1: /* function 1 */
return dev->pci_regs[1][addr];
default: /* out of range */
return 0xff;
}
}
}
static void
foo_pci_write(int func, int addr, uint8_t val, void *priv)
{
/* Get the device state structure. */
foo_t *dev = (foo_t *) dev;
static void
foo_pci_write(int func, int addr, uint8_t val, void *priv)
{
/* Get the device state structure. */
foo_t *dev = (foo_t *) priv;
/* Write to a register on the given function. */
switch (func) {
case 0: /* function 0 */
dev->pci_regs[0][addr] = val;
break;
/* Write configuration space register on the given function. */
switch (func) {
case 0: /* function 0 */
dev->pci_regs[0][addr] = val;
break;
case 1: /* function 1 */
dev->pci_regs[1][addr] = val;
break;
case 1: /* function 1 */
dev->pci_regs[1][addr] = val;
break;
default: /* out of range */
break;
default: /* out of range */
break;
}
}
}
/* ... */
static void
foo_reset(void *priv)
{
/* Get the device state structure. */
foo_t *dev = (foo_t *) priv;
static void
foo_reset(void *priv)
{
foo_t *dev = /* ... */
/* Reset PCI configuration registers. */
memset(dev->pci_regs[0], 0, sizeof(dev->pci_regs[0]));
memset(dev->pci_regs[0], 0, sizeof(dev->pci_regs[0]));
/* Flag this device as multi-function. */
dev->pci_regs[0][0x0e] = 0x80;
/* Write default vendor IDs, device IDs, etc. */
/* ... */
}
/* Flag this device as multi-function. */
dev->pci_regs[0][0x0e] = 0x80;
}
/* ... */
const device_t foo4321_device = {
/* ... */
.reset = foo_reset,
/* ... */
};
Base Address Registers
----------------------
@@ -270,15 +359,15 @@ The aforementioned base address alignment allows software (BIOSes and operating
.. container:: bit-table
.. flat-table:: Memory BAR (example: 4 KB large)
.. flat-table:: Memory BAR (example: 4 KB large, starting at 0x10)
:header-rows: 2
:stub-columns: 1
* - Byte
- :cspan:`7` BAR+3
- :cspan:`7` BAR+2
- :cspan:`7` BAR+1
- :cspan:`7` BAR+0
- :cspan:`7` 0x13
- :cspan:`7` 0x12
- :cspan:`7` 0x11
- :cspan:`7` 0x10
* - Bit
- 31
@@ -317,18 +406,22 @@ The aforementioned base address alignment allows software (BIOSes and operating
* - Value
- :cspan:`19` Base memory address (4096-byte aligned)
- :cspan:`7` Always ``0``
- :cspan:`2` Flags
- :cspan:`2`
.. raw:: html
Flags (<abbr title="Read-only">RO</span>)
- ``0``
.. flat-table:: I/O BAR (64 ports large)
.. flat-table:: I/O BAR (example: 64 ports large, starting at 0x14)
:header-rows: 2
:stub-columns: 1
* - Byte
- :cspan:`7` BAR+3
- :cspan:`7` BAR+2
- :cspan:`7` BAR+1
- :cspan:`7` BAR+0
- :cspan:`7` 0x17
- :cspan:`7` 0x16
- :cspan:`7` 0x15
- :cspan:`7` 0x14
* - Bit
- 31
@@ -370,165 +463,168 @@ The aforementioned base address alignment allows software (BIOSes and operating
- :cspan:`3` Always ``0``
- .. raw:: html
<abbr title="Reserved">R</abbr>
<abbr title="Reserved (read-only)">R</abbr>
- ``1``
.. code-block::
.. container:: toggle
#include <86box/io.h>
#include <86box/mem.h>
.. container:: toggle-header
typedef struct {
/* ... */
uint8_t pci_regs[256]; /* note: 1D array as this example is not multi-function */
uint16_t io_base;
mem_mapping_t mem_mapping;
} foo_t;
Code example: memory and I/O BARs descibed above
/* ... */
.. code-block::
static void
foo_remap_mem(foo_t *dev)
{
if (dev->pci_regs[0x04] & 0x02) {
/* Memory Space bit set, apply the base address.
Least significant bits are masked off to maintain 4096-byte alignment.
We skip reading dev->pci_regs[0x10] as it contains nothing of interest. */
mem_mapping_set_addr(&dev->mem_mapping,
((dev->pci_regs[0x11] << 8) | (dev->pci_regs[0x12] << 16) | (dev->pci_regs[0x13] << 24)) & 0xfffff000,
4096);
} else {
/* Memory Space bit not set, disable the mapping. */
mem_mapping_set_addr(&dev->mem_mapping, 0, 0);
}
}
#include <86box/io.h>
#include <86box/mem.h>
static void
foo_remap_io(foo_t *dev)
{
/* Remove existing I/O handler if present. */
if (dev->io_base)
io_removehandler(dev->io_base, 64,
foo_io_inb, foo_io_inw, foo_io_inl,
foo_io_outb, foo_io_outw, foo_io_outl, dev);
typedef struct {
uint8_t pci_regs[256];
uint16_t io_base;
mem_mapping_t mem_mapping;
} foo_t;
if (dev->pci_regs[0x04] & 0x01) {
/* I/O Space bit set, read the base address.
Least significant bits are masked off to maintain 64-byte alignment. */
dev->io_base = (dev->pci_regs[0x14] | (dev->pci_regs[0x15] << 8)) & 0xffc0;
} else {
/* I/O Space bit not set, don't do anything. */
dev->io_base = 0;
static void
foo_remap_mem(foo_t *dev)
{
if (dev->pci_regs[0x04] & 0x02) {
/* Memory Space bit set, apply the base address.
Least significant bits are masked off to maintain 4096-byte alignment.
We skip reading dev->pci_regs[0x10] as it contains nothing of interest. */
mem_mapping_set_addr(&dev->mem_mapping,
((dev->pci_regs[0x11] << 8) | (dev->pci_regs[0x12] << 16) | (dev->pci_regs[0x13] << 24)) & 0xfffff000,
4096);
} else {
/* Memory Space bit not set, disable the mapping. */
mem_mapping_set_addr(&dev->mem_mapping, 0, 0);
}
}
/* Add new I/O handler if required. */
if (dev->io_base)
io_sethandler(dev->io_base, 64,
foo_io_inb, foo_io_inw, foo_io_inl,
foo_io_outb, foo_io_outw, foo_io_outl, dev);
}
static void
foo_remap_io(foo_t *dev)
{
/* Remove existing I/O handler if present. */
if (dev->io_base)
io_removehandler(dev->io_base, 64,
foo_io_inb, foo_io_inw, foo_io_inl,
foo_io_outb, foo_io_outw, foo_io_outl, dev);
/* ... */
if (dev->pci_regs[0x04] & 0x01) {
/* I/O Space bit set, read the base address.
Least significant bits are masked off to maintain 64-byte alignment. */
dev->io_base = (dev->pci_regs[0x14] | (dev->pci_regs[0x15] << 8)) & 0xffc0;
} else {
/* I/O Space bit not set, don't do anything. */
dev->io_base = 0;
}
static void
foo_pci_write(int func, int addr, uint8_t val, void *priv)
{
/* Get the device state structure. */
foo_t *dev = (foo_t *) dev;
/* Ignore unknown functions. */
if (func > 0)
return;
/* Write register. */
switch (addr) {
case 0x04:
/* Our device only supports the I/O and Memory Space bits of the Command register. */
dev->pci_regs[addr] = val & 0x03;
/* Update memory and I/O spaces. */
foo_remap_mem(dev);
foo_remap_io(dev);
break;
case 0x10:
/* Least significant byte of the memory BAR is read-only. */
break;
case 0x11:
/* 2nd byte of the memory BAR is masked to maintain 4096-byte alignment. */
dev->pci_regs[addr] = val & 0xf0;
/* Update memory space. */
foo_remap_mem(dev);
break;
case 0x12: case 0x13:
/* 3rd and most significant bytes of the memory BAR are fully writable. */
dev->pci_regs[addr] = val;
/* Update memory space. */
foo_remap_mem(dev);
break;
case 0x14:
/* Least significant byte of the I/O BAR is masked to maintain 64-byte alignment, and
ORed with the default value's least significant bits so that the flags stay in place. */
dev->pci_regs[addr] = (val & 0xc0) | (dev->pci_regs[addr] & 0x03);
/* Update I/O space. */
foo_remap_io(dev);
break;
case 0x15:
/* Most significant byte of the I/O BAR is fully writable. */
dev->pci_regs[addr] = val;
/* Update I/O space. */
foo_remap_io(dev);
break;
case 0x16: case 0x17:
/* I/O BARs are only 2 bytes long, ignore the rest. */
break;
/* Add new I/O handler if required. */
if (dev->io_base)
io_sethandler(dev->io_base, 64,
foo_io_inb, foo_io_inw, foo_io_inl,
foo_io_outb, foo_io_outw, foo_io_outl, dev);
}
}
/* ... */
static void
foo_pci_write(int func, int addr, uint8_t val, void *priv)
{
/* Get the device state structure. */
foo_t *dev = (foo_t *) priv;
static void
foo_reset(void *priv)
{
foo_t *dev = /* ... */
/* Ignore unknown functions. */
if (func > 0)
return;
/* Example: the BAR at 0x10-0x13 is a memory BAR. */
dev->pci_regs[0x10] = 0x00; /* least significant bit not set = memory */
dev->pci_regs[0x11] = 0x00;
dev->pci_regs[0x12] = 0x00;
dev->pci_regs[0x13] = 0x00;
/* Write configuration space register. */
switch (addr) {
case 0x04:
/* Our device only supports the I/O and Memory Space bits of the Command register. */
dev->pci_regs[addr] = val & 0x03;
/* Example: the BAR at 0x14-0x17 is an I/O BAR. */
dev->pci_regs[0x14] = 0x01; /* least significant bit set = I/O */
dev->pci_regs[0x15] = 0x00;
dev->pci_regs[0x16] = 0x00;
dev->pci_regs[0x17] = 0x00;
/* Update memory and I/O spaces. */
foo_remap_mem(dev);
foo_remap_io(dev);
break;
/* Clear all BAR memory mappings and I/O handlers. */
dev->pci_regs[0x04] = 0x00;
foo_remap_mem(dev);
foo_remap_io(dev);
case 0x10:
/* Least significant byte of the memory BAR is read-only. */
break;
/* ... */
}
case 0x11:
/* 2nd byte of the memory BAR is masked to maintain 4096-byte alignment. */
dev->pci_regs[addr] = val & 0xf0;
/* Update memory space. */
foo_remap_mem(dev);
break;
case 0x12: case 0x13:
/* 3rd and most significant bytes of the memory BAR are fully writable. */
dev->pci_regs[addr] = val;
/* Update memory space. */
foo_remap_mem(dev);
break;
case 0x14:
/* Least significant byte of the I/O BAR is masked to maintain 64-byte alignment, and
ORed with the default value's least significant bits so that the flags stay in place. */
dev->pci_regs[addr] = (val & 0xc0) | (dev->pci_regs[addr] & 0x03);
/* Update I/O space. */
foo_remap_io(dev);
break;
case 0x15:
/* Most significant byte of the I/O BAR is fully writable. */
dev->pci_regs[addr] = val;
/* Update I/O space. */
foo_remap_io(dev);
break;
case 0x16: case 0x17:
/* I/O BARs are only 2 bytes long, ignore the rest. */
break;
}
}
static void
foo_reset(void *priv)
{
/* Get the device state structure. */
foo_t *dev = (foo_t *) dev;
/* Reset PCI configuration registers. */
memset(dev->pci_regs, 0, sizeof(dev->pci_regs));
/* Write default vendor ID, device ID, etc. */
/* The BAR at 0x10-0x13 is a memory BAR. */
//dev->pci_regs[0x10] = 0x00; /* least significant bit already not set = memory */
/* The BAR at 0x14-0x17 is an I/O BAR. */
dev->pci_regs[0x14] = 0x01; /* least significant bit set = I/O */
/* Clear all BAR memory mappings and I/O handlers. */
//dev->pci_regs[0x04] = 0x00; /* Memory and I/O Space bits already cleared */
foo_remap_mem(dev);
foo_remap_io(dev);
}
const device_t foo4321_device = {
/* ... */
.reset = foo_reset,
/* ... */
};
Option ROM
----------
A PCI function can have an **option ROM**, which behaves similarly to a :ref:`memory BAR <dev/api/pci:Base Address Registers>` in that the ROM can be mapped to any address in 32-bit memory space. As with BARs, the BIOS and/or operating system takes care of mapping; for example, a BIOS will map the primary PCI video card's ROM to the legacy ``0xc0000`` address.
A PCI function may have an **option ROM**, which behaves similarly to a :ref:`memory BAR <dev/api/pci:Base Address Registers>` in that the ROM can be mapped to any address in 32-bit memory space, aligned to its size. As with BARs, the BIOS and/or operating system takes care of mapping; for example, a BIOS will map the primary PCI video card's ROM to the legacy ``0xc0000`` address.
The main difference between this register and BARs is that the ROM can be enabled or disabled through this register's least significant bit. Both that bit and the Command (``0x04``) register's Memory Space bit (bit 1 or ``0x02``) must be set for the ROM to be accessible.
The main difference between this register and BARs is that the ROM can be enabled or disabled through bit 0 (``0x01``) of this register. Both that bit and the Command (``0x04``) register's Memory Space bit (bit 1 or ``0x02``) must be set for the ROM to be accessible.
.. note:: The minimum size for an option ROM is 4 KB (see the note about 86Box memory limitations on the :ref:`BAR <dev/api/pci:Base Address Registers>` section), and the maximum size is 16 MB.
.. note:: The minimum size for an option ROM is 4 KB (see the note about 86Box memory limitations in the :ref:`BAR <dev/api/pci:Base Address Registers>` section), and the maximum size is 16 MB.
.. container:: bit-table
@@ -581,121 +677,132 @@ The main difference between this register and BARs is that the ROM can be enable
- :cspan:`13` Always ``0``
- .. raw:: html
<abbr title="Enable">E</span>
<abbr title="ROM Enable">E</span>
.. code-block::
.. container:: toggle
#include <86box/mem.h>
#include <86box/rom.h>
.. container:: toggle-header
typedef struct {
/* ... */
uint8_t pci_regs[256]; /* note: 1D array as this example is not multi-function */
rom_t rom;
} foo_t;
Code example: 32 KB option ROM
/* ... */
.. code-block::
static void
foo_remap_rom(foo_t *dev)
{
if ((dev->pci_regs[0x30] & 0x01) && (dev->pci_regs[0x04] & 0x02)) {
/* Expansion ROM Enable and Memory Space bits set, apply the base address.
Least significant bits are masked off to maintain 32768-byte alignment.
We skip reading dev->pci_regs[0x30] as it contains nothing of interest. */
mem_mapping_set_addr(&dev->rom.mapping,
((dev->pci_regs[0x31] << 8) | (dev->pci_regs[0x32] << 16) | (dev->pci_regs[0x33] << 24)) & 0xffff8000,
4096);
} else {
/* Expansion ROM Enable and/or Memory Space bits not set, disable the mapping. */
#include <86box/mem.h>
#include <86box/rom.h>
typedef struct {
uint8_t pci_regs[256];
rom_t rom;
} foo_t;
static void
foo_remap_rom(foo_t *dev)
{
if ((dev->pci_regs[0x30] & 0x01) && (dev->pci_regs[0x04] & 0x02)) {
/* Expansion ROM Enable and Memory Space bits set, apply the base address.
Least significant bits are masked off to maintain 32768-byte alignment.
We skip reading dev->pci_regs[0x30] as it contains nothing of interest. */
mem_mapping_set_addr(&dev->rom.mapping,
((dev->pci_regs[0x31] << 8) | (dev->pci_regs[0x32] << 16) | (dev->pci_regs[0x33] << 24)) & 0xffff8000,
4096);
} else {
/* Expansion ROM Enable and/or Memory Space bits not set, disable the mapping. */
mem_mapping_set_addr(&dev->rom.mapping, 0, 0);
}
}
static void
foo_pci_write(int func, int addr, uint8_t val, void *priv)
{
/* Get the device state structure. */
foo_t *dev = (foo_t *) priv;
/* Ignore unknown functions. */
if (func > 0)
return;
/* Write configuration space register. */
switch (addr) {
case 0x04:
/* Our device only supports the Memory Space bit of the Command register. */
dev->pci_regs[addr] = val & 0x02;
/* Update ROM space. */
foo_remap_rom(dev);
break;
case 0x30:
/* Least significant byte of the ROM address is read-only, except for the enable bit. */
dev->pci_regs[addr] = val & 0x01;
/* Update ROM space. */
foo_remap_rom(dev);
break;
case 0x31:
/* 2nd byte of the ROM address is masked to maintain 32768-byte alignment. */
dev->pci_regs[addr] = val & 0x80;
/* Update ROM space. */
foo_remap_rom(dev);
break;
case 0x32: case 0x33:
/* 3rd and most significant bytes of the ROM address are fully writable. */
dev->pci_regs[addr] = val;
/* Update ROM space. */
foo_remap_rom(dev);
break;
}
}
static void
foo_reset(void *priv)
{
/* Get the device state structure. */
foo_t *dev = (foo_t *) dev;
/* Reset PCI configuration registers. */
memset(dev->pci_regs, 0, sizeof(dev->pci_regs));
/* Clear ROM memory mapping. */
//dev->pci_regs[0x04] = 0x00; /* Memory Space bit already cleared */
//dev->pci_regs[0x30] = 0x00; /* Expansion ROM Enable bit already cleared */
foo_remap_rom(dev);
}
static int
foo_available()
{
/* This device can only be used if its ROM is present. */
return rom_present("roms/scsi/foo/foo4321.bin");
}
static void *
foo_init(const device_t *info)
{
/* Allocate the device state structure. */
foo_t *dev = /* ... */
/* Don't forget to add the PCI device. */
/* Load 32 KB ROM... */
rom_init(&dev->rom, "roms/scsi/foo/foo4321.bin", 0, 0x8000, 0x7fff, 0, MEM_MAPPING_EXTERNAL);
/* ...but don't map it right now. */
mem_mapping_set_addr(&dev->rom.mapping, 0, 0);
return dev;
}
}
/* ... */
static void
foo_pci_write(int func, int addr, uint8_t val, void *priv)
{
/* Get the device state structure. */
foo_t *dev = (foo_t *) dev;
/* Ignore unknown functions. */
if (func > 0)
return;
/* Write register. */
switch (addr) {
case 0x04:
/* Our device only supports the Memory Space bit of the Command register. */
dev->pci_regs[addr] = val & 0x02;
/* Update ROM space. */
foo_remap_rom(dev);
break;
case 0x30:
/* Least significant byte of the ROM address is read-only, except for the enable bit. */
dev->pci_regs[addr] = val & 0x01;
/* Update ROM space. */
foo_remap_rom(dev);
break;
case 0x31:
/* 2nd byte of the ROM address is masked to maintain 32768-byte alignment. */
dev->pci_regs[addr] = val & 0x80;
/* Update ROM space. */
foo_remap_rom(dev);
break;
case 0x32: case 0x33:
/* 3rd and most significant bytes of the ROM address are fully writable. */
dev->pci_regs[addr] = val;
/* Update ROM space. */
foo_remap_rom(dev);
break;
}
}
/* ... */
static void
foo_reset(void *priv)
{
foo_t *dev = /* ... */
/* Clear ROM memory mapping. */
dev->pci_regs[0x04] = 0x00;
foo_remap_rom(dev);
/* ... */
}
static int
foo_available()
{
/* This device can only be used if its ROM is present. */
return rom_present("roms/scsi/foo/foo4321.bin");
}
static void *
foo_init(const device_t *info)
{
foo_t *dev = /* ... */
/* Example: load 32 KB ROM... */
rom_init(&dev->rom, "roms/scsi/foo/foo4321.bin", 0, 0x8000, 0x7fff, 0, MEM_MAPPING_EXTERNAL);
/* ...but don't map it right now. */
mem_mapping_set_addr(&dev->rom.mapping, 0, 0);
/* ... */
}
/* ... */
const device_t foo4321_device = {
/* ... */
.init = foo_init,
.reset = foo_reset,
{ .available = foo_available },
/* ... */
};
Interrupts
----------

View File

@@ -8,39 +8,48 @@ Timers
Adding
------
Timers can be added with the ``timer_add`` function. The best place for adding a timer is in a :doc:`device <device>`'s ``init`` callback, storing the ``pc_timer_t`` object in the :ref:`state structure <dev/api/device:State structure>`::
Timers can be added with the ``timer_add`` function. The best place for adding a timer is in a :doc:`device <device>`'s ``init`` callback, storing the ``pc_timer_t`` object in the :ref:`state structure <dev/api/device:State structure>`.
#include <86box/device.h>
#include <86box/timer.h>
.. container:: toggle
typedef struct {
/* ... */
pc_timer_t countdown_timer;
} foo_t;
.. container:: toggle-header
/* ... */
Code example: adding a timer
/* Called once the timer period is reached. */
static void
foo_countdown_timer(void *priv)
{
foo_t *dev = (foo_t *) priv;
.. code-block::
/* Do whatever you want. */
}
#include <86box/device.h>
#include <86box/timer.h>
/* ... */
typedef struct {
pc_timer_t countdown_timer;
} foo_t;
static void *
foo_init(const device_t *info)
{
foo_t *dev = /* ... */
/* Called once the timer period is reached. */
static void
foo_countdown_timer(void *priv)
{
/* Get the device state structure. */
foo_t *dev = (foo_t *) priv;
/* Add timer. */
timer_add(&dev->countdown_timer, foo_countdown_timer, foo, 0);
/* Do whatever you want. */
}
/* ... */
}
static void *
foo_init(const device_t *info)
{
/* Allocate the device state structure. */
foo_t *dev = /* ... */
/* Add timer. */
timer_add(&dev->countdown_timer, foo_countdown_timer, foo, 0);
}
const foo1234_device = {
/* ... */
.init = foo_init,
/* ... */
};
.. flat-table:: timer_add
:header-rows: 1
@@ -69,54 +78,52 @@ Timers can be added with the ``timer_add`` function. The best place for adding a
Triggering
----------
The ``timer_on_auto`` function can be used to start (with the provided microsecond period) or stop a timer. It can also be called from a timer callback to restart the timer::
The ``timer_on_auto`` function can be used to start (with the provided microsecond period) or stop a timer. It can also be called from a timer callback to restart the timer:
#include <86box/timer.h>
.. container:: toggle
typedef struct {
/* ... */
uint8_t regs[256];
pc_timer_t countdown_timer;
} foo_t;
.. container:: toggle-header
/* ... */
Code example: starting, restarting and stopping a timer
static void
foo_countdown_timer(void *priv)
{
/* Get the device state structure. */
foo_t *dev = (foo_t *) priv;
.. code-block::
/* ... */
#include <86box/timer.h>
/* Example: restart timer automatically if bit 1 (0x02) of register 0x80 is set. */
if (dev->regs[0x80] & 0x02)
timer_on_auto(&dev->countdown_timer, 100.0);
}
typedef struct {
uint8_t regs[256];
pc_timer_t countdown_timer; /* remember to timer_add on init, per the example above */
} foo_t;
/* Example: writing to I/O port register 0x__80:
- Bit 0 (0x01) set: start 100-microsecond countdown timer;
- Bit 0 (0x01) clear: stop countdown timer. */
static void
foo_outb(uint16_t addr, uint8_t val, void *priv)
{
/* Get the device state structure. */
foo_t *dev = (foo_t *) priv;
static void
foo_countdown_timer(void *priv)
{
/* Get the device state structure. */
foo_t *dev = (foo_t *) priv;
/* ... */
if ((addr & 0xff) == 0x80) {
dev->regs[0x80] = val;
if (val & 0x01)
/* Restart timer automatically if bit 1 (0x02) of register 0x80 is set. */
if (dev->regs[0x80] & 0x02)
timer_on_auto(&dev->countdown_timer, 100.0);
else
timer_on_auto(&dev->countdown_timer, 0.0);
}
/* ... */
}
/* Example: writing to I/O port register 0x__80:
- Bit 0 (0x01) set: start 100-microsecond countdown timer;
- Bit 0 (0x01) clear: stop countdown timer. */
static void
foo_outb(uint16_t addr, uint8_t val, void *priv)
{
/* Get the device state structure. */
foo_t *dev = (foo_t *) priv;
/* ... */
/* Handle writes to register 0x80. */
if ((addr & 0xff) == 0x80) {
dev->regs[0x80] = val;
if (val & 0x01)
timer_on_auto(&dev->countdown_timer, 100.0);
else
timer_on_auto(&dev->countdown_timer, 0.0);
}
}
.. flat-table:: timer_on_auto
:header-rows: 1