More API progress

This commit is contained in:
RichardG867
2022-03-19 15:50:21 -03:00
parent 21227970dc
commit 7ac62f7cdf
6 changed files with 432 additions and 113 deletions

View File

@@ -16,24 +16,24 @@ The **device** is the main unit of emulated components in 86Box. Each device has
- The device's name, displayed in the user interface. ``"Foo-1234"`` for example. Suffixes like ``"(PCI)"`` are removed at run-time.
* - :cspan:`1` internal_name
- The device's internal name, used to identify the device in the emulated machine's configuration file. ``"foo1234"`` for example.
- The device's internal name, used to identify it in the emulated machine's configuration file. ``"foo1234"`` for example.
* - :cspan:`1` flags
- One or more bit flags to indicate the expansion bus(es) supported by the device, for determining device eligibility on the selected machine:
- One or more bit flags to indicate the expansion bus(es) supported by the device, for determining :ref:`device availability <dev/api/device:Availability>` on the selected machine:
* ``DEVICE_ISA``: 8-bit ISA;
* ``DEVICE_AT``: 16-bit ISA;
* ``DEVICE_EISA``: EISA (reserved for future use);
* ``DEVICE_VLB``: VESA Local Bus;
* ``DEVICE_VLB``: VESA Local Bus or proprietary equivalents;
* ``DEVICE_PCI``: 32-bit PCI;
* ``DEVICE_AGP``: AGP 3.3V;
* ``DEVICE_AC97``: AMR or CNR;
* ``DEVICE_AC97``: AMR, CNR or ACR;
* ``DEVICE_PCJR``: IBM PCjr;
* ``DEVICE_PS2``: IBM PS/1 or PS/2;
* ``DEVICE_MCA``: IBM Micro Channel Architecture;
* ``DEVICE_CBUS``: PC-98 C-BUS (reserved for future use);
* ``DEVICE_COM``: serial (reserved for future use);
* ``DEVICE_LPT``: parallel (reserved for future use).
* ``DEVICE_COM``: serial port (reserved for future use);
* ``DEVICE_LPT``: parallel port (reserved for future use).
* - :cspan:`1` local
- 32-bit value which can be read from this structure by the ``init`` callback.
@@ -79,7 +79,7 @@ The **device** is the main unit of emulated components in 86Box. Each device has
``int poll(int x, int y, int z, int b, void *priv)``
* ``x`` and ``y``: relative mouse movement coordinates (signed);
* ``z``: relative wheel movement coordinate (signed);
* ``z``: relative scroll wheel movement coordinate (signed);
* ``b``: button state: bit 0 (0x1) set if left button pressed, bit 1 (0x2) set if right button pressed, bit 2 (0x4) set if middle button pressed;
* ``priv``: opaque pointer previously returned by ``init``;
* Return value: ``0`` if the change was processed, or any other value otherwise.
@@ -88,7 +88,7 @@ The **device** is the main unit of emulated components in 86Box. Each device has
- Reserved for future use.
* - :cspan:`1` speed_changed
- Function called whenever the emulated CPU clock speed is changed. Can be ``NULL``. Timer intervals and anything else sensitive to the CPU clock speed should be updated in this callback. Takes the form of:
- Function called whenever the emulated CPU clock speed is changed. Can be ``NULL``. Timer intervals (when using the undocumented legacy timer API) and anything else sensitive to the CPU clock speed should be updated in this callback. Takes the form of:
``void speed_changed(void *priv)``
@@ -107,61 +107,100 @@ The **device** is the main unit of emulated components in 86Box. Each device has
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::
typedef struct {
uint32_t type; /* example: copied from device_t.local */
uint8_t regs[256]; /* example: 256*8-bit registers */
} foo_t;
#include <86box/device.h>
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));
typedef struct {
uint32_t type; /* example: copied from device_t.local */
uint8_t regs[256]; /* example: 256*8-bit registers */
} foo_t;
/* Do whatever you want. */
dev->type = info->local; /* copy device_t.local value */
/* ... */
/* Return a pointer to the state structure. */
return dev;
}
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));
static void
foo_close(void *priv)
{
/* Get the state structure. */
foo_t *dev = (foo_t *) priv;
/* Do whatever you want. */
dev->type = info->local; /* copy device_t.local value */
/* Do whatever you want, then deallocate the state structure. */
free(dev);
}
/* Return a pointer to the state structure. */
return 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,
/* ... */
};
static void
foo_close(void *priv)
{
/* Get the state structure. */
foo_t *dev = (foo_t *) priv;
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,
/* ... */
};
/* 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
------------
Availability
------------
A device will be available for selection by the user if these criteria are met:
1) The device is :ref:`registered <dev/api/device:Registration>`, so that the user interface knows about it;
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::
#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
-------------
Devices can have any number of user-facing configuration options, usually accessed through the **Configure** button next to the selection box for the device's class. Examples for all option types currently configurable through the user interface are shown in the image below.
Devices can have any number of user-facing configuration options, usually accessed through the **Configure** button next to the selection box for the device's class. Examples for all option types currently configurable through the user interface are shown in the image below. [TO BE UPDATED ONCE I GET OUT OF HIDPI]
.. image:: images/deviceconfig.png
:align: center
@@ -170,56 +209,62 @@ These options are stored in the emulated machine's configuration file, in a sect
.. code-block:: none
[Foo-1234]
selection = 0
hex16 = 0220
hex20 = D8000
fname = D:/VMs/86Box/86Box.exe
binary = 1
spinner = 1234
midi_out = 0
midi_in = 0
[Foo-1234]
selection = 0
hex16 = 0220
hex20 = D8000
fname = D:/VMs/86Box/86Box.exe
binary = 1
spinner = 1234
midi_out = 0
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``::
static const device_config_t foo_config[] = {
{ "selection", "Selection", CONFIG_SELECTION, "", 0, "", { 0 },
{
{ "Option", 0 },
{ "Another option", 1 },
{ "" }
}
},
{ "hex16", "16-bit hex", CONFIG_HEX16, "", 0x220, "", { 0 },
{
{ "0x220", 0x220 },
{ "0x330", 0x330 },
{ "" }
}
},
{ "hex20", "20-bit hex", CONFIG_HEX20, "", 0xd8000, "", { 0 },
{
{ "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_int", "MIDI input", CONFIG_MIDI_IN, "", 0 },
{ "", "", -1 }
};
#include <86box/device.h>
const device_t foo_device = {
/* ... */
.config = foo_config
}
/* ... */
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
@@ -229,7 +274,7 @@ Configuration options can be specified in the ``config`` member of ``device_t``,
- Description
* - name
- Internal name for this option, stored in the emulated machine's configuration file.
- Internal name for this option, used to identify it in the emulated machine's configuration file.
* - description
- Description for this option, displayed in the user interface.
@@ -254,7 +299,7 @@ Configuration options can be specified in the ``config`` member of ``device_t``,
- Default string value for a ``CONFIG_STRING`` option. Can be ``""`` if not applicable.
* - default_int
- Default integer value for a ``CONFIG_HEX16``, ``CONFIG_HEX20``, ``CONFIG_INT`` or ``CONFIG_SPINNER`` option. Can be 0 if not applicable.
- Default integer value for a ``CONFIG_HEX16``, ``CONFIG_HEX20``, ``CONFIG_BINARY``, ``CONFIG_INT`` or ``CONFIG_SPINNER`` option. Can be ``0`` if not applicable.
* - file_filter
- File type filter for a ``CONFIG_FNAME`` option. Can be ``""`` if not applicable. Must be specified in Windows ``description|mask|description|mask...`` format, for example:
@@ -294,9 +339,9 @@ Configuration options can be specified in the ``config`` member of ``device_t``,
- Description for this choice, displayed in the user interface.
* - value
- Integer value corresponding to this choice, stored in the emulated machine's configuration file.
- Integer value corresponding to this choice, used to identify it in the emulated machine's configuration file.
Configured option values can be accessed using ``device_get_config_*`` functions from within the device's ``init`` callback.
Configured option values can be read from within the device's ``init`` callback with the ``device_get_config_*`` functions. These functions automatically operate in the context of the device currently being initialized.
.. note:: ``device_get_config_*`` functions should **never** be called outside of a device's ``init`` callback. You are responsible for reading the options' configured values in the ``init`` callback and storing them in the device's :ref:`state structure <dev/api/device:State structure>` if necessary.

View File

@@ -8,7 +8,7 @@ DMA
8237 DMA
--------
``86box/dma.h`` provides the ``dma_channel_read`` and ``dma_channel_write`` functions to read or write (respectively) a value from or to an 8237 DMA channel.
``86box/dma.h`` provides the ``dma_channel_read`` and ``dma_channel_write`` functions to read or write (respectively) a value from or to an **8237 DMA channel**.
.. flat-table:: dma_channel_read
:header-rows: 1
@@ -23,7 +23,7 @@ DMA
* - **Return value**
- 8- (channels ``0``-``3``) or 16-bit (channels ``5``-``7``) value read from the given DMA channel, or ``DMA_NODATA`` if no data was read.
May include a ``DMA_OVER`` bit flag (located above the most significant data bit so as to not affect the data) indicating that this was the last byte or word transferred, after which the channel is auto-initialized or masked depending on its configuration.
May include a ``DMA_OVER`` bit flag (located above the most significant data bit so as to not interfere with the data) indicating that this was the last byte or word transferred, after which the channel is auto-initialized or masked depending on its configuration.
.. flat-table:: dma_channel_write
:header-rows: 1
@@ -46,7 +46,7 @@ DMA
Direct memory read/write
------------------------
``86box/mem.h`` provides the ``mem_read*_phys`` and ``mem_write*_phys`` functions, which directly read or write physical memory. These are useful for PCI devices, which have to perform DMA on their own.
``86box/mem.h`` provides the ``mem_read*_phys`` and ``mem_write*_phys`` functions, which read or write physical memory directly. These are useful for **PCI devices**, which perform DMA on their own.
.. flat-table:: mem_readb_phys / mem_readw_phys / mem_readl_phys
:header-rows: 1

View File

@@ -1,11 +1,13 @@
API
===
This section documents the internal **Application Programming Interface** for extending on 86Box.
This section documents the internal **Application Programming Interface** for extending 86Box.
.. toctree::
:maxdepth: 1
device
timer
thread
io
dma

View File

@@ -13,20 +13,20 @@ Port I/O
- Description
* - base
- The first I/O port (0x0000-0xFFFF) to be covered by this handler.
- First I/O port (0x0000-0xFFFF) covered by this handler.
* - size
- The amount of I/O ports (1-65536) starting at ``base`` to be covered by this handler.
- Amount of I/O ports (1-65536) covered by this handler, starting at ``base``.
* - inb
- :rspan:`2` I/O read operation callback functions. Can be ``NULL``. Each callback takes the form of:
``TYPE callback(uint16_t addr, void *priv)``
* ``TYPE``: operation width (``uint8_t`` for ``inb``, ``uint16_t`` for ``inw`` or ``uint32_t`` for ``inl``);
* ``TYPE``: operation width: ``uint8_t`` for ``inb``, ``uint16_t`` for ``inw``, ``uint32_t`` for ``inl``;
* ``addr``: exact I/O port being read;
* ``priv``: opaque pointer (see ``priv`` below);
* Return value: value read from this port.
* Return value: 8- (``inb``), 16- (``inw``) or 32-bit (``inl``) value read from this port.
* - inw
@@ -37,9 +37,9 @@ Port I/O
``void callback(uint16_t addr, TYPE val, void *priv)``
* ``TYPE``: operation width (``uint8_t`` for ``outb``, ``uint16_t`` for ``outw`` or ``uint32_t`` for ``outl``);
* ``addr``: exact I/O port being written;
* ``val``: value being written to this port;
* ``TYPE``: operation width: ``uint8_t`` for ``outb``, ``uint16_t`` for ``outw``, ``uint32_t`` for ``outl``;
* ``val``: 8- (``outb``), 16- (``outw``) or 32-bit (``outl``) value being written to this port;
* ``priv``: opaque pointer (see ``priv`` below).
* - outw
@@ -47,8 +47,8 @@ Port I/O
* - outl
* - priv
- Opaque pointer, passed to this handler's read/write operation callbacks.
Usually a pointer to the device's :ref:`state structure <dev/api/device:State structure>`.
- Opaque pointer passed to this handler's read/write operation callbacks.
Usually a pointer to a device's :ref:`state structure <dev/api/device: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.
@@ -112,7 +112,7 @@ This feature's main use cases are devices which store registers that are 8-bit w
uint8_t regs[256];
} foo_t;
uint8_t
static uint8_t
foo_inb(uint16_t addr, void *priv)
{
foo_t *dev = (foo_t *) priv;

135
dev/api/thread.rst Normal file
View File

@@ -0,0 +1,135 @@
.. include:: /include.rst
Threads
=======
Compute-intensive tasks can be offloaded from the main emulation flow with **threads**. Unless otherwise stated, all structures, functions and constants in this page are provided by ``86box/plat.h``.
.. warning:: 86Box API functions (excluding those in this page) are generally **not thread-safe** and must be called from the **main emulation thread**. Thread-unsafe actions (like raising an interrupt) can be performed by the callback of a free-running :doc:`timer <timer>` which looks for data written to the device's :ref:`state structure <dev/api/device:State structure>` by a thread, as timers run on the main emulation thread.
.. note:: The contents of ``thread_t`` and other structures used by ``thread_*`` functions are platform-specific; therefore, pointers to those structures should be treated as opaque pointers.
Starting
--------
Threads can be started with the ``thread_create`` function. Additionally, the ``thread_wait`` function can be used to wait for a thread's function to return.
.. flat-table:: thread_create
:header-rows: 1
:widths: 1 999
* - Parameter
- Description
* - thread_func
- Function to run in the thread. Takes the form of:
``void thread_func(void *priv)``
* ``priv``: opaque pointer (see ``priv`` below).
* - priv
- Opaque pointer passed to the ``thread_func`` above.
Usually a pointer to a device's :ref:`state structure <dev/api/device:State structure>`.
* - **Return value**
- ``thread_t`` pointer representing the newly-created thread.
That pointer will become **invalid** once the thread's function returns.
.. flat-table:: thread_wait
:header-rows: 1
:widths: 1 999
* - Parameter
- Description
* - arg
- ``thread_t`` pointer representing the thread to wait for.
* - **Return value**
- * ``0`` on success;
* Any other value on failure.
Events
------
**Events** allow for synchronization between threads. An event, represented by an ``event_t`` pointer returned by the ``thread_create_event`` function, can be *set* (``thread_set_event`` function) or *reset* (``thread_reset_event`` function), and a thread can wait for an event to be *set* with the ``thread_wait_event`` function. Events that are no longer to be used should be deallocated with the ``thread_destroy_event`` function.
.. flat-table:: thread_create_event
:header-rows: 1
:widths: 1 999
* - Parameter
- Description
* - **Return value**
- ``event_t`` pointer representing the newly-created event.
.. flat-table:: thread_set_event / thread_reset_event / thread_destroy_event
:header-rows: 1
:widths: 1 999
* - Parameter
- Description
* - arg
- ``event_t`` pointer representing the event to *set* (``thread_set_event``), *reset* (``thread_reset_event``) or deallocate (``thread_destroy_event``).
.. flat-table:: thread_wait_event
:header-rows: 1
:widths: 1 999
* - Parameter
- Description
* - arg
- ``event_t`` pointer representing the event to wait for.
* - timeout
- Maximum amount of time in **milliseconds** (not microseconds, unlike :doc:`timers <timer>`) to spend waiting for the event to be *set*. If set to ``-1``, this function will not return until the event is *set*.
* - **Return value**
- * ``0`` on success;
* Any other value if ``timeout`` was reached or the wait otherwise failed.
.. note:: A ``thread_wait_event`` call does not *reset* the event once it is *set*; the event must be *reset* manually with ``thread_reset_event``. ``thread_wait_event`` returns immediately if the event is already *set*.
Mutexes
-------
`Mutexes <https://en.wikipedia.org/wiki/Mutual_exclusion>`_, also known as **locks**, can control access to a shared resource, ensuring no concurrent modifications or other issues arise from multiple threads attempting to use the same resource at the same time. A mutex, represented by a ``mutex_t`` pointer returned by the ``thread_create_mutex`` function, can be *locked* with the ``thread_wait_mutex`` function (which waits until the mutex is *released*) and *released* with the ``thread_release_mutex`` function. Additionally, the status of a mutex can be independently checked with the ``thread_test_mutex`` function. Mutexes that are no longer to be used should be deallocated with the ``thread_close_mutex`` function.
.. flat-table:: thread_create_mutex
:header-rows: 1
:widths: 1 999
* - Parameter
- Description
* - **Return value**
- ``mutex_t`` pointer representing the newly-created mutex.
.. flat-table:: thread_wait_mutex / thread_release_mutex / thread_close_mutex
:header-rows: 1
:widths: 1 999
* - Parameter
- Description
* - arg
- ``mutex_t`` pointer representing the mutex to *lock* (``thread_wait_mutex``), *release* (``thread_release_mutex``) or deallocate (``thread_close_mutex``).
If the mutex is locked, ``thread_wait_mutex`` will not return until the mutex is *released* by another thread.
.. flat-table:: thread_test_mutex
:header-rows: 1
:widths: 1 999
* - Parameter
- Description
* - arg
- ``mutex_t`` pointer representing the mutex to check.
* - **Return value**
- * ``0`` if the mutex is *locked*;
* Any other value if the mutex is *released*.

137
dev/api/timer.rst Normal file
View File

@@ -0,0 +1,137 @@
.. include:: /include.rst
Timers
======
**Timers** allow for devices to perform tasks after a set period. This period is **automatically scaled** to match the emulation speed, which helps 86Box stay relatively accurate, unlike other emulators and virtualizers which may operate timers in real time independently of speed. Unless otherwise stated, all structures, functions and constants in this page are provided by ``86box/timer.h``.
.. note:: Timers are processed after each instruction in interpreter mode, or each recompiled code block in dynamic recompiler mode (unless an instruction requests a Time Stamp Counter (TSC) update). In both cases, timer accuracy **should** be in the single-digit microseconds at a minimum, which is good enough for most time-sensitive applications like 48 KHz audio playback.
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>`::
#include <86box/device.h>
#include <86box/timer.h>
typedef struct {
/* ... */
pc_timer_t countdown_timer;
} foo_t;
/* ... */
/* Called once the timer period is reached. */
static void
foo_countdown_timer(void *priv)
{
foo_t *dev = (foo_t *) priv;
/* Do whatever you want. */
}
/* ... */
static void *
foo_init(const device_t *info)
{
foo_t *dev = /* ... */
/* Add timer. */
timer_add(&dev->countdown_timer, foo_countdown_timer, foo, 0);
/* ... */
}
.. flat-table:: timer_add
:header-rows: 1
:widths: 1 999
* - Parameter
- Description
* - timer
- Pointer to a ``pc_timer_t`` object stored somewhere, usually in a device's :ref:`state structure <dev/api/device:State structure>`.
* - callback
- Function called every time the timer's period is reached. Takes the form of:
``void callback(void *priv)``
* ``priv``: opaque pointer (see ``priv`` below).
* - priv
- Opaque pointer passed to the ``callback`` above.
Usually a pointer to a device's :ref:`state structure <dev/api/device:State structure>`.
* - start_timer
- Part of the :ref:`legacy API <dev/api/timer:Legacy API>`, should always be ``0``.
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::
#include <86box/timer.h>
typedef struct {
/* ... */
uint8_t regs[256];
pc_timer_t countdown_timer;
} foo_t;
/* ... */
static void
foo_countdown_timer(void *priv)
{
foo_t *dev = (foo_t *) priv;
/* ... */
/* 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);
}
/* 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)
{
foo_t *dev = (foo_t *) priv;
/* ... */
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
:widths: 1 999
* - Parameter
- Description
* - timer
- Pointer to the timer's ``pc_timer_t`` object.
* - period
- Period after which the timer callback is called, in microseconds (1/1,000,000th of a second or 1/1,000th of a millisecond) as a ``double``.
A period of ``0.0`` stops the timer if it's active.
Legacy API
----------
Existing devices may use the ``timer_set_delay_u64`` and ``timer_advance_u64`` functions, which are considered legacy and will not be documented here for simplicity. These functions used an internal 64-bit period unit, which had to be obtained by multiplying the microsecond value by the ``TIMER_USEC`` constant, and updated by the device's ``speed_changed`` callback. The new ``timer_on_auto`` function is much simpler, requiring no constant multiplication or updates.