Date: Sun, 14 Jun 2026 20:57:24 +0000 From: Adrian Chadd <adrian@FreeBSD.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Cc: Abdelkader Boudih <freebsd@seuros.com> Subject: git: 9f90536c74b8 - main - apple_bce/vhci: add T2 virtual USB host controller Message-ID: <6a2f15b4.383ee.79ad3166@gitrepo.freebsd.org>
index | next in thread | raw e-mail
The branch main has been updated by adrian: URL: https://cgit.FreeBSD.org/src/commit/?id=9f90536c74b8172fc67cd977e5451f37a12462d5 commit 9f90536c74b8172fc67cd977e5451f37a12462d5 Author: Abdelkader Boudih <freebsd@seuros.com> AuthorDate: 2026-06-14 20:54:28 +0000 Commit: Adrian Chadd <adrian@FreeBSD.org> CommitDate: 2026-06-14 20:56:53 +0000 apple_bce/vhci: add T2 virtual USB host controller Implements a VHCI driver on top of the BCE transport: - Virtual USB bus registration via usb_controller - Port discovery and device enumeration - Control, interrupt, and bulk endpoint support - Firmware event handling with taskqueue - Suspend/resume via BCE mailbox Provides keyboard, trackpad, and Touch Bar access on T2 Macs. Tested-on: MacBookPro16,2 (A2251), Mac mini 8,1 (A1993) Reviewed by: adrian Differential Revision: https://reviews.freebsd.org/D57089 --- sys/conf/files.amd64 | 1 + sys/dev/apple_bce/apple_bce.c | 87 + sys/dev/apple_bce/apple_bce.h | 3 +- sys/dev/apple_bce/apple_bce_mailbox.c | 5 + sys/dev/apple_bce/apple_bce_vhci.c | 4821 +++++++++++++++++++++++++++++++ sys/dev/apple_bce/apple_bce_vhci.h | 251 ++ sys/dev/usb/controller/usb_controller.c | 1 + sys/modules/apple_bce/Makefile | 2 + 8 files changed, 5170 insertions(+), 1 deletion(-) diff --git a/sys/conf/files.amd64 b/sys/conf/files.amd64 index 4d37266d1973..0b4a22eebc78 100644 --- a/sys/conf/files.amd64 +++ b/sys/conf/files.amd64 @@ -115,6 +115,7 @@ dev/amdgpio/amdgpio.c optional amdgpio dev/apple_bce/apple_bce.c optional apple_bce pci dev/apple_bce/apple_bce_mailbox.c optional apple_bce pci dev/apple_bce/apple_bce_queue.c optional apple_bce pci +dev/apple_bce/apple_bce_vhci.c optional apple_bce pci dev/asmc/asmc.c optional asmc isa dev/asmc/asmcmmio.c optional asmc isa dev/axgbe/if_axgbe_pci.c optional axp diff --git a/sys/dev/apple_bce/apple_bce.c b/sys/dev/apple_bce/apple_bce.c index 8700eddbc0fe..2b0e6928ffcd 100644 --- a/sys/dev/apple_bce/apple_bce.c +++ b/sys/dev/apple_bce/apple_bce.c @@ -32,10 +32,13 @@ #include "apple_bce.h" #include "apple_bce_mailbox.h" #include "apple_bce_queue.h" +#include "apple_bce_vhci.h" static int apple_bce_probe(device_t dev); static int apple_bce_attach(device_t dev); static int apple_bce_detach(device_t dev); +static int apple_bce_suspend(device_t dev); +static int apple_bce_resume(device_t dev); static void apple_bce_timestamp_cb(void *arg); static void apple_bce_timestamp_init(struct apple_bce_softc *sc); static void apple_bce_timestamp_start(struct apple_bce_softc *sc, @@ -516,6 +519,12 @@ apple_bce_attach(device_t dev) goto fail; device_printf(dev, "Apple T2 BCE initialized\n"); + + /* Create VHCI child for virtual USB */ + error = bce_vhci_attach(sc); + if (error != 0) + goto fail; + return (0); fail: @@ -531,6 +540,9 @@ apple_bce_detach(device_t dev) { struct apple_bce_softc *sc = device_get_softc(dev); + /* 0. Detach VHCI child first (before destroying parent resources) */ + bce_vhci_detach(sc); + /* 1. Stop timestamp */ if (sc->sc_bar4 != NULL && mtx_initialized(&sc->sc_timestamp_lock)) apple_bce_timestamp_stop(sc); @@ -616,10 +628,85 @@ apple_bce_detach(device_t dev) return (0); } +static int +apple_bce_suspend(device_t dev) +{ + struct apple_bce_softc *sc = device_get_softc(dev); + int error, restore_error; + + apple_bce_timestamp_stop(sc); + + error = bce_vhci_detach(sc); + if (error != 0) { + device_printf(dev, "failed to detach VHCI for suspend: %d\n", + error); + apple_bce_timestamp_start(sc, 0); + return (error); + } + + error = bce_mailbox_send(&sc->sc_mbox, + BCE_MB_MSG(BCE_MB_SLEEP_NO_STATE, 0), NULL, + BCE_MBOX_TIMEOUT_MS); + if (error != 0) { + device_printf(dev, + "failed to send SLEEP_NO_STATE mailbox command: %d\n", + error); + restore_error = bce_vhci_attach(sc); + if (restore_error != 0) { + device_printf(dev, + "failed to reattach VHCI after suspend error: %d\n", + restore_error); + } + apple_bce_timestamp_start(sc, 0); + return (error); + } + + return (0); +} + +static int +apple_bce_resume(device_t dev) +{ + struct apple_bce_softc *sc = device_get_softc(dev); + uint64_t reply; + int error; + + error = bce_mailbox_send(&sc->sc_mbox, + BCE_MB_MSG(BCE_MB_RESTORE_NO_STATE, 0), &reply, + BCE_MBOX_TIMEOUT_MS); + if (error != 0) { + device_printf(dev, + "failed to send RESTORE_NO_STATE mailbox command: %d\n", + error); + return (error); + } + + if (BCE_MB_TYPE(reply) != BCE_MB_RESTORE_NO_STATE) { + device_printf(dev, + "unexpected RESTORE_NO_STATE reply: type=%u val=0x%llx\n", + BCE_MB_TYPE(reply), + (unsigned long long)BCE_MB_VALUE(reply)); + return (EINVAL); + } + + error = bce_vhci_attach(sc); + if (error != 0) { + device_printf(dev, "failed to reattach VHCI after resume: %d\n", + error); + apple_bce_timestamp_start(sc, 0); + return (error); + } + + apple_bce_timestamp_start(sc, 0); + return (0); +} + static device_method_t apple_bce_methods[] = { DEVMETHOD(device_probe, apple_bce_probe), DEVMETHOD(device_attach, apple_bce_attach), DEVMETHOD(device_detach, apple_bce_detach), + DEVMETHOD(device_suspend, apple_bce_suspend), + DEVMETHOD(device_resume, apple_bce_resume), DEVMETHOD_END }; diff --git a/sys/dev/apple_bce/apple_bce.h b/sys/dev/apple_bce/apple_bce.h index 7c2198c7232b..0e1999e48d5b 100644 --- a/sys/dev/apple_bce/apple_bce.h +++ b/sys/dev/apple_bce/apple_bce.h @@ -27,7 +27,7 @@ #define BCE_PCI_DEVICE_T2 0x1801 #define BCE_MAX_QUEUE_COUNT 0x100 -#define BCE_MAX_CQ_COUNT 16 /* Max completion queues tracked */ +#define BCE_MAX_CQ_COUNT 64 /* Max completion queues tracked */ #define BCE_QUEUE_USER_MIN 2 #define BCE_QUEUE_USER_MAX (BCE_MAX_QUEUE_COUNT - 1) #define BCE_CMD_SIZE 0x40 @@ -286,6 +286,7 @@ struct apple_bce_softc { struct mtx sc_queues_lock; struct bce_queue_cq *sc_cq_list[BCE_MAX_CQ_COUNT]; struct bce_queue_sq *sc_int_sq_list[BCE_MAX_QUEUE_COUNT]; + device_t sc_vhci_dev; }; /* Inline helpers */ diff --git a/sys/dev/apple_bce/apple_bce_mailbox.c b/sys/dev/apple_bce/apple_bce_mailbox.c index abdb809b5c54..c19a01b7269c 100644 --- a/sys/dev/apple_bce/apple_bce_mailbox.c +++ b/sys/dev/apple_bce/apple_bce_mailbox.c @@ -57,6 +57,11 @@ bce_mailbox_send(struct bce_mailbox *mb, uint64_t msg, uint64_t *recv, bus_write_4(mb->reg, BCE_REG_MBOX_OUT + 8, 0); bus_write_4(mb->reg, BCE_REG_MBOX_OUT + 12, 0); + if (recv == NULL) { + atomic_store_int(&mb->status, 0); + return (0); + } + /* Wait for interrupt-driven reply */ if (sema_timedwait(&mb->mb_cmpl, hz * timeout_ms / 1000) != 0) { /* Timeout -- reset to idle */ diff --git a/sys/dev/apple_bce/apple_bce_vhci.c b/sys/dev/apple_bce/apple_bce_vhci.c new file mode 100644 index 000000000000..b8dbc9638b36 --- /dev/null +++ b/sys/dev/apple_bce/apple_bce_vhci.c @@ -0,0 +1,4821 @@ +/*- + * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com> + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Apple T2 BCE Virtual USB Host Controller Interface (VHCI). + */ + +#ifdef USB_GLOBAL_INCLUDE_FILE +#include USB_GLOBAL_INCLUDE_FILE +#else +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> +#endif + +#include <sys/sema.h> +#include <sys/taskqueue.h> +#include <sys/endian.h> +#include <machine/bus.h> +#include <machine/atomic.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usb_core.h> +#include <dev/usb/usb_busdma.h> +#include <dev/usb/usb_process.h> +#include <dev/usb/usb_transfer.h> +#include <dev/usb/usb_device.h> +#include <dev/usb/usb_hub.h> +#include <dev/usb/usb_util.h> +#include <dev/usb/usb_controller.h> +#include <dev/usb/usb_bus.h> + +#include "apple_bce.h" +#include "apple_bce_queue.h" +#include "apple_bce_vhci.h" + +/* + * VHCI softc, defined here because it depends on USB headers. + */ +struct bce_vhci_softc { + struct usb_bus sc_bus; /* Must be first */ + struct usb_device *sc_devices[BCE_VHCI_MAX_DEVICES]; + struct apple_bce_softc *sc_bce; + device_t sc_dev; + + /* Controller state */ + uint32_t sc_port_mask; + uint8_t sc_port_count; + int sc_started; + + /* Port state */ + uint32_t sc_port_status[BCE_VHCI_MAX_PORTS]; + uint32_t sc_port_change[BCE_VHCI_MAX_PORTS]; + uint8_t sc_port_power[BCE_VHCI_MAX_PORTS]; + + /* Hub scratch buffer (for descriptor/status responses) */ + uint8_t sc_hub_idata[32]; + + /* Message queues (host -> device) */ + struct bce_vhci_msg_queue msg_commands; + struct bce_vhci_msg_queue msg_system; + struct bce_vhci_msg_queue msg_isochronous; + struct bce_vhci_msg_queue msg_interrupt; + struct bce_vhci_msg_queue msg_asynchronous; + + /* Event queues (device -> host), share a single CQ */ + struct bce_queue_cq *ev_cq; + struct bce_vhci_evt_queue ev_commands; + struct bce_vhci_evt_queue ev_system; + struct bce_vhci_evt_queue ev_isochronous; + struct bce_vhci_evt_queue ev_interrupt; + struct bce_vhci_evt_queue ev_asynchronous; + + /* Command execution (synchronous, wraps msg_commands) */ + struct bce_vhci_cmd_queue cmd; + + /* Queue ID bitmap (256 bits = BCE_MAX_QUEUE_COUNT) */ + uint32_t sc_qid_bitmap[8]; + + /* Per-device state (indexed by firmware device ID) */ + struct bce_vhci_device sc_devs[BCE_VHCI_MAX_DEVICES]; + uint8_t sc_port_to_dev[BCE_VHCI_MAX_PORTS]; + + /* Deferred firmware event processing (from ev_commands) */ + struct task sc_fwevt_task; + volatile int sc_detaching; /* Teardown guard */ + + /* + * Firmware event mailbox: ISR copies events here, task processes. + * Protected by sc_fwevt_lock. Ring of BCE_VHCI_EVT_PENDING entries. + */ + struct mtx sc_fwevt_lock; +#define BCE_VHCI_FWEVT_RING (BCE_VHCI_EVT_PENDING + 1) + struct { + struct bce_vhci_message msg; + int needs_reply; + } sc_fwevt_ring[BCE_VHCI_FWEVT_RING]; + uint32_t sc_fwevt_prod; + uint32_t sc_fwevt_cons; + + /* Spinlock for msg_asynchronous writes (ISR + taskqueue context) */ + struct mtx sc_async_lock; + + /* Deferred endpoint reset (cannot sleep in pipe_start) */ + struct task sc_reset_task; + + /* Deferred endpoint create (cannot sleep in pipe_start) */ + struct task sc_create_task; + + /* Deferred port status change (ISR cannot call cmd_execute) */ + struct task sc_port_chg_task; + volatile uint32_t sc_port_chg_mask; +}; + +/* Command timeout (ticks) */ +#define BCE_VHCI_CMD_TIMEOUT_SHORT (hz * 2) +#define BCE_VHCI_CMD_TIMEOUT_LONG (hz * 30) + +static usb_handle_req_t bce_vhci_roothub_exec; +static void bce_vhci_endpoint_init(struct usb_device *udev, + struct usb_endpoint_descriptor *edesc, struct usb_endpoint *ep); +static void bce_vhci_xfer_setup(struct usb_setup_params *parm); +static void bce_vhci_xfer_unsetup(struct usb_xfer *xfer); +static void bce_vhci_get_dma_delay(struct usb_device *udev, uint32_t *pus); + +static void bce_vhci_pipe_open(struct usb_xfer *xfer); +static void bce_vhci_pipe_close(struct usb_xfer *xfer); +static void bce_vhci_pipe_enter(struct usb_xfer *xfer); +static void bce_vhci_pipe_start(struct usb_xfer *xfer); + +static int bce_vhci_probe(device_t dev); +static int bce_vhci_attach_dev(device_t dev); +static int bce_vhci_detach_dev(device_t dev); + +static int bce_vhci_alloc_qid(struct bce_vhci_softc *vhci); +static void bce_vhci_free_qid(struct bce_vhci_softc *vhci, int qid); +static int bce_vhci_create_queues(struct bce_vhci_softc *vhci); +static void bce_vhci_destroy_queues(struct bce_vhci_softc *vhci); +static int bce_vhci_start_controller(struct bce_vhci_softc *vhci); +static void bce_vhci_msg_queue_completion(struct bce_queue_sq *sq); +static void bce_vhci_ev_cmd_completion(struct bce_queue_sq *sq); +static void bce_vhci_ev_system_completion(struct bce_queue_sq *sq); +static void bce_vhci_ev_generic_completion(struct bce_queue_sq *sq); +static void bce_vhci_cmd_deliver_completion(struct bce_vhci_softc *vhci, + struct bce_vhci_message *msg); +static void bce_vhci_handle_port_status_change(struct bce_vhci_softc *vhci, + struct bce_vhci_message *msg); +static void bce_vhci_evt_queue_submit_pending(struct bce_vhci_softc *vhci, + struct bce_vhci_evt_queue *eq, uint32_t count); + +static int bce_vhci_device_create(struct bce_vhci_softc *vhci, uint8_t port); +static void bce_vhci_device_destroy(struct bce_vhci_softc *vhci, uint8_t port); +static int bce_vhci_endpoint_create(struct bce_vhci_softc *vhci, + struct bce_vhci_device *dev, uint8_t ep_addr, + struct usb_endpoint_descriptor *edesc); +static void bce_vhci_endpoint_destroy(struct bce_vhci_softc *vhci, + struct bce_vhci_device *dev, uint8_t ep_addr); +static void bce_vhci_handle_transfer_request(struct bce_vhci_softc *vhci, + struct bce_vhci_message *msg); +static void bce_vhci_complete_ctrl_locked(struct bce_vhci_softc *vhci, + struct bce_vhci_transfer_queue *tq, struct bce_vhci_message *msg); +static void bce_vhci_handle_ctrl_status(struct bce_vhci_softc *vhci, + struct bce_vhci_message *msg); +static uint16_t bce_vhci_handle_endpoint_req_state(struct bce_vhci_softc *vhci, + struct bce_vhci_message *msg); +static uint16_t bce_vhci_handle_endpoint_set_state(struct bce_vhci_softc *vhci, + struct bce_vhci_message *msg); +static void bce_vhci_fwevt_task(void *arg, int pending); +static void bce_vhci_send_fw_event_reply(struct bce_vhci_softc *vhci, + struct bce_vhci_message *req, uint16_t status); +static void bce_vhci_tq_completion(struct bce_queue_sq *sq); +static void bce_vhci_reset_task(void *arg, int pending); +static void bce_vhci_create_task(void *arg, int pending); +static void bce_vhci_port_chg_task(void *arg, int pending); +static int bce_vhci_cmd_execute(struct bce_vhci_softc *vhci, + struct bce_vhci_message *req, struct bce_vhci_message *reply, + int timeout_ticks); + +/* + * Convert USB endpoint address to tq[] index. + * ep0 (0x00) maps to index 0. For other endpoints, IN and OUT get + * separate slots: OUT 0x01 -> 1, IN 0x81 -> 2, OUT 0x02 -> 3, etc. + * Maximum index is 30 (ep 0x8F), fits in BCE_VHCI_MAX_ENDPOINTS (32). + */ +static inline uint8_t +bce_vhci_ep_index(uint8_t ep_addr) +{ + uint8_t num; + + num = ep_addr & 0x0F; + if (num == 0) + return (0); + return (num * 2 - ((ep_addr & 0x80) ? 0 : 1)); +} + +static const struct usb_bus_methods bce_vhci_bus_methods = { + .roothub_exec = bce_vhci_roothub_exec, + .endpoint_init = bce_vhci_endpoint_init, + .xfer_setup = bce_vhci_xfer_setup, + .xfer_unsetup = bce_vhci_xfer_unsetup, + .get_dma_delay = bce_vhci_get_dma_delay, +}; + +/* + * Generic pipe methods (all transfer types for now). + */ +static const struct usb_pipe_methods bce_vhci_pipe_methods = { + .open = bce_vhci_pipe_open, + .close = bce_vhci_pipe_close, + .enter = bce_vhci_pipe_enter, + .start = bce_vhci_pipe_start, +}; + +/* + * Device methods. + */ +static device_method_t bce_vhci_methods[] = { + DEVMETHOD(device_probe, bce_vhci_probe), + DEVMETHOD(device_attach, bce_vhci_attach_dev), + DEVMETHOD(device_detach, bce_vhci_detach_dev), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + /* Bus interface for usbus child */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD_END +}; + +static driver_t bce_vhci_driver = { + .name = "bce_vhci", + .methods = bce_vhci_methods, + .size = sizeof(struct bce_vhci_softc), +}; + +DRIVER_MODULE(bce_vhci, apple_bce, bce_vhci_driver, 0, 0); +MODULE_DEPEND(bce_vhci, usb, 1, 1, 1); + +/* + * Hub descriptor (USB 2.0 hub with per-port power switching) + */ + +/* Hub descriptor built dynamically in roothub_exec (port count varies) */ + +/* Device descriptor for the root hub */ +static const struct usb_device_descriptor bce_vhci_devd = { + .bLength = sizeof(struct usb_device_descriptor), + .bDescriptorType = UDESC_DEVICE, + .bcdUSB = { 0x00, 0x02 }, /* USB 2.0 */ + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_HSHUBSTT, + .bMaxPacketSize = 64, + .idVendor = { 0x6b, 0x10 }, /* Apple 0x106b */ + .idProduct = { 0x01, 0x18 }, /* T2 BCE 0x1801 */ + .bcdDevice = { 0x00, 0x01 }, /* 1.00 */ + .iManufacturer = 1, + .iProduct = 2, + .bNumConfigurations = 1, +}; + +static const struct usb_device_qualifier bce_vhci_odevd = { + .bLength = sizeof(struct usb_device_qualifier), + .bDescriptorType = UDESC_DEVICE_QUALIFIER, + .bcdUSB = { 0x00, 0x02 }, + .bDeviceClass = UDCLASS_HUB, + .bDeviceSubClass = UDSUBCLASS_HUB, + .bDeviceProtocol = UDPROTO_HSHUBSTT, + .bMaxPacketSize0 = 0, + .bNumConfigurations = 0, +}; + +/* Configuration descriptor + interface + endpoint */ +/* 9 + 9 + 7 = 25 bytes */ +static const uint8_t bce_vhci_confd[] = { + /* Configuration descriptor */ + 0x09, 0x02, /* bLength, bDescriptorType */ + 0x19, 0x00, /* wTotalLength = 25 */ + 0x01, 0x01, 0x00, 0xC0, 0x00, /* nIntf, cfgVal, iCfg */ + /* Interface descriptor */ + 0x09, 0x04, /* bLength, bDescriptorType */ + 0x00, 0x00, 0x01, 0x09, 0x00, 0x01, 0x00, + /* Endpoint descriptor (interrupt IN ep1) */ + 0x07, 0x05, /* bLength, bDescriptorType */ + 0x81, 0x03, 0x08, 0x00, 0xFF, /* addr, attr, maxPkt, interval */ +}; + +struct bce_vhci_dma_cb_arg { + bus_addr_t addr; + int error; +}; + +static void +bce_vhci_dma_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + struct bce_vhci_dma_cb_arg *cb = arg; + + cb->error = error; + if (error == 0) + cb->addr = segs[0].ds_addr; +} + +/* + * Allocate a CQ + SQ pair and DMA message buffer for a host->device queue. + * Register it with firmware under the given name. + */ +static int +bce_vhci_msg_queue_create(struct bce_vhci_softc *vhci, + struct bce_vhci_msg_queue *mq, const char *name, int cq_qid, int sq_qid, + bce_sq_completion_fn compl_fn, void *compl_arg) +{ + struct apple_bce_softc *sc = vhci->sc_bce; + struct bce_vhci_dma_cb_arg cb; + struct bce_queue_memcfg cfg; + uint32_t el_count = BCE_VHCI_MSG_QUEUE_EL; + uint32_t status; + int error, i; + + memset(mq, 0, sizeof(*mq)); + mq->el_count = el_count; + + /* Allocate CQ */ + mq->cq = bce_alloc_cq(sc, cq_qid, el_count); + if (mq->cq == NULL) + return (ENOMEM); + + /* Register CQ with firmware via command path */ + bce_get_cq_memcfg(mq->cq, &cfg); + /* CQ interrupt vector = 4 (DMA MSI) */ + cfg.vector_or_cq = 4; + status = bce_cmd_register_queue(sc->sc_cmd_cmdq, sc, &cfg, NULL, 0); + if (status != 0) { + device_printf(vhci->sc_dev, + "failed to register CQ %d for %s: %u\n", + cq_qid, name, status); + error = EIO; + goto fail_cq; + } + + /* Register CQ in parent's dispatch tables */ + mtx_lock(&sc->sc_queues_lock); + sc->sc_queues[cq_qid] = mq->cq; + for (i = 0; i < BCE_MAX_CQ_COUNT; i++) { + if (sc->sc_cq_list[i] == NULL) { + sc->sc_cq_list[i] = mq->cq; + break; + } + } + if (i == BCE_MAX_CQ_COUNT) { + sc->sc_queues[cq_qid] = NULL; + mtx_unlock(&sc->sc_queues_lock); + device_printf(vhci->sc_dev, + "CQ list full for %s\n", name); + error = ENOSPC; + goto fail_cq_reg; + } + mtx_unlock(&sc->sc_queues_lock); + + /* Allocate SQ (element size = bce_qe_submission = 32 bytes) */ + mq->sq = bce_alloc_sq(sc, sq_qid, + sizeof(struct bce_qe_submission), el_count, + compl_fn, compl_arg); + if (mq->sq == NULL) { + error = ENOMEM; + goto fail_cq_reg; + } + + /* Register SQ with firmware under the given name */ + bce_get_sq_memcfg(mq->sq, mq->cq, &cfg); + status = bce_cmd_register_queue(sc->sc_cmd_cmdq, sc, &cfg, name, 1); + if (status != 0) { + device_printf(vhci->sc_dev, + "failed to register SQ %d (%s): %u\n", + sq_qid, name, status); + error = EIO; + goto fail_sq; + } + + /* Register SQ in parent's dispatch tables */ + mtx_lock(&sc->sc_queues_lock); + sc->sc_queues[sq_qid] = mq->sq; + sc->sc_int_sq_list[sq_qid] = mq->sq; + mtx_unlock(&sc->sc_queues_lock); + + /* Allocate DMA-coherent message buffer */ + error = bus_dma_tag_create(sc->sc_dma_tag, + 4, 0, + BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, + NULL, NULL, + el_count * sizeof(struct bce_vhci_message), 1, + el_count * sizeof(struct bce_vhci_message), + BUS_DMA_WAITOK, + NULL, NULL, + &mq->dma_tag); + if (error != 0) + goto fail_sq_reg; + + error = bus_dmamem_alloc(mq->dma_tag, (void **)&mq->data, + BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, + &mq->dma_map); + if (error != 0) + goto fail_dma_tag; + + error = bus_dmamap_load(mq->dma_tag, mq->dma_map, mq->data, + el_count * sizeof(struct bce_vhci_message), + bce_vhci_dma_cb, &cb, BUS_DMA_WAITOK); + if (error != 0 || cb.error != 0) { + error = error != 0 ? error : cb.error; + goto fail_dma_mem; + } + mq->dma_addr = cb.addr; + + return (0); + +fail_dma_mem: + bus_dmamem_free(mq->dma_tag, mq->data, mq->dma_map); +fail_dma_tag: + bus_dma_tag_destroy(mq->dma_tag); +fail_sq_reg: + bce_cmd_unregister_queue(sc->sc_cmd_cmdq, sc, sq_qid); + mtx_lock(&sc->sc_queues_lock); + sc->sc_queues[sq_qid] = NULL; + sc->sc_int_sq_list[sq_qid] = NULL; + mtx_unlock(&sc->sc_queues_lock); +fail_sq: + bce_free_sq(sc, mq->sq); + mq->sq = NULL; +fail_cq_reg: + bce_cmd_unregister_queue(sc->sc_cmd_cmdq, sc, cq_qid); + mtx_lock(&sc->sc_queues_lock); + sc->sc_queues[cq_qid] = NULL; + for (i = 0; i < BCE_MAX_CQ_COUNT; i++) { + if (sc->sc_cq_list[i] == mq->cq) { + sc->sc_cq_list[i] = NULL; + break; + } + } + mtx_unlock(&sc->sc_queues_lock); +fail_cq: + bce_free_cq(sc, mq->cq); + mq->cq = NULL; + return (error); +} + +static void +bce_vhci_msg_queue_destroy(struct bce_vhci_softc *vhci, + struct bce_vhci_msg_queue *mq) +{ + struct apple_bce_softc *sc = vhci->sc_bce; + int i; + + if (mq->cq == NULL) + return; + + /* + * Unregister and free SQ before releasing the DMA buffer it + * references + */ + if (mq->sq != NULL) { + bce_cmd_unregister_queue(sc->sc_cmd_cmdq, sc, mq->sq->qid); + mtx_lock(&sc->sc_queues_lock); + sc->sc_queues[mq->sq->qid] = NULL; + sc->sc_int_sq_list[mq->sq->qid] = NULL; + mtx_unlock(&sc->sc_queues_lock); + bce_free_sq(sc, mq->sq); + mq->sq = NULL; + } + + /* Free DMA message buffer */ + if (mq->data != NULL) { + bus_dmamap_unload(mq->dma_tag, mq->dma_map); + bus_dmamem_free(mq->dma_tag, mq->data, mq->dma_map); + bus_dma_tag_destroy(mq->dma_tag); + mq->data = NULL; + } + + /* Unregister and free CQ */ + bce_cmd_unregister_queue(sc->sc_cmd_cmdq, sc, mq->cq->qid); + mtx_lock(&sc->sc_queues_lock); + sc->sc_queues[mq->cq->qid] = NULL; + for (i = 0; i < BCE_MAX_CQ_COUNT; i++) { + if (sc->sc_cq_list[i] == mq->cq) { + sc->sc_cq_list[i] = NULL; + break; + } + } + mtx_unlock(&sc->sc_queues_lock); + bce_free_cq(sc, mq->cq); + mq->cq = NULL; +} + +/* + * Write a message to a host->device queue. + * Caller must have reserved a submission slot. + */ +static void +bce_vhci_msg_queue_write(struct bce_vhci_softc *vhci, + struct bce_vhci_msg_queue *mq, struct bce_vhci_message *msg) +{ + struct bce_qe_submission *s; + uint32_t sidx; + + sidx = mq->sq->tail; + s = bce_next_submission(mq->sq); + + /* Copy message into DMA buffer slot and sync for device access */ + mq->data[sidx] = *msg; + bus_dmamap_sync(mq->dma_tag, mq->dma_map, BUS_DMASYNC_PREWRITE); + + /* Fill SQ entry pointing to the DMA buffer slot */ + s->length = sizeof(struct bce_vhci_message); + s->addr = mq->dma_addr + + sidx * sizeof(struct bce_vhci_message); + s->segl_addr = 0; + s->segl_length = 0; + + bce_submit_to_device(vhci->sc_bce, mq->sq); +} + +/* + * Message queue completion: consume completions and free slots. + */ +static void +bce_vhci_msg_queue_completion(struct bce_queue_sq *sq) +{ + struct bce_vhci_msg_queue *mq = sq->userdata; + + while (sq->completion_cidx != sq->completion_tail) { + sq->completion_cidx = + (sq->completion_cidx + 1) % sq->el_count; + bce_notify_submission_complete(sq); + } + bus_dmamap_sync(mq->dma_tag, mq->dma_map, BUS_DMASYNC_POSTWRITE); +} + +/* + * Allocate an SQ (paired with the shared ev_cq) and DMA buffer for a + * device->host event queue. Register with firmware and pre-submit + * receive buffers. + */ +static int +bce_vhci_evt_queue_create(struct bce_vhci_softc *vhci, + struct bce_vhci_evt_queue *eq, const char *name, int sq_qid, + bce_sq_completion_fn compl_fn) +{ + struct apple_bce_softc *sc = vhci->sc_bce; + struct bce_vhci_dma_cb_arg cb; + struct bce_queue_memcfg cfg; + uint32_t el_count = BCE_VHCI_EVT_QUEUE_EL; + uint32_t status; + int error; + + memset(eq, 0, sizeof(*eq)); + eq->el_count = el_count; + eq->userdata = vhci; + + /* Allocate SQ (shared CQ = vhci->ev_cq) */ + eq->sq = bce_alloc_sq(sc, sq_qid, + sizeof(struct bce_qe_submission), el_count, + compl_fn, eq); + if (eq->sq == NULL) + return (ENOMEM); + + /* Register SQ with firmware (direction = from device = 0) */ + bce_get_sq_memcfg(eq->sq, vhci->ev_cq, &cfg); + status = bce_cmd_register_queue(sc->sc_cmd_cmdq, sc, &cfg, name, 0); + if (status != 0) { + device_printf(vhci->sc_dev, + "failed to register event SQ %d (%s): %u\n", + sq_qid, name, status); + error = EIO; + goto fail_sq; + } + + /* Register SQ in dispatch tables */ + mtx_lock(&sc->sc_queues_lock); + sc->sc_queues[sq_qid] = eq->sq; + sc->sc_int_sq_list[sq_qid] = eq->sq; + mtx_unlock(&sc->sc_queues_lock); + + /* Allocate DMA-coherent receive buffer */ + error = bus_dma_tag_create(sc->sc_dma_tag, + 4, 0, + BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR, + NULL, NULL, + el_count * sizeof(struct bce_vhci_message), 1, + el_count * sizeof(struct bce_vhci_message), + BUS_DMA_WAITOK, + NULL, NULL, + &eq->dma_tag); + if (error != 0) + goto fail_sq_reg; + + error = bus_dmamem_alloc(eq->dma_tag, (void **)&eq->data, + BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, + &eq->dma_map); + if (error != 0) + goto fail_dma_tag; + + error = bus_dmamap_load(eq->dma_tag, eq->dma_map, eq->data, + el_count * sizeof(struct bce_vhci_message), + bce_vhci_dma_cb, &cb, BUS_DMA_WAITOK); + if (error != 0 || cb.error != 0) { + error = error != 0 ? error : cb.error; + goto fail_dma_mem; + } + eq->dma_addr = cb.addr; + + /* Pre-submit receive buffers */ + bce_vhci_evt_queue_submit_pending(vhci, eq, BCE_VHCI_EVT_PENDING); + + return (0); + +fail_dma_mem: + bus_dmamem_free(eq->dma_tag, eq->data, eq->dma_map); +fail_dma_tag: + bus_dma_tag_destroy(eq->dma_tag); +fail_sq_reg: + bce_cmd_unregister_queue(sc->sc_cmd_cmdq, sc, sq_qid); + mtx_lock(&sc->sc_queues_lock); + sc->sc_queues[sq_qid] = NULL; + sc->sc_int_sq_list[sq_qid] = NULL; + mtx_unlock(&sc->sc_queues_lock); +fail_sq: + bce_free_sq(sc, eq->sq); + eq->sq = NULL; + return (error); +} + +static void +bce_vhci_evt_queue_destroy(struct bce_vhci_softc *vhci, + struct bce_vhci_evt_queue *eq) +{ + struct apple_bce_softc *sc = vhci->sc_bce; + + if (eq->sq == NULL) + return; + + /* Unregister SQ from dispatch tables FIRST to stop IRQ callbacks */ + bce_cmd_flush_queue(sc->sc_cmd_cmdq, sc, eq->sq->qid); + bce_cmd_unregister_queue(sc->sc_cmd_cmdq, sc, eq->sq->qid); + mtx_lock(&sc->sc_queues_lock); + sc->sc_queues[eq->sq->qid] = NULL; + sc->sc_int_sq_list[eq->sq->qid] = NULL; + mtx_unlock(&sc->sc_queues_lock); + + /* Now safe to free DMA buffer; no IRQ can reference it */ + if (eq->data != NULL) { + bus_dmamap_unload(eq->dma_tag, eq->dma_map); + bus_dmamem_free(eq->dma_tag, eq->data, eq->dma_map); + bus_dma_tag_destroy(eq->dma_tag); + eq->data = NULL; + } + bce_free_sq(sc, eq->sq); + eq->sq = NULL; +} + +/* + * Submit empty receive buffers to an event queue so firmware can + * write messages into them. + */ +static void +bce_vhci_evt_queue_submit_pending(struct bce_vhci_softc *vhci, + struct bce_vhci_evt_queue *eq, uint32_t count) +{ + struct bce_qe_submission *s; + uint32_t idx; + + bus_dmamap_sync(eq->dma_tag, eq->dma_map, BUS_DMASYNC_PREREAD); + + while (count-- > 0) { + if (bce_reserve_submission(eq->sq) != 0) { + device_printf(vhci->sc_dev, + "cannot reserve event submission\n"); + break; + } + idx = eq->sq->tail; + s = bce_next_submission(eq->sq); + s->length = sizeof(struct bce_vhci_message); + s->addr = eq->dma_addr + + idx * sizeof(struct bce_vhci_message); + s->segl_addr = 0; + s->segl_length = 0; + } + bce_submit_to_device(vhci->sc_bce, eq->sq); +} + +/* + * Enqueue a firmware event into sc_fwevt_ring for deferred processing. + * Called from ISR context; returns 0 on success, -1 if ring is full. + */ +static int +bce_vhci_fwevt_enqueue(struct bce_vhci_softc *vhci, + struct bce_vhci_message *msg, int needs_reply) +{ + uint32_t next_prod; + + mtx_lock_spin(&vhci->sc_fwevt_lock); + next_prod = (vhci->sc_fwevt_prod + 1) % BCE_VHCI_FWEVT_RING; + if (next_prod == vhci->sc_fwevt_cons) { + mtx_unlock_spin(&vhci->sc_fwevt_lock); + device_printf(vhci->sc_dev, + "fwevt ring full, dropping 0x%04x\n", msg->cmd); + return (-1); + } + vhci->sc_fwevt_ring[vhci->sc_fwevt_prod].msg = *msg; + vhci->sc_fwevt_ring[vhci->sc_fwevt_prod].needs_reply = needs_reply; + vhci->sc_fwevt_prod = next_prod; + mtx_unlock_spin(&vhci->sc_fwevt_lock); + + if (vhci->sc_detaching == 0) + taskqueue_enqueue(taskqueue_thread, &vhci->sc_fwevt_task); + return (0); +} + +/* + * Generic event queue completion: read messages and resubmit buffers. + * Used for system, isochronous, interrupt, and asynchronous event queues. + */ +static void +bce_vhci_ev_generic_completion(struct bce_queue_sq *sq) +{ + struct bce_vhci_evt_queue *eq = sq->userdata; + struct bce_vhci_softc *vhci = eq->userdata; + struct bce_vhci_message *msg; + uint32_t cnt = 0; + + bus_dmamap_sync(eq->dma_tag, eq->dma_map, BUS_DMASYNC_POSTREAD); + + while (sq->completion_cidx != sq->completion_tail) { + struct bce_sq_completion_data *cd; + + cd = &sq->completion_data[sq->completion_cidx]; + if (cd->status == BCE_COMP_ABORTED) { + sq->completion_cidx = + (sq->completion_cidx + 1) % sq->el_count; + bce_notify_submission_complete(sq); + cnt++; + continue; + } + + msg = &eq->data[sq->head]; + /* + * Route events to appropriate handlers. + * Strip 0x4000 flag; firmware uses it as a + * variant marker. + */ + if (msg->cmd & BCE_VHCI_CMD_REPLY_FLAG) + bce_vhci_cmd_deliver_completion(vhci, msg); + else { + uint16_t base_cmd = msg->cmd & *** 4335 LINES SKIPPED ***home | help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a2f15b4.383ee.79ad3166>
