All About Devices

From MAMEDEV Wiki
Revision as of 03:25, 25 November 2014 by Stiletto (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Devices in MAME are a mechanism for encapsulating a particular bit of functionality in a standard way. All devices have a set of common characteristics (how they start, reset, interact with the rest of the system) which makes it possible for the MAME core to treat them equally, and allows for a driver to plug any number of them together to create a working system.

The first thing to be aware of when thinking about devices is that you shouldn't get too hung up on the term "device". A device in MAME doesn't have to correspond to a physical chip or device or anything that concrete (although they can and often do). Instead, just think of a device as a means of grouping a set of functions and behaviors together and calling it something. They are a preferred means of wrapping together functions that are used across multiple machines.

Devices in MAME might seem like a relatively recent concept, but in reality they are simply the culmination of previous efforts to define a consistent "plug-in" style interface for assembling a machine. CPU cores were the first device-like objects which exposed a consistent interface (see cpuintrf.h (broken link) for definitions). Later on, the sound cores got their own similar interface (see sndintrf.h (broken link)). It has only been recently that a more abstract device interface (see devintrf.h (broken link)) has been introduced. Eventually, the CPU and sound interfaces will be retooled to be based fully on the abstract device interface, and then everything will be able to be treated equally as a generic device.

Devices have many nice features beyond their consistent interface that makes them useful in MAME:

  • They demand dynamic allocation of state (instead of relying on static global variables)
  • They demand support for an arbitrary number of instances (no more hard-coded limits)
  • They are uniquely identifiable via their required tag
  • The preferred way to locate devices is by tag, not by index, removing dependencies on ordering and the possibility of conflicts
  • They are described directly in the machine configuration structure
  • They can be configured entirely within the machine configuration structure (as opposed to requiring an external struct)
  • They can be directly mapped in the address map, referenced by tag
  • They can add to or modify the machine configuration they are part of
  • They can reference their own device-specific ROMs

How this all works is explained in more detail below, in the section on writing a new device.

However, the first and most important thing to know is: as a user, how do you add, remove, configure, and interact with devices?

Using Devices

Declaring Devices

The first step to using a device is declaring its existence. This is done in the machine configuration, like so:

MDRV_DEVICE_ADD( "tag", DEVICETYPE )

Here "tag" is a short, unique string that idenitifies the device, and DEVICETYPE is the name of the device in question (by convention, device names are in ALL CAPS). There can be multiple devices declared in a single machine configuration, and multiple instances of the same device (though each instance must have a unique tag).

Once you define a device this way, it is considered the "current" device for the MDRV_ macros that follow. It is often the case that there are configuration macros following the declaration of a device:

MDRV_DEVICE_ADD( "tag", DEVICETYPE )     // declare device
MDRV_DEVICE_CONFIG( my_config_struct )   // provide configuration data

In practice, however, you will rarely see MDRV_DEVICE_ADD macros in the wild. This is because it is convention for each device to define its own MDRV_ macro for declaring itself, along with any required configuration. Here is an example for the IDE controller:

MDRV_IDE_CONTROLLER_ADD("ide", ide_interrupt)
MDRV_IDE_BUS_MASTER_SPACE("main", PROGRAM)

The first macro expands to an MDRV_DEVICE_ADD of an IDE_CONTROLLER, followed by configuration macros to specify the ide_interrupt function. The second macro provides two additional optional pieces of configuration data for the device.

If you need to modify the configuration of an existing device, you can use:

MDRV_DEVICE_MODIFY( "tag", DEVICETYPE )

which makes a previously-declare device "current" again so that subsequent configuration macros apply to it. You can also remove a device:

MDRV_DEVICE_REMOVE( "tag", DEVICETYPE )

All this manipulation of devices (add/configure/remove) is done when the machine configuration is first created, and before the emulation begins. This means you can examine the list of devices and identify them very early on.

Locating Devices

Once a device has been declared, you will likely need to find it, in order to operate upon it. Since devices are created as part of the machine configuration, it makes sense that the list of devices is stored within the machine configuration structure itself:

struct _machine_config
{
    ...
    const device_config *   devicelist;          /* list head for devices */
};

Once you have the head of the list, you can either walk through the list directly to find your device, or take advantage of several functions that help you locate devices. For example, to find a particular device in a machine configuration (config), use:

const device_config *device;
device = device_list_find_by_tag(config->devicelist, "tag");

You can also enumerate through all the devices of a given type:

const device_config *device;
for (device = device_list_first(config->devicelist, DEVICETYPE);
     device != NULL;
     device = device_list_next(device, DEVICETYPE)
{
   ...
}

One thing to notice is that none of these basic location functions depends on the existing of a running emulation (i.e., no running_machine object). This is primarily to allow easy enumeration and identification of devices during early startup and for informational purposes.

However, since it is very common that a driver needs to find one or more of its devices during driver startup, there is a preferred shortcut:

const device_config *device;
device = devtag_get_device(machine, "tag");

There are several similar devtag_ functions declared in devintrf.h (broken link), which replace the list head parameter with a running_machine object for easier use when the system is running.

Device Configuration

struct _device_config
{
    /* device relationships (always valid) */
    device_config *            next;                    /* next device (of any type/class) */
    device_config *            owner;                    /* device that owns us, or NULL if nobody */
    device_config *            typenext;                /* next device of the same type */
    device_config *            classnext;                /* next device of the same class */

    /* device properties (always valid) */
    device_type                type;                    /* device type */
    device_class            devclass;                /* device class */

    /* device configuration (always valid) */
    UINT32                    clock;                    /* device clock */
    const void *            static_config;            /* static device configuration */
    void *                    inline_config;            /* inline device configuration */

    /* these fields are only valid once the device is attached to a machine */
    running_machine *        machine;                /* machine if device is live */

    /* these fields are only valid if the device is live */
    UINT8                    started;                /* TRUE if the start function has succeeded */
    void *                    token;                    /* token if device is live */
    UINT32                    tokenbytes;                /* size of the token data allocated */
    UINT8 *                    region;                    /* pointer to region with the device's tag, or NULL */
    UINT32                    regionbytes;            /* size of the region, in bytes */
    device_execute_func     execute;                /* quick pointer to execute callback */

    /* tag (always valid; at end because it is variable-length) */
    char                    tag[1];                    /* tag for this instance */
};