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
This commit is contained in:
Abdelkader Boudih
2026-06-14 13:54:28 -07:00
committed by Adrian Chadd
parent 6fd2ad9aa3
commit 9f90536c74
8 changed files with 5170 additions and 1 deletions
+1
View File
@@ -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
+87
View File
@@ -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
};
+2 -1
View File
@@ -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 */
+5
View File
@@ -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 */
File diff suppressed because it is too large Load Diff
+251
View File
@@ -0,0 +1,251 @@
/*-
* Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*
* Apple T2 BCE Virtual USB Host Controller Interface (VHCI).
* Translates USB operations into BCE firmware messages over DMA queues.
*/
#ifndef _APPLE_BCE_VHCI_H_
#define _APPLE_BCE_VHCI_H_
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/sx.h>
#include <sys/sema.h>
#include <sys/taskqueue.h>
#include "apple_bce.h"
/* Forward declaration -- full USB headers included only in .c file */
struct usb_bus;
struct usb_device;
struct usb_xfer;
/*
* VHCI limits.
*/
#define BCE_VHCI_MAX_PORTS 16
#define BCE_VHCI_MAX_DEVICES 32
#define BCE_VHCI_MAX_ENDPOINTS 32
/* Queue element counts */
#define BCE_VHCI_MSG_QUEUE_EL 32
#define BCE_VHCI_EVT_QUEUE_EL 256
#define BCE_VHCI_EVT_PENDING 32
#define BCE_VHCI_TQ_EL 32 /* Transfer queue elements */
#define BCE_VHCI_XFER_BUFSZ 4096 /* DMA buffer per transfer queue */
/*
* VHCI message format (16 bytes, matches firmware protocol).
*/
struct bce_vhci_message {
uint16_t cmd;
uint16_t status;
uint32_t param1;
uint64_t param2;
} __packed;
/*
* VHCI command IDs.
*/
enum bce_vhci_cmd_id {
/* Controller commands */
BCE_VHCI_CMD_CONTROLLER_ENABLE = 0x0001,
BCE_VHCI_CMD_CONTROLLER_DISABLE = 0x0002,
BCE_VHCI_CMD_CONTROLLER_START = 0x0003,
BCE_VHCI_CMD_CONTROLLER_PAUSE = 0x0004,
/* Port commands */
BCE_VHCI_CMD_PORT_POWER_ON = 0x0010,
BCE_VHCI_CMD_PORT_POWER_OFF = 0x0011,
BCE_VHCI_CMD_PORT_RESUME = 0x0012,
BCE_VHCI_CMD_PORT_SUSPEND = 0x0013,
BCE_VHCI_CMD_PORT_RESET = 0x0014,
BCE_VHCI_CMD_PORT_DISABLE = 0x0015,
BCE_VHCI_CMD_PORT_STATUS = 0x0016,
BCE_VHCI_CMD_PORT_STATUS_CHANGE = 0x0018,
/* Device commands */
BCE_VHCI_CMD_DEVICE_CREATE = 0x0030,
BCE_VHCI_CMD_DEVICE_DESTROY = 0x0031,
/* Endpoint commands */
BCE_VHCI_CMD_ENDPOINT_CREATE = 0x0040,
BCE_VHCI_CMD_ENDPOINT_DESTROY = 0x0041,
BCE_VHCI_CMD_ENDPOINT_SET_STATE = 0x0042,
BCE_VHCI_CMD_ENDPOINT_REQ_STATE = 0x0043,
BCE_VHCI_CMD_ENDPOINT_RESET = 0x0044,
/* Transfer commands */
BCE_VHCI_CMD_TRANSFER_REQUEST = 0x1000,
BCE_VHCI_CMD_CTRL_TRANSFER_STATUS = 0x1005,
/* Reply flag -- firmware replies have cmd | 0x8000 */
BCE_VHCI_CMD_REPLY_FLAG = 0x8000,
/* Cancel flag -- timeout sends cmd | 0x4000 */
BCE_VHCI_CMD_CANCEL_FLAG = 0x4000,
};
/*
* VHCI message status codes.
*/
enum bce_vhci_msg_status {
BCE_VHCI_SUCCESS = 1,
BCE_VHCI_ERROR = 2,
BCE_VHCI_PIPE_STALL = 3,
BCE_VHCI_ABORT = 4,
BCE_VHCI_BAD_ARGUMENT = 5,
BCE_VHCI_OVERRUN = 6,
BCE_VHCI_INTERNAL_ERROR = 7,
BCE_VHCI_NO_POWER = 8,
BCE_VHCI_UNSUPPORTED = 9,
};
/*
* Endpoint states.
*/
enum bce_vhci_endpoint_state {
BCE_VHCI_ENDP_ACTIVE = 0,
BCE_VHCI_ENDP_PAUSED = 1,
BCE_VHCI_ENDP_STALLED = 2,
};
/*
* Control transfer state machine.
*/
enum bce_vhci_ctrl_state {
BCE_VHCI_CTRL_IDLE = 0,
BCE_VHCI_CTRL_SETUP = 1, /* Awaiting setup XFER_REQ */
BCE_VHCI_CTRL_DATA = 2, /* Awaiting data XFER_REQ */
BCE_VHCI_CTRL_STATUS = 3, /* Awaiting CTRL_XFER_STATUS */
};
/*
* Pause sources (bitmask).
*/
#define BCE_VHCI_PAUSE_INTERNAL 0x01
#define BCE_VHCI_PAUSE_FIRMWARE 0x02
#define BCE_VHCI_PAUSE_SUSPEND 0x04
#define BCE_VHCI_PAUSE_SHUTDOWN 0x08
/*
* Port status bit mapping (firmware -> USB).
* Firmware uses its own bit encoding; we translate in roothub_exec.
*/
#define BCE_VHCI_PORT_CONNECTED 0x0004
#define BCE_VHCI_PORT_ENABLED 0x0010
#define BCE_VHCI_PORT_SUSPENDED 0x0060
#define BCE_VHCI_PORT_OVERCURRENT 0x0002
#define BCE_VHCI_PORT_RESET 0x0008
#define BCE_VHCI_PORT_C_CONNECTION 0x40000
/*
* VHCI message queue (host -> device).
*/
struct bce_vhci_msg_queue {
struct bce_queue_cq *cq;
struct bce_queue_sq *sq;
bus_dma_tag_t dma_tag;
bus_dmamap_t dma_map;
bus_addr_t dma_addr;
struct bce_vhci_message *data;
uint32_t el_count;
};
/*
* VHCI event queue (device -> host).
* Single contiguous DMA buffer for all receive slots.
*/
struct bce_vhci_evt_queue {
struct bce_queue_sq *sq;
bus_dma_tag_t dma_tag;
bus_dmamap_t dma_map;
bus_addr_t dma_addr;
struct bce_vhci_message *data;
uint32_t el_count;
void *userdata;
};
/*
* VHCI command queue (synchronous command execution).
*/
struct bce_vhci_cmd_queue {
struct bce_vhci_msg_queue *msg;
struct sx exec_lock; /* Serialize callers */
struct mtx lock;
struct sema completion;
struct bce_vhci_message response;
volatile int pending;
uint16_t expected_cmd; /* Filter late replies */
};
/*
* VHCI transfer queue (per endpoint).
*
* Each endpoint gets a CQ + IN SQ + OUT SQ triplet registered with
* firmware as named DMA queues. The DMA buffer is used to shuttle
* USB payloads between the USB stack's frame buffers and firmware.
*/
struct bce_vhci_transfer_queue {
struct bce_vhci_softc *vhci;
uint8_t dev_addr; /* firmware device id */
uint8_t endp_addr; /* USB endpoint address */
struct mtx lock; /* Protects SQ submission */
struct bce_queue_cq *cq;
struct bce_queue_sq *sq_in; /* Device -> host */
struct bce_queue_sq *sq_out; /* Host -> device */
struct usb_xfer *active_xfer;
uint32_t paused_by;
int active; /* Queues created with FW */
int stalled;
int dma_inflight; /* DMA pending */
int create_pending; /* Deferred ep create */
struct usb_endpoint_descriptor *create_edesc;
struct usb_xfer *create_xfer; /* Deferred xfer */
/* DMA buffer for data transfer */
bus_dma_tag_t dma_tag;
bus_dmamap_t dma_map;
bus_addr_t dma_addr;
void *dma_buf;
/* Control transfer state machine */
enum bce_vhci_ctrl_state ctrl_state;
uint8_t ctrl_dir; /* UE_DIR_IN or UE_DIR_OUT */
uint32_t ctrl_data_len; /* Expected data phase length */
uint32_t ctrl_actual; /* Actual bytes transferred */
int ctrl_data_done; /* IN DMA completion seen */
int ctrl_status_pending; /* Deferred STATUS msg */
struct bce_vhci_message ctrl_status_msg; /* Saved STATUS for defer */
/* Queued transfer waiting for active_xfer to finish */
struct usb_xfer *pending_xfer;
/* Deferred firmware event (TRANSFER_REQUEST arrives before xfer) */
int evt_pending;
struct bce_vhci_message evt_saved;
};
/*
* VHCI per-device state.
*/
struct bce_vhci_device {
int allocated; /* Device created with FW */
uint8_t fw_dev_id; /* Firmware device ID */
uint8_t port; /* Port number */
struct bce_vhci_transfer_queue tq[BCE_VHCI_MAX_ENDPOINTS];
};
/* VHCI softc is defined in apple_bce_vhci.c (depends on USB headers) */
struct bce_vhci_softc;
/* VHCI driver interface */
int bce_vhci_attach(struct apple_bce_softc *sc);
int bce_vhci_detach(struct apple_bce_softc *sc);
#endif /* _APPLE_BCE_VHCI_H_ */
+1
View File
@@ -135,6 +135,7 @@ DRIVER_MODULE(usbus, octusb, usb_driver, 0, 0);
/* Dual Mode Drivers */
DRIVER_MODULE(usbus, dwcotg, usb_driver, 0, 0);
DRIVER_MODULE(usbus, bce_vhci, usb_driver, 0, 0);
/*------------------------------------------------------------------------*
* usb_probe
+2
View File
@@ -2,6 +2,8 @@
KMOD= apple_bce
SRCS= apple_bce.c apple_bce_mailbox.c apple_bce_queue.c
SRCS+= apple_bce_vhci.c
SRCS+= bus_if.h device_if.h pci_if.h
SRCS+= opt_usb.h opt_bus.h
.include <bsd.kmod.mk>