Re: [RFC PATCH 3/4] Documentation: espi: add subsystem overview and MAINTAINERS entry

"M, Krishnamoorthi" <[email protected]> Wed, 5 Aug 2026 00:44:39 +0530
Newsgroups org.ozlabs.lists.openbmc,dev.linux.lists.chrome-platform,org.kernel.vger.linux-doc,org.kernel.vger.linux-kernel,org.kernel.vger.linux-spi,org.ozlabs.lists.linux-aspeed
Message-ID <[email protected]>

On 8/4/2026 10:09 PM, Randy Dunlap wrote:
> 
> 
> On 8/4/26 4:52 AM, Krishnamoorthi M wrote:
>> Add a driver-api overview of the eSPI subsystem and a MAINTAINERS entry
>> covering the subsystem files.
>>
>> The document describes the architecture, how to write a controller driver
>> and a slave driver, the per-channel APIs (Peripheral, Virtual Wire, OOB,
>> Flash), the alert mechanism flow, and the event notification model. An
>> API Reference section renders kernel-doc from the exported symbols.
>>
>> Signed-off-by: Krishnamoorthi M <[email protected]>
>> ---
>>   Documentation/driver-api/espi.rst  | 213 +++++++++++++++++++++++++++++
>>   Documentation/driver-api/index.rst |   1 +
>>   MAINTAINERS                        |   8 ++
>>   3 files changed, 222 insertions(+)
>>   create mode 100644 Documentation/driver-api/espi.rst
>>
>> diff --git a/Documentation/driver-api/espi.rst b/Documentation/driver-api/espi.rst
>> new file mode 100644
>> index 000000000000..60a3187edb05
>> --- /dev/null
>> +++ b/Documentation/driver-api/espi.rst
>> @@ -0,0 +1,213 @@
>> +.. SPDX-License-Identifier: GPL-2.0-or-later
>> +
>> +===========================================
>> +eSPI (Enhanced Serial Peripheral Interface)
>> +===========================================
>> +
>> +Introduction
>> +============
>> +
>> +eSPI is a bus defined by Intel that replaces the legacy LPC bus. Unlike
>> +SPI it is a structured, capability-negotiated, message-oriented protocol
>> +with four logically independent channels (Peripheral, Virtual Wire, OOB,
>> +Flash) over a shared physical link, and asynchronous target-to-controller
>> +events, so it is modelled as its own bus type rather than an extension of
>> +the SPI subsystem.
>> +
>> +Architecture
>> +============
>> +
>> +* ``struct espi_controller`` - host controller, created with
>> +  espi_controller_alloc() and registered with espi_controller_register().
>> +  It is not itself a device on espi_bus_type.
>> +* ``struct espi_device`` - a target on the bus, matched to a
>> +  ``struct espi_driver`` via its modalias.
>> +* ``struct espi_controller_ops`` - the optional hardware-op table; the
>> +  channel API returns -EOPNOTSUPP for ops a controller does not provide.
>> +
>> +Writing a controller driver
>> +===========================
>> +
>> +A controller driver allocates and registers a controller from its
>> +``probe()`` function::
>> +
>> +    ctrl = espi_controller_alloc(&pdev->dev, sizeof(*priv));
>> +    if (IS_ERR(ctrl))
>> +        return PTR_ERR(ctrl);
>> +
>> +    priv = espi_controller_get_devdata(ctrl);
>> +    ctrl->ops = &my_espi_ops;
>> +    ctrl->max_targets = 1;
>> +
>> +    /* populate ctrl->caps from hardware capability registers */
>> +    ctrl->caps.supported_channels = ESPI_CHANNEL_ALL;
>> +    ctrl->caps.max_freq_mhz       = 33;
>> +    ctrl->caps.io_mode            = ESPI_IO_MODE_SINGLE;
>> +
>> +    ret = espi_controller_register(ctrl);
>> +    if (ret)
>> +        goto err_put;
>> +
>> +After registration the controller calls espi_new_device() for each
>> +target enumerated from firmware (ACPI or device tree)::
>> +
>> +    struct espi_board_info info = {
>> +        .type = "my-ec",
>> +        .cs   = 0,
>> +    };
>> +    edev = espi_new_device(ctrl, &info);
>> +
>> +On removal::
>> +
>> +    espi_remove_device(edev);
>> +    espi_controller_unregister(ctrl);
>> +    espi_controller_put(ctrl);
>> +
>> +Writing a slave driver
>> +======================
>> +
>> +A slave driver declares a device ID table and a ``struct espi_driver``::
>> +
>> +    static const struct espi_device_id my_ec_ids[] = {
>> +        { "my-ec", 0 },
>> +        { }
>> +    };
>> +    MODULE_DEVICE_TABLE(espi, my_ec_ids);
>> +
>> +    static int my_ec_probe(struct espi_device *edev)
>> +    {
>> +        /* register for hardware events */
>> +        nb->notifier_call = my_ec_event;
>> +        espi_register_notifier(edev->ctrl, nb);
>> +        return 0;
>> +    }
>> +
>> +    static void my_ec_remove(struct espi_device *edev)
>> +    {
>> +        espi_unregister_notifier(edev->ctrl, nb);
>> +    }
>> +
>> +    static struct espi_driver my_ec_driver = {
>> +        .driver   = { .name = "my-ec" },
>> +        .id_table = my_ec_ids,
>> +        .probe    = my_ec_probe,
>> +        .remove   = my_ec_remove,
>> +    };
>> +    module_espi_driver(my_ec_driver);
>> +
>> +Channel-independent commands
>> +============================
>> +
>> +espi_get_configuration(), espi_set_configuration(), espi_inband_reset()
>> +and espi_get_status(). GET_STATUS is optional: controllers whose hardware
>> +does not implement the wire command leave .get_status unset.
>> +
>> +Capability negotiation and channel management
>> +=============================================
>> +
>> +At boot the controller driver reads the target's capability registers via
>> +espi_get_configuration(), negotiates link parameters (I/O mode, clock
>> +frequency, CRC) via espi_set_configuration(), then enables each channel
>> +with espi_enable_channel(). espi_channel_is_enabled() may be called at
>> +any time to query the current state. Channels may be disabled individually
>> +with espi_disable_channel(), for example before an in-band reset.
>> +
>> +Channel APIs
>> +============
>> +
>> +Peripheral channel
>> +------------------
>> +
>> +Carries I/O and memory cycles between the host and target endpoints.
>> +
>> +* espi_periph_io_read() / espi_periph_io_write() — 16-bit I/O port
>> +  access; ``width`` is the access size in bytes (1, 2, or 4).
>> +* espi_periph_mem_read() / espi_periph_mem_write() — 32-bit memory
>> +  mapped access.
>> +
>> +Virtual Wire channel
>> +--------------------
>> +
>> +Carries logical signal state (power sequencing, SMI#, SCI#, IRQs) as
>> +indexed wire groups. Each group carries up to four wire values with
>> +individual valid bits.
>> +
>> +* espi_vwire_get() — read a wire group from the target.
>> +* espi_vwire_put() — send a PUT_VIRTUAL_WIRE command to the target.
>> +  Named after the eSPI PUT_VW wire command, not a reference-count
>> +  release.
>> +
>> +Wire changes from the target generate an ``ESPI_EVENT_VWIRE_CHANGED``
>> +event delivered through the notifier chain.
>> +
>> +OOB channel
>> +-----------
>> +
>> +Tunnels SMBus/I2C messages between the host and target out-of-band
>> +processor (BMC, EC). Messages are exchanged as opaque byte buffers with
>> +a tag field for matching requests to responses.
>> +
>> +* espi_oob_send() / espi_oob_recv()
>> +
>> +Incoming OOB messages generate an ``ESPI_EVENT_OOB_RECEIVED`` event.
>> +
>> +Flash Access channel
>> +--------------------
>> +
>> +Provides access to a SPI flash device attached to the target. The target
>> +acts as a proxy for flash read, write, and erase operations.
>> +
>> +* espi_flash_read() / espi_flash_write() / espi_flash_erase()
>> +
>> +Alert mechanism
>> +===============
>> +
>> +When the target has upstream data pending it asserts ``ALERT#``. The
>> +controller's hard-IRQ handler acknowledges the interrupt and defers
>> +processing to a threaded IRQ or workqueue. From that process context the
>> +controller driver calls espi_handle_alert(), which acquires the
>> +controller lock and dispatches to ``ops->handle_alert``. The hardware
>> +callback reads the target's status register (GET_STATUS), identifies the
>> +pending channel, and calls espi_notify_event() to deliver the appropriate
>> +``ESPI_EVENT_*`` to all registered slave driver notifiers::
>> +
>> +    ALERT# asserted by target
>> +          |
>> +          v
>> +    hard-IRQ handler (controller driver)
>> +          |
>> +          v
>> +    threaded IRQ / workqueue
>> +          |
>> +          v
>> +    espi_handle_alert(ctrl)          [espi-core.c]
>> +          |
>> +          v
>> +    ops->handle_alert(ctrl)          [controller driver]
>> +          | reads GET_STATUS, decodes channel
>> +          v
>> +    espi_notify_event(ctrl, &event)  [espi-slave.c]
>> +          |
>> +          v
>> +    slave driver notifier callback
>> +
>> +espi_handle_alert() must always be called from process context; it must
>> +never be called from a hard-IRQ handler.
>> +
>> +Events and concurrency
>> +======================
>> +
>> +Hardware events (Virtual Wire changes, OOB messages, Peripheral channel
>> +completions, channel state changes) are delivered through a per-controller
>> +blocking notifier chain (espi_register_notifier()/espi_notify_event()).
>> +Callbacks run in process context; controllers deliver events from a
>> +threaded IRQ or workqueue, never from hardirq and never while holding the
>> +controller lock.
>> +
>> +API Reference
>> +=============
>> +
>> +.. kernel-doc:: include/linux/espi/espi.h
>> +
> 
> Describe each of these:
> 
> WARNING: ../include/linux/espi/espi.h:176 struct member 'setup' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'cleanup' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'get_configuration' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'set_configuration' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'inband_reset' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'get_status' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'enable_channel' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'disable_channel' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'periph_io_read' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'periph_io_write' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'periph_mem_read' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'periph_mem_write' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'vwire_get' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'vwire_put' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'oob_send' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'oob_recv' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'flash_read' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'flash_write' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'flash_erase' not described in 'espi_controller_ops'
> WARNING: ../include/linux/espi/espi.h:176 struct member 'handle_alert' not described in 'espi_controller_ops'
> 
> 
> or don't use "/**" for that struct's comment header.

Noted. Will add descriptions for all struct espi_controller_ops members 
in v2.

Thanks,
Krishna
> 
>> +.. kernel-doc:: drivers/espi/espi-slave.c
>> +   :export:
> 
>