#include <sys/param.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/sema.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/callout.h>
#include <sys/rman.h>
#include <sys/time.h>
#include <machine/bus.h>
#include <machine/resource.h>
#include <machine/atomic.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcireg.h>
#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,
int is_initial);
static void apple_bce_timestamp_stop(struct apple_bce_softc *sc);
static void
apple_bce_timestamp_init(struct apple_bce_softc *sc)
{
bus_read_4(sc->sc_bar4, BCE_REG_TIMESTAMP);
mb();
}
static void
apple_bce_timestamp_start(struct apple_bce_softc *sc, int is_initial)
{
bus_write_4(sc->sc_bar4, BCE_REG_TIMESTAMP + 8,
is_initial ? 0xFFFFFFFC : 0xFFFFFFFD);
bus_write_4(sc->sc_bar4, BCE_REG_TIMESTAMP, 0xFFFFFFFF);
mtx_lock_spin(&sc->sc_timestamp_lock);
sc->sc_timestamp_stopped = 0;
mtx_unlock_spin(&sc->sc_timestamp_lock);
callout_reset(&sc->sc_timestamp_co, hz * BCE_TIMESTAMP_MS / 1000,
apple_bce_timestamp_cb, sc);
}
static void
apple_bce_timestamp_stop(struct apple_bce_softc *sc)
{
mtx_lock_spin(&sc->sc_timestamp_lock);
sc->sc_timestamp_stopped = 1;
mtx_unlock_spin(&sc->sc_timestamp_lock);
callout_drain(&sc->sc_timestamp_co);
bus_write_4(sc->sc_bar4, BCE_REG_TIMESTAMP + 8, 0xFFFFFFFE);
bus_write_4(sc->sc_bar4, BCE_REG_TIMESTAMP, 0xFFFFFFFF);
}
static void
apple_bce_timestamp_cb(void *arg)
{
struct apple_bce_softc *sc = arg;
struct bintime bt;
uint64_t ns;
bus_read_4(sc->sc_bar4, BCE_REG_TIMESTAMP + 8);
mb();
binuptime(&bt);
ns = (uint64_t)bt.sec * 1000000000ULL +
(((uint64_t)(bt.frac >> 32) * 1000000000ULL) >> 32);
bus_write_4(sc->sc_bar4, BCE_REG_TIMESTAMP + 8, (uint32_t)ns);
bus_write_4(sc->sc_bar4, BCE_REG_TIMESTAMP, (uint32_t)(ns >> 32));
mtx_lock_spin(&sc->sc_timestamp_lock);
if (!sc->sc_timestamp_stopped)
callout_schedule(&sc->sc_timestamp_co,
hz * BCE_TIMESTAMP_MS / 1000);
mtx_unlock_spin(&sc->sc_timestamp_lock);
}
static void
apple_bce_mbox_intr(void *arg)
{
struct apple_bce_softc *sc = arg;
bce_mailbox_handle_interrupt(&sc->sc_mbox);
}
static void
apple_bce_dma_intr(void *arg)
{
struct apple_bce_softc *sc = arg;
int i;
mtx_lock(&sc->sc_queues_lock);
for (i = 0; i < BCE_MAX_CQ_COUNT; i++) {
if (sc->sc_cq_list[i] != NULL)
bce_handle_cq_completions(sc, sc->sc_cq_list[i]);
}
mtx_unlock(&sc->sc_queues_lock);
}
static int
apple_bce_enable_pci0_busmaster(device_t dev)
{
device_t pci0;
device_t bus;
bus = device_get_parent(dev);
if (bus == NULL)
return (ENXIO);
pci0 = pci_find_dbsf(pci_get_domain(dev), pci_get_bus(dev),
pci_get_slot(dev), 0);
if (pci0 == NULL) {
device_printf(dev, "cannot find PCI function 0\n");
return (ENXIO);
}
pci_enable_busmaster(pci0);
device_printf(dev, "enabled bus master on function 0 (%s)\n",
device_get_nameunit(pci0));
return (0);
}
static int
apple_bce_fw_handshake(struct apple_bce_softc *sc)
{
uint64_t reply;
int error;
error = bce_mailbox_send(&sc->sc_mbox,
BCE_MB_MSG(BCE_MB_SET_FW_PROTOCOL_VER, BCE_FW_PROTOCOL_VER),
&reply, BCE_MBOX_TIMEOUT_MS);
if (error != 0) {
device_printf(sc->sc_dev, "firmware handshake timeout\n");
return (error);
}
if (BCE_MB_TYPE(reply) != BCE_MB_SET_FW_PROTOCOL_VER ||
BCE_MB_VALUE(reply) != BCE_FW_PROTOCOL_VER) {
device_printf(sc->sc_dev,
"firmware version mismatch: got type=%u val=0x%llx\n",
BCE_MB_TYPE(reply),
(unsigned long long)BCE_MB_VALUE(reply));
return (ENODEV);
}
device_printf(sc->sc_dev, "firmware handshake OK (protocol 0x%x)\n",
BCE_FW_PROTOCOL_VER);
return (0);
}
static void
bce_reg_dma_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error)
{
struct { bus_addr_t addr; int error; } *cb = arg;
cb->error = error;
if (error == 0)
cb->addr = segs[0].ds_addr;
}
static int
apple_bce_register_cmd_queue(struct apple_bce_softc *sc,
struct bce_queue_memcfg *cfg, int is_sq)
{
bus_dma_tag_t tag;
bus_dmamap_t map;
bus_addr_t paddr;
struct bce_queue_memcfg *dma_cfg;
struct {
bus_addr_t addr;
int error;
} cb;
uint64_t reply;
int error, cmd_type;
error = bus_dma_tag_create(sc->sc_dma_tag,
8, 0, BUS_SPACE_MAXADDR, BUS_SPACE_MAXADDR,
NULL, NULL, sizeof(*cfg), 1, sizeof(*cfg),
BUS_DMA_WAITOK, NULL, NULL, &tag);
if (error != 0)
return (error);
error = bus_dmamem_alloc(tag, (void **)&dma_cfg,
BUS_DMA_WAITOK | BUS_DMA_ZERO | BUS_DMA_COHERENT, &map);
if (error != 0) {
bus_dma_tag_destroy(tag);
return (error);
}
memcpy(dma_cfg, cfg, sizeof(*cfg));
cb.error = 0;
error = bus_dmamap_load(tag, map, dma_cfg, sizeof(*cfg),
bce_reg_dma_cb, &cb, BUS_DMA_WAITOK);
if (error != 0 || cb.error != 0) {
bus_dmamem_free(tag, dma_cfg, map);
bus_dma_tag_destroy(tag);
return (error != 0 ? error : cb.error);
}
paddr = cb.addr;
bus_dmamap_sync(tag, map, BUS_DMASYNC_PREWRITE);
cmd_type = is_sq ? BCE_MB_REGISTER_CMD_SQ : BCE_MB_REGISTER_CMD_CQ;
error = bce_mailbox_send(&sc->sc_mbox,
BCE_MB_MSG(cmd_type, paddr), &reply, BCE_MBOX_TIMEOUT_MS);
bus_dmamap_sync(tag, map, BUS_DMASYNC_POSTWRITE);
bus_dmamap_unload(tag, map);
bus_dmamem_free(tag, dma_cfg, map);
bus_dma_tag_destroy(tag);
if (error != 0)
return (error);
if (BCE_MB_TYPE(reply) != BCE_MB_REGISTER_QUEUE_REPLY) {
device_printf(sc->sc_dev,
"unexpected queue registration reply: type=%u\n",
BCE_MB_TYPE(reply));
return (EINVAL);
}
return (0);
}
static int
apple_bce_setup_cmd_queues(struct apple_bce_softc *sc)
{
struct bce_queue_memcfg cfg;
struct bce_queue_sq *sq;
int error;
sc->sc_cmd_cq = bce_alloc_cq(sc, 0, 32);
if (sc->sc_cmd_cq == NULL) {
device_printf(sc->sc_dev, "failed to allocate command CQ\n");
return (ENOMEM);
}
bce_get_cq_memcfg(sc->sc_cmd_cq, &cfg);
error = apple_bce_register_cmd_queue(sc, &cfg, 0);
if (error != 0) {
device_printf(sc->sc_dev, "failed to register command CQ\n");
goto fail_cq;
}
mtx_lock(&sc->sc_queues_lock);
sc->sc_queues[0] = sc->sc_cmd_cq;
sc->sc_cq_list[0] = sc->sc_cmd_cq;
mtx_unlock(&sc->sc_queues_lock);
sq = bce_alloc_sq(sc, 1, BCE_CMD_SIZE, 32, NULL, NULL);
if (sq == NULL) {
device_printf(sc->sc_dev, "failed to allocate command SQ\n");
goto fail_cq;
}
sc->sc_cmd_cmdq = bce_alloc_cmdq(sc, sq);
if (sc->sc_cmd_cmdq == NULL) {
bce_free_sq(sc, sq);
goto fail_cq;
}
bce_get_sq_memcfg(sq, sc->sc_cmd_cq, &cfg);
error = apple_bce_register_cmd_queue(sc, &cfg, 1);
if (error != 0) {
device_printf(sc->sc_dev, "failed to register command SQ\n");
goto fail_sq;
}
mtx_lock(&sc->sc_queues_lock);
sc->sc_queues[1] = sq;
sc->sc_int_sq_list[0] = sq;
mtx_unlock(&sc->sc_queues_lock);
device_printf(sc->sc_dev, "command queues created (CQ=0, SQ=1)\n");
return (0);
fail_sq:
bce_free_cmdq(sc->sc_cmd_cmdq);
sc->sc_cmd_cmdq = NULL;
bce_free_sq(sc, sq);
fail_cq:
mtx_lock(&sc->sc_queues_lock);
sc->sc_queues[0] = NULL;
sc->sc_cq_list[0] = NULL;
mtx_unlock(&sc->sc_queues_lock);
bce_free_cq(sc, sc->sc_cmd_cq);
sc->sc_cmd_cq = NULL;
return (error != 0 ? error : ENOMEM);
}
static int
apple_bce_probe(device_t dev)
{
if (pci_get_vendor(dev) != BCE_PCI_VENDOR_APPLE ||
pci_get_device(dev) != BCE_PCI_DEVICE_T2)
return (ENXIO);
if (pci_get_function(dev) != 1)
return (ENXIO);
device_set_desc(dev, "Apple T2 Buffer Copy Engine");
return (BUS_PROBE_DEFAULT);
}
static int
apple_bce_attach(device_t dev)
{
struct apple_bce_softc *sc = device_get_softc(dev);
int error;
sc->sc_dev = dev;
pci_enable_busmaster(dev);
mtx_init(&sc->sc_queues_lock, "bce_queues", NULL, MTX_DEF);
mtx_init(&sc->sc_timestamp_lock, "bce_ts", NULL, MTX_SPIN);
callout_init(&sc->sc_timestamp_co, 1);
sc->sc_bar2_rid = PCIR_BAR(2);
sc->sc_bar2 = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
&sc->sc_bar2_rid, RF_ACTIVE);
if (sc->sc_bar2 == NULL) {
device_printf(dev, "cannot map BAR2 (DMA)\n");
error = ENXIO;
goto fail;
}
sc->sc_bar4_rid = PCIR_BAR(4);
sc->sc_bar4 = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
&sc->sc_bar4_rid, RF_ACTIVE);
if (sc->sc_bar4 == NULL) {
device_printf(dev, "cannot map BAR4 (mailbox)\n");
error = ENXIO;
goto fail;
}
sc->sc_msi_count = 8;
error = pci_alloc_msi(dev, &sc->sc_msi_count);
if (error != 0 || sc->sc_msi_count < 8) {
device_printf(dev, "cannot allocate MSI vectors: "
"error=%d got=%d\n", error, sc->sc_msi_count);
if (error == 0 && sc->sc_msi_count > 0)
pci_release_msi(dev);
sc->sc_msi_count = 0;
error = ENXIO;
goto fail;
}
bce_mailbox_init(&sc->sc_mbox, sc->sc_bar4);
sc->sc_irq_rid_mbox = 1;
sc->sc_irq_mbox = bus_alloc_resource_any(dev, SYS_RES_IRQ,
&sc->sc_irq_rid_mbox, RF_ACTIVE);
if (sc->sc_irq_mbox == NULL) {
device_printf(dev, "cannot allocate mailbox IRQ\n");
error = ENXIO;
goto fail;
}
error = bus_setup_intr(dev, sc->sc_irq_mbox,
INTR_TYPE_MISC | INTR_MPSAFE,
NULL, apple_bce_mbox_intr, sc, &sc->sc_irq_mbox_cookie);
if (error != 0) {
device_printf(dev, "cannot setup mailbox IRQ\n");
goto fail;
}
sc->sc_irq_rid_dma = 5;
sc->sc_irq_dma = bus_alloc_resource_any(dev, SYS_RES_IRQ,
&sc->sc_irq_rid_dma, RF_ACTIVE);
if (sc->sc_irq_dma == NULL) {
device_printf(dev, "cannot allocate DMA IRQ\n");
error = ENXIO;
goto fail;
}
error = bus_setup_intr(dev, sc->sc_irq_dma,
INTR_TYPE_MISC | INTR_MPSAFE,
NULL, apple_bce_dma_intr, sc, &sc->sc_irq_dma_cookie);
if (error != 0) {
device_printf(dev, "cannot setup DMA IRQ\n");
goto fail;
}
error = bus_dma_tag_create(bus_get_dma_tag(dev),
1, 0,
(1ULL << 37) - 1,
BUS_SPACE_MAXADDR,
NULL, NULL,
BUS_SPACE_MAXSIZE,
BUS_SPACE_UNRESTRICTED,
BUS_SPACE_MAXSIZE,
0,
NULL, NULL,
&sc->sc_dma_tag);
if (error != 0) {
device_printf(dev, "cannot create DMA tag\n");
goto fail;
}
apple_bce_enable_pci0_busmaster(dev);
apple_bce_timestamp_init(sc);
apple_bce_timestamp_start(sc, 1);
error = apple_bce_fw_handshake(sc);
if (error != 0)
goto fail;
error = apple_bce_setup_cmd_queues(sc);
if (error != 0)
goto fail;
device_printf(dev, "Apple T2 BCE initialized\n");
error = bce_vhci_attach(sc);
if (error != 0)
goto fail;
return (0);
fail:
apple_bce_detach(dev);
return (error);
}
static int
apple_bce_detach(device_t dev)
{
struct apple_bce_softc *sc = device_get_softc(dev);
bce_vhci_detach(sc);
if (sc->sc_bar4 != NULL && mtx_initialized(&sc->sc_timestamp_lock))
apple_bce_timestamp_stop(sc);
else
callout_drain(&sc->sc_timestamp_co);
if (sc->sc_irq_dma_cookie != NULL) {
bus_teardown_intr(dev, sc->sc_irq_dma, sc->sc_irq_dma_cookie);
sc->sc_irq_dma_cookie = NULL;
}
if (sc->sc_irq_mbox_cookie != NULL) {
bus_teardown_intr(dev, sc->sc_irq_mbox,
sc->sc_irq_mbox_cookie);
sc->sc_irq_mbox_cookie = NULL;
}
if (sc->sc_cmd_cmdq != NULL) {
struct bce_queue_sq *cmd_sq;
cmd_sq = sc->sc_cmd_cmdq->sq;
bce_free_cmdq(sc->sc_cmd_cmdq);
sc->sc_cmd_cmdq = NULL;
bce_free_sq(sc, cmd_sq);
}
if (sc->sc_cmd_cq != NULL) {
bce_free_cq(sc, sc->sc_cmd_cq);
sc->sc_cmd_cq = NULL;
}
if (mtx_initialized(&sc->sc_queues_lock)) {
mtx_lock(&sc->sc_queues_lock);
memset(sc->sc_queues, 0, sizeof(sc->sc_queues));
memset(sc->sc_cq_list, 0, sizeof(sc->sc_cq_list));
memset(sc->sc_int_sq_list, 0, sizeof(sc->sc_int_sq_list));
mtx_unlock(&sc->sc_queues_lock);
}
bce_mailbox_destroy(&sc->sc_mbox);
if (sc->sc_dma_tag != NULL) {
bus_dma_tag_destroy(sc->sc_dma_tag);
sc->sc_dma_tag = NULL;
}
if (sc->sc_irq_dma != NULL) {
bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid_dma,
sc->sc_irq_dma);
sc->sc_irq_dma = NULL;
}
if (sc->sc_irq_mbox != NULL) {
bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid_mbox,
sc->sc_irq_mbox);
sc->sc_irq_mbox = NULL;
}
if (sc->sc_msi_count > 0) {
pci_release_msi(dev);
sc->sc_msi_count = 0;
}
if (sc->sc_bar4 != NULL) {
bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_bar4_rid,
sc->sc_bar4);
sc->sc_bar4 = NULL;
}
if (sc->sc_bar2 != NULL) {
bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_bar2_rid,
sc->sc_bar2);
sc->sc_bar2 = NULL;
}
if (mtx_initialized(&sc->sc_timestamp_lock))
mtx_destroy(&sc->sc_timestamp_lock);
if (mtx_initialized(&sc->sc_queues_lock))
mtx_destroy(&sc->sc_queues_lock);
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
};
static driver_t apple_bce_driver = {
"apple_bce",
apple_bce_methods,
sizeof(struct apple_bce_softc)
};
DRIVER_MODULE(apple_bce, pci, apple_bce_driver, NULL, NULL);
MODULE_DEPEND(apple_bce, pci, 1, 1, 1);
static const struct {
uint16_t vendor;
uint16_t device;
} apple_bce_pnp[] = {
{ BCE_PCI_VENDOR_APPLE, BCE_PCI_DEVICE_T2 },
};
MODULE_PNP_INFO("U16:vendor;U16:device", pci, apple_bce, apple_bce_pnp,
nitems(apple_bce_pnp));