WDC(9)

πŸ“¦ version 11.0, in nixpkgs unstable

okay bestie, welcome to WDC(9) from the Kernel Developer's Manual β€” it's the wdc page, AKA the machine-independent IDE/ATAPI driver, and this is part 1 of 2 because the lore is long 😭.

🌸 NAME 🌸

wdc β€” machine-independent IDE/ATAPI driver πŸ’…

✨ SYNOPSIS ✨

#include <dev/ata/atavar.h>
#include <sys/dev/ic/wdcvar.h>

int
wdcprobe(struct channel_softc * chp);

void
wdcattach(struct channel_softc * chp);

πŸ’– DESCRIPTION πŸ’–

🧠 what wdc actually is

So the wdc driver is the machine-independent core-function girlie for driving IDE devices. IDE device-specific drivers β€” that's wd(4) or atapibus(4) β€” use the services that wdc provides. periodt.

The machine-dependent bus front-end gives info to wdc through the wdc_softc and channel_softc structures. The first one defines global controller properties, and the second contains per-channel information. wdc returns information about the attached devices in the ata_drive_datas structure.

🧱 the structs, bestie (copy these exact)

struct wdc_softc

struct wdc_softc { /* Per controller state */
        struct device sc_dev;
        int           cap;
#define WDC_CAPABILITY_DATA16 0x0001
#define WDC_CAPABILITY_DATA32 0x0002
#define WDC_CAPABILITY_MODE   0x0004
#define WDC_CAPABILITY_DMA    0x0008
#define WDC_CAPABILITY_UDMA   0x0010
#define WDC_CAPABILITY_HWLOCK 0x0020
#define WDC_CAPABILITY_ATA_NOSTREAM 0x0040
#define WDC_CAPABILITY_ATAPI_NOSTREAM 0x0080
#define WDC_CAPABILITY_NO_EXTRA_RESETS 0x0100
#define WDC_CAPABILITY_PREATA 0x0200
#define WDC_CAPABILITY_IRQACK 0x0400
#define WDC_CAPABILITY_SINGLE_DRIVE 0x0800
#define WDC_CAPABILITY_NOIRQ  0x1000
#define WDC_CAPABILITY_SELECT  0x2000
        uint8_t      pio_mode;
        uint8_t      dma_mode;
        int nchannels;
        struct channel_softc *channels;

        void            *dma_arg;
        int            (*dma_init)(void *, int, int, void *, size_t, int);
        void           (*dma_start)(void *, int, int, int);
        int            (*dma_finish)(void *, int, int, int);
#define WDC_DMA_READ 0x01
#define WDC_DMA_POLL 0x02

        int            (*claim_hw)(void *, int);
        void            (*free_hw)(void *);
};

struct channel_softc

struct channel_softc { /* Per channel data */
        int channel;
        struct wdc_softc *wdc;
        bus_space_tag_t       cmd_iot;
        bus_space_handle_t    cmd_ioh;
        bus_space_tag_t       ctl_iot;
        bus_space_handle_t    ctl_ioh;
        bus_space_tag_t       data32iot;
        bus_space_handle_t    data32ioh;
        int ch_flags;
#define WDCF_ACTIVE   0x01
#define WDCF_IRQ_WAIT 0x10
        uint8_t ch_status;
        uint8_t ch_error;
        struct ata_drive_datas ch_drive[2];
        struct channel_queue *ch_queue;
};

struct ata_drive_datas

struct ata_drive_datas {
    uint8_t drive;
    uint8_t drive_flags;
#define DRIVE_ATA   0x01
#define DRIVE_ATAPI 0x02
#define DRIVE (DRIVE_ATA|DRIVE_ATAPI)
#define DRIVE_CAP32 0x04
#define DRIVE_DMA   0x08
#define DRIVE_UDMA  0x10
#define DRIVE_MODE 0x20
    uint8_t PIO_mode;
    uint8_t DMA_mode;
    uint8_t UDMA_mode;
    uint8_t state;

    struct device *drv_softc;
    void* chnl_softc;
};

βœ… required wdc_softc fields

The bus front-end needs to fill in these elements of wdc_softc:

  • cap β€” supports one or more of the WDC_CAPABILITY flags.
  • nchannels β€” number of channels supported by this controller.
  • channels β€” array of struct channel_softc of size nchannels, properly initialised.

πŸŽ€ optional wdc_softc fields

These are optional, bestie:

  • pio_mode
  • dma_mode
  • dma_arg
  • dma_init
  • dma_start
  • dma_finish
  • claim_hw
  • free_hw

πŸ”₯ capability flags, explained

The WDC_CAPABILITY_DATA16 and WDC_CAPABILITY_DATA32 flags tell wdc whether the controller supports 16- or 32-bit I/O accesses on the data port. If both are set, a test will be done for each drive using the ATA or ATAPI IDENTIFY command, to automatically select the working mode. iconic.

The WDC_CAPABILITY_DMA and WDC_CAPABILITY_UDMA flags are set for controllers supporting the DMA and Ultra-DMA modes. The bus front-end needs to provide the dma_init(), dma_start() and dma_finish() functions.

  • dma_init() is called just before issuing a DMA command to the IDE device. The arguments are, respectively: dma_arg, the channel number, the drive number on this channel, the virtual address of the DMA buffer, the size of the transfer, and the WDC_DMA flags.
  • dma_start() is called just after issuing a DMA command to the IDE device. The arguments are, respectively: dma_arg, the channel number, the drive number on this channel, and the WDC_DMA flags.
  • dma_finish() is called once the transfer is complete. The arguments are, respectively: dma_arg, the channel number, the drive number on this channel, and the WDC_DMA flags.

WDC_DMA_READ indicates the direction of the data transfer, and WDC_DMA_POLL indicates if the transfer will use (or used) interrupts. no cap.

The WDC_CAPABILITY_MODE flag means that the bus front-end can program the PIO and DMA modes, so wdc needs to provide back the supported modes for each drive, and set the drives modes. The pio_mode and dma_mode needs to be set to the highest PIO and DMA mode supported. If WDC_CAPABILITY_UDMA is set, then dma_mode must be set to the highest Ultra-DMA mode supported. If WDC_CAPABILITY_MODE is not set, wdc will not attempt to change the current drive's settings, assuming the host's firmware has done it right.

The WDC_CAPABILITY_HWLOCK flag is set for controllers needing hardware locking before accessing the I/O ports. If this flag is set, the bus front-end needs to provide the claim_hw() and free_hw() functions.

  • claim_hw() will be called when the driver wants to access the controller ports. The second parameter is set to 1 when it is possible to sleep waiting for the lock, 0 otherwise. It should return 1 when access has been granted, 0 otherwise.
  • When access has not been granted and sleep is not allowed, the bus front-end shall call wdcrestart() with the first argument passed to claim_hw() as argument. That same argument is also the one passed to free_hw().
  • free_hw() is called once the transfer is complete, so that the lock can be released.

Accesses to the data port are done by using the bus_space stream functions, unless the WDC_CAPABILITY_ATA_NOSTREAM or WDC_CAPABILITY_ATAPI_NOSTREAM flags are set.

⚠️ this should not be used, unless the data bus is not wired properly (which seems common on big-endian systems), and byte-order needs to be preserved for compatibility with the host's firmware. Also note that the IDE bus is a little-endian bus, so the bus_space functions used for the bus_space tag passed in the channel_softc have to do the appropriate byte-swapping for big-endian systems.

WDC_CAPABILITY_NO_EXTRA_RESETS avoid the controller reset at the end of the disks probe. This reset is needed for some controllers, but causes problems with some others. fr fr.

WDC_CAPABILITY_NOIRQ tells the driver that this controller doesn't have its interrupt lines wired up usefully, so it should always use polled transfers.

πŸ“¦ required channel_softc fields

The bus front-end needs to fill in these elements of channel_softc:

  • channel β€” the channel number on the controller.
  • wdc β€” a pointer to the controller's wdc_softc.
  • cmd_iot, cmd_ioh β€” bus-space tag and handle for access to the command block registers (which includes the 16-bit data port).
  • ctl_iot, ctl_ioh β€” bus-space tag and handle for access to the control block registers.
  • ch_queue β€” a pointer to a struct channel_queue. This will hold the queues of outstanding commands for this controller.

πŸ¦‹ optional channel_softc fields

  • data32iot, data32ioh β€” bus-space tag and handle for 32-bit data accesses. Only needed if WDC_CAPABILITY_DATA32 is set in the controller's wdc_softc.

ch_queue can point to a common struct channel_queue if the controller doesn't support concurrent access to its different channels. If all channels are independent, it is recommended that each channel has its own ch_queue (for better performance). ✨

πŸš€ wdcprobe() and wdcattach()

The bus-specific front-end can use the wdcprobe() function, with a properly initialised struct channel_softc as argument (wdc can be set to NULL; this allows wdcprobe() to be easily used in bus front-end probe functions). This function will return an integer where bit 0 will be set if the master device has been found, and 1 if the slave device has been found.

The bus-specific attach function has to call wdcattach() for each channel, with a pointer to a properly initialised channel softc as argument. This will probe devices attached to the IDE channel and attach them. Once this function returns, the ch_drive array of the channel_softc will contain the drive's capabilities. This can be used to properly initialise the controller's mode, or disable a channel without drives.

πŸš— ata_drive_datas fields and drive_flags

The elements of interest in ata_drive_datas for a bus front-end are:

  • drive β€” the drive number.
  • drive_flags β€” flags indicating the drive capabilities. A null drive_flags indicate either that no drive is here, or that no driver was found for this device.
  • PIO_mode, DMA_mode, UDMA_mode β€” the highest supported modes for this drive compatible with the controller's capabilities. Needs to be reset to the mode to use by the drive, if known.
  • drv_softc β€” a pointer to the drive's softc. Can be used to print the drive's name.

drive_flags handles the following flags:

  • DRIVE_ATA, DRIVE_ATAPI β€” gives the drive type, if any. The shortcut DRIVE can be used to just test the presence/absence of a drive.
  • DRIVE_CAP32 β€” this drive works with 32-bit data I/O.
  • DRIVE_DMA β€” this drive supports DMA.
  • DRIVE_UDMA β€” this drive supports Ultra-DMA.
  • DRIVE_MODE β€” this drive properly reported its PIO and DMA mode.

🏁 after init

Once the controller has been initialised, it has to reset the DRIVE_DMA and DRIVE_UDMA, as well as the values of PIO_mode, DMA_mode and UDMA_mode if the modes to be used are not highest ones supported by the drive.

πŸ”₯ CODE REFERENCES πŸ”₯

The wdc core functions are implemented in sys/dev/ic/wdc.c. Low-level ATA and ATAPI support is provided by sys/dev/ata_wdc.c and sys/dev/scsipi/atapi_wdc.c respectively.

An example of a simple bus front-end can be found in sys/dev/isapnp/wdc_isapnp.c. A more complex one, with multiple channels and bus-master DMA support is sys/dev/pci/pciide.c. sys/arch/atari/dev/wdc_mb.c makes use of hardware locking, and also provides an example of bus-front end for a big-endian system, which needs byte-swapping bus_space functions. 🫢

✨ SEE ALSO ✨

okay bestie, you made it to the end, so here's your little reading list β€” the pages you're gonna wanna pull up right after this one πŸ“šπŸ’–

  • wdc(4) β€” the sibling page, definitely worth a look πŸ‘€
  • bus_space(9) β€” the other go-to reference, pull this one up too 🫢

and that's genuinely it, no hidden extras, no secret bonus references hiding at the bottom ~~(unlike some pages, we're not gonna jumpscare you)~~ 😌✨


πŸ’… quick vibe check before you close the tab: cross-references are your friends. if a page tells you to go read another page, go read the other page. that's literally the whole point. iykyk πŸ¦‹

🌸 and that's a wrap on WDC(9)!! you did it, you read the whole thing, i'm so proud of you fr fr πŸŽ€

tiny credits corner just so you know where this came from: it's from Debian, and it's dated April 18, 2010 β€” so yeah, this page is a genuine elder millennial, lowkey vintage, and honestly it's giving "iconic and still relevant" πŸ‘‘πŸ’œ

now go forth and go touch some docs, bestie β€” you've got this πŸ’–βœ¨

πŸ“¦ where this page comes from