#include "opt_acpi.h"
#include "opt_thunderbolt.h"
#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/lock.h>
#include <sys/param.h>
#include <sys/endian.h>
#include <machine/bus.h>
#include <machine/resource.h>
#include <machine/stdarg.h>
#include <sys/rman.h>
#include <machine/pci_cfgreg.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcib_private.h>
#include <dev/pci/pci_private.h>
#include <contrib/dev/acpica/include/acpi.h>
#include <contrib/dev/acpica/include/accommon.h>
#include <dev/acpica/acpivar.h>
#include <dev/acpica/acpi_pcibvar.h>
#include <machine/md_var.h>
#include <dev/thunderbolt/tb_reg.h>
#include <dev/thunderbolt/tb_pcib.h>
#include <dev/thunderbolt/nhi_var.h>
#include <dev/thunderbolt/nhi_reg.h>
#include <dev/thunderbolt/tbcfg_reg.h>
#include <dev/thunderbolt/tb_debug.h>
#include "tb_if.h"
static int tb_pcib_probe(device_t);
static int tb_pcib_attach(device_t);
static int tb_pcib_detach(device_t);
static int tb_pcib_lc_mailbox(device_t, struct tb_lcmbox_cmd *);
static int tb_pcib_pcie2cio_read(device_t, u_int, u_int, u_int,
uint32_t *);
static int tb_pcib_pcie2cio_write(device_t, u_int, u_int, u_int, uint32_t);
static int tb_pcib_find_ufp(device_t, device_t *);
static int tb_pcib_get_debug(device_t, u_int *);
static int tb_pci_probe(device_t);
static int tb_pci_attach(device_t);
static int tb_pci_detach(device_t);
struct tb_pcib_ident {
uint16_t vendor;
uint16_t device;
uint16_t subvendor;
uint16_t subdevice;
uint32_t flags;
const char *desc;
} tb_pcib_identifiers[] = {
{ 0, 0, 0, 0, 0, NULL }
};
static struct tb_pcib_ident *
tb_pcib_find_ident(device_t dev)
{
struct tb_pcib_ident *n;
uint16_t v, d, sv, sd;
v = pci_get_vendor(dev);
d = pci_get_device(dev);
sv = pci_get_subvendor(dev);
sd = pci_get_subdevice(dev);
for (n = tb_pcib_identifiers; n->vendor != 0; n++) {
if ((n->vendor != v) || (n->device != d))
continue;
if (pci_get_class(dev) != PCIC_BRIDGE ||
pci_get_subclass(dev) != PCIS_BRIDGE_PCI)
continue;
if (((n->subvendor != 0xffff) && (n->subvendor != sv)) ||
((n->subdevice != 0xffff) && (n->subdevice != sd)))
continue;
return (n);
}
return (NULL);
}
static void
tb_pcib_get_tunables(struct tb_pcib_softc *sc)
{
char tmpstr[80], oid[80];
sc->debug = 0;
bzero(oid, 80);
if (TUNABLE_STR_FETCH("hw.tbolt.debug_level", oid, 80) != 0)
tb_parse_debug(&sc->debug, oid);
bzero(oid, 80);
snprintf(tmpstr, sizeof(tmpstr), "dev.tbolt.%d.debug_level",
device_get_unit(sc->dev));
if (TUNABLE_STR_FETCH(tmpstr, oid, 80) != 0)
tb_parse_debug(&sc->debug, oid);
return;
}
static int
tb_pcib_setup_sysctl(struct tb_pcib_softc *sc)
{
struct sysctl_ctx_list *ctx = NULL;
struct sysctl_oid *tree = NULL;
ctx = device_get_sysctl_ctx(sc->dev);
if (ctx != NULL)
tree = device_get_sysctl_tree(sc->dev);
if (tree == NULL) {
tb_printf(sc, "Error: cannot create sysctl nodes\n");
return (EINVAL);
}
sc->sysctl_tree = tree;
sc->sysctl_ctx = ctx;
SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree),
OID_AUTO, "debug_level", CTLTYPE_STRING|CTLFLAG_RW|CTLFLAG_MPSAFE,
&sc->debug, 0, tb_debug_sysctl, "A", "Thunderbolt debug level");
return (0);
}
int
tb_pcib_probe_common(device_t dev, char *desc)
{
device_t ufp;
struct tb_pcib_ident *n;
char *suffix;
if ((n = tb_pcib_find_ident(dev)) != NULL) {
ufp = NULL;
if ((TB_FIND_UFP(dev, &ufp) == 0) && (ufp == dev))
suffix = "(Upstream port)";
else
suffix = "(Downstream port)";
snprintf(desc, TB_DESC_MAX, "%s %s", n->desc, suffix);
return (BUS_PROBE_VENDOR);
}
return (ENXIO);
}
static int
tb_pcib_probe(device_t dev)
{
char desc[TB_DESC_MAX];
int val;
if ((val = tb_pcib_probe_common(dev, desc)) <= 0)
device_set_desc_copy(dev, desc);
return (val);
}
int
tb_pcib_attach_common(device_t dev)
{
device_t ufp;
struct tb_pcib_ident *n;
struct tb_pcib_softc *sc;
uint32_t val;
int error;
sc = device_get_softc(dev);
sc->dev = dev;
sc->vsec = -1;
n = tb_pcib_find_ident(dev);
KASSERT(n != NULL, ("Cannot find TB ident"));
sc->flags = n->flags;
tb_pcib_get_tunables(sc);
tb_pcib_setup_sysctl(sc);
tb_debug(sc, DBG_BRIDGE, "busmaster status was %s\n",
(pci_read_config(dev, PCIR_COMMAND, 2) & PCIM_CMD_BUSMASTEREN)
? "enabled" : "disabled");
pci_enable_busmaster(dev);
ufp = NULL;
if ((TB_FIND_UFP(dev, &ufp) == 0) && (ufp != NULL)) {
if (ufp == dev) {
sc->flags |= TB_FLAGS_ISUFP;
if (TB_FIND_UFP(device_get_parent(dev), NULL) ==
EOPNOTSUPP) {
sc->flags |= TB_FLAGS_ISROOT;
}
}
}
if (TB_IS_AR(sc) || TB_IS_TR(sc)) {
error = pci_find_extcap(dev, PCIZ_VENDOR, &sc->vsec);
if (error) {
tb_printf(sc, "Cannot find VSEC capability: %d\n",
error);
return (ENXIO);
}
}
if ((1 || TB_IS_AR(sc)) && TB_IS_ROOT(sc)) {
struct tb_lcmbox_cmd cmd;
cmd.cmd = LC_MBOXOUT_CMD_SXEXIT_TBT;
cmd.data_in = 0;
error = TB_LC_MAILBOX(dev, &cmd);
tb_debug(sc, DBG_BRIDGE, "SXEXIT returned error= %d resp= 0x%x "
"data= 0x%x\n", error, cmd.cmd_resp, cmd.data_out);
}
if (TB_IS_AR(sc) && TB_IS_DFP(sc)) {
tb_debug(sc, DBG_BRIDGE, "Doing AR L1 fixup\n");
val = pci_read_config(dev, sc->vsec + AR_VSCAP_1C, 4);
tb_debug(sc, DBG_BRIDGE|DBG_FULL, "VSEC+0x1c= 0x%08x\n", val);
val |= (1 << 8);
pci_write_config(dev, sc->vsec + AR_VSCAP_1C, val, 4);
val = pci_read_config(dev, sc->vsec + AR_VSCAP_B0, 4);
tb_debug(sc, DBG_BRIDGE|DBG_FULL, "VSEC+0xb0= 0x%08x\n", val);
val |= (1 << 12);
pci_write_config(dev, sc->vsec + AR_VSCAP_B0, val, 4);
}
return (0);
}
static int
tb_pcib_attach(device_t dev)
{
int error;
error = tb_pcib_attach_common(dev);
if (error)
return (error);
return (pcib_attach(dev));
}
static int
tb_pcib_detach(device_t dev)
{
struct tb_pcib_softc *sc;
int error __maybe_unused;
sc = device_get_softc(dev);
tb_debug(sc, DBG_BRIDGE|DBG_ROUTER|DBG_EXTRA, "tb_pcib_detach\n");
if (0 && TB_IS_ROOT(sc)) {
struct tb_lcmbox_cmd cmd;
cmd.cmd = LC_MBOXOUT_CMD_GO2SX;
cmd.data_in = 0;
error = TB_LC_MAILBOX(dev, &cmd);
tb_debug(sc, DBG_BRIDGE, "SXEXIT returned error= %d resp= 0x%x "
"data= 0x%x\n", error, cmd.cmd_resp, cmd.data_out);
}
return (pcib_detach(dev));
}
static int
tb_pcib_lc_mailbox(device_t dev, struct tb_lcmbox_cmd *cmd)
{
struct tb_pcib_softc *sc;
uint32_t regcmd, result;
uint16_t m_in, m_out;
int vsec, i;
sc = device_get_softc(dev);
vsec = TB_PCIB_VSEC(dev);
if (vsec == -1)
return (EOPNOTSUPP);
if (TB_IS_AR(sc)) {
m_in = AR_LC_MBOX_IN;
m_out = AR_LC_MBOX_OUT;
} else if (TB_IS_ICL(sc)) {
m_in = ICL_LC_MBOX_IN;
m_out = ICL_LC_MBOX_OUT;
} else
return (EOPNOTSUPP);
regcmd = LC_MBOXOUT_VALID | (cmd->cmd & LC_MBOXOUT_CMD_MASK);
regcmd |= (cmd->data_in << LC_MBOXOUT_DATA_SHIFT);
tb_debug(sc, DBG_BRIDGE|DBG_FULL, "Writing LC cmd 0x%x\n", regcmd);
pci_write_config(dev, vsec + m_out, regcmd, 4);
for (i = 0; i < 10; i++) {
pause("nhi", 1 * hz);
result = pci_read_config(dev, vsec + m_in, 4);
tb_debug(sc, DBG_BRIDGE|DBG_FULL, "LC Mailbox= 0x%08x\n",
result);
if ((result & LC_MBOXIN_DONE) != 0)
break;
}
pci_write_config(dev, vsec + m_out, 0, 4);
cmd->cmd_resp = result & LC_MBOXIN_CMD_MASK;
cmd->data_out = result >> LC_MBOXIN_CMD_SHIFT;
if ((result & LC_MBOXIN_DONE) == 0)
return (ETIMEDOUT);
return (0);
}
static int
tb_pcib_pcie2cio_wait(device_t dev, u_int timeout)
{
#if 0
uint32_t val;
int vsec;
vsec = TB_PCIB_VSEC(dev);
do {
pci_read_config(dev, vsec + PCIE2CIO_CMD, &val);
if ((val & PCIE2CIO_CMD_START) == 0) {
if (val & PCIE2CIO_CMD_TIMEOUT)
break;
return 0;
}
msleep(50);
} while (time_before(jiffies, end));
#endif
return ETIMEDOUT;
}
static int
tb_pcib_pcie2cio_read(device_t dev, u_int space, u_int port, u_int offset,
uint32_t *val)
{
#if 0
uint32_t cmd;
int ret, vsec;
vsec = TB_PCIB_VSEC(dev);
if (vsec == -1)
return (EOPNOTSUPP);
cmd = index;
cmd |= (port << PCIE2CIO_CMD_PORT_SHIFT) & PCIE2CIO_CMD_PORT_MASK;
cmd |= (space << PCIE2CIO_CMD_CS_SHIFT) & PCIE2CIO_CMD_CS_MASK;
cmd |= PCIE2CIO_CMD_START;
pci_write_config(dev, vsec + PCIE2CIO_CMD, cmd, 4);
if ((ret = pci2cio_wait_completion(dev, 5000)) != 0)
return (ret);
*val = pci_read_config(dev, vsec + PCIE2CIO_RDDATA, 4);
#endif
return (0);
}
static int
tb_pcib_pcie2cio_write(device_t dev, u_int space, u_int port, u_int offset,
uint32_t val)
{
#if 0
uint32_t cmd;
int ret, vsec;
vsec = TB_PCIB_VSEC(dev);
if (vsec == -1)
return (EOPNOTSUPP);
pci_write_config(dev, vsec + PCIE2CIO_WRDATA, val, 4);
cmd = index;
cmd |= (port << PCIE2CIO_CMD_PORT_SHIFT) & PCIE2CIO_CMD_PORT_MASK;
cmd |= (space << PCIE2CIO_CMD_CS_SHIFT) & PCIE2CIO_CMD_CS_MASK;
cmd |= PCIE2CIO_CMD_WRITE | PCIE2CIO_CMD_START;
pci_write_config(dev, vsec + PCIE2CIO_CMD, cmd);
#endif
return (tb_pcib_pcie2cio_wait(dev, 5000));
}
static int
tb_pcib_find_ufp(device_t dev, device_t *ufp)
{
device_t upstream;
struct tb_pcib_softc *sc;
uint32_t vsec, val;
int error;
upstream = NULL;
sc = device_get_softc(dev);
if (sc == NULL)
return (EOPNOTSUPP);
if (TB_IS_UFP(sc)) {
upstream = dev;
error = 0;
goto out;
}
error = pci_find_extcap(dev, PCIZ_VENDOR, &vsec);
if (error == 0) {
val = pci_read_config(dev, vsec + 0x18, 4);
if ((val & 0x1f) > 0) {
upstream = dev;
goto out;
}
}
error = TB_FIND_UFP(device_get_parent(dev), ufp);
if (error == EOPNOTSUPP) {
upstream = dev;
error = 0;
goto out;
} else
return (error);
out:
if (ufp != NULL)
*ufp = upstream;
return (error);
}
static int
tb_pcib_get_debug(device_t dev, u_int *debug)
{
struct tb_pcib_softc *sc;
sc = device_get_softc(dev);
if ((sc == NULL) || (debug == NULL))
return (EOPNOTSUPP);
*debug = sc->debug;
return (0);
}
static device_method_t tb_pcib_methods[] = {
DEVMETHOD(device_probe, tb_pcib_probe),
DEVMETHOD(device_attach, tb_pcib_attach),
DEVMETHOD(device_detach, tb_pcib_detach),
DEVMETHOD(tb_lc_mailbox, tb_pcib_lc_mailbox),
DEVMETHOD(tb_pcie2cio_read, tb_pcib_pcie2cio_read),
DEVMETHOD(tb_pcie2cio_write, tb_pcib_pcie2cio_write),
DEVMETHOD(tb_find_ufp, tb_pcib_find_ufp),
DEVMETHOD(tb_get_debug, tb_pcib_get_debug),
DEVMETHOD_END
};
DEFINE_CLASS_1(tbolt, tb_pcib_driver, tb_pcib_methods,
sizeof(struct tb_pcib_softc), pcib_driver);
DRIVER_MODULE_ORDERED(tb_pcib, pci, tb_pcib_driver,
NULL, NULL, SI_ORDER_MIDDLE);
MODULE_DEPEND(tb_pcib, pci, 1, 1, 1);
static int
tb_pci_probe(device_t dev)
{
struct tb_pcib_ident *n;
device_t parent;
parent = device_get_parent(dev);
if (!is_pci_device(parent))
return (ENXIO);
if ((n = tb_pcib_find_ident(parent)) != NULL) {
switch (n->flags & TB_GEN_MASK) {
case TB_GEN_TB1:
device_set_desc(dev, "Thunderbolt 1 Link");
break;
case TB_GEN_TB2:
device_set_desc(dev, "Thunderbolt 2 Link");
break;
case TB_GEN_TB3:
device_set_desc(dev, "Thunderbolt 3 Link");
break;
case TB_GEN_USB4:
device_set_desc(dev, "USB4 Link");
break;
case TB_GEN_UNK:
default:
device_set_desc(dev, "Thunderbolt Link");
}
return (BUS_PROBE_VENDOR);
}
return (ENXIO);
}
static int
tb_pci_attach(device_t dev)
{
return (pci_attach(dev));
}
static int
tb_pci_detach(device_t dev)
{
return (pci_detach(dev));
}
static device_method_t tb_pci_methods[] = {
DEVMETHOD(device_probe, tb_pci_probe),
DEVMETHOD(device_attach, tb_pci_attach),
DEVMETHOD(device_detach, tb_pci_detach),
DEVMETHOD(tb_find_ufp, tb_generic_find_ufp),
DEVMETHOD(tb_get_debug, tb_generic_get_debug),
DEVMETHOD_END
};
DEFINE_CLASS_1(pci, tb_pci_driver, tb_pci_methods, sizeof(struct pci_softc),
pci_driver);
DRIVER_MODULE(tb_pci, pcib, tb_pci_driver, NULL, NULL);
MODULE_DEPEND(tb_pci, pci, 1, 1, 1);
MODULE_VERSION(tb_pci, 1);