571 lines
13 KiB
C
571 lines
13 KiB
C
/* $OpenBSD: sti_pci.c,v 1.13 2023/04/13 15:07:43 miod Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2006, 2007, 2023 Miodrag Vallat.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice, this permission notice, and the disclaimer below
|
|
* appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/device.h>
|
|
|
|
#include <dev/pci/pcireg.h>
|
|
#include <dev/pci/pcivar.h>
|
|
#include <dev/pci/pcidevs.h>
|
|
|
|
#include <dev/wscons/wsdisplayvar.h>
|
|
|
|
#include <dev/ic/stireg.h>
|
|
#include <dev/ic/stivar.h>
|
|
|
|
int sti_pci_match(struct device *, void *, void *);
|
|
void sti_pci_attach(struct device *, struct device *, void *);
|
|
|
|
struct sti_pci_softc {
|
|
struct sti_softc sc_base;
|
|
|
|
pci_chipset_tag_t sc_pc;
|
|
pcitag_t sc_tag;
|
|
|
|
bus_space_handle_t sc_romh;
|
|
};
|
|
|
|
const struct cfattach sti_pci_ca = {
|
|
sizeof(struct sti_pci_softc), sti_pci_match, sti_pci_attach
|
|
};
|
|
|
|
const struct pci_matchid sti_pci_devices[] = {
|
|
{ PCI_VENDOR_HP, PCI_PRODUCT_HP_VISUALIZE_EG },
|
|
{ PCI_VENDOR_HP, PCI_PRODUCT_HP_VISUALIZE_FX2 },
|
|
{ PCI_VENDOR_HP, PCI_PRODUCT_HP_VISUALIZE_FX4 },
|
|
{ PCI_VENDOR_HP, PCI_PRODUCT_HP_VISUALIZE_FX6 },
|
|
{ PCI_VENDOR_HP, PCI_PRODUCT_HP_VISUALIZE_FXE },
|
|
};
|
|
|
|
int sti_readbar(struct sti_softc *, struct pci_attach_args *, u_int, int);
|
|
int sti_check_rom(struct sti_pci_softc *, struct pci_attach_args *);
|
|
void sti_pci_enable_rom(struct sti_softc *);
|
|
void sti_pci_disable_rom(struct sti_softc *);
|
|
|
|
int sti_pci_is_console(struct pci_attach_args *, bus_addr_t *);
|
|
|
|
int
|
|
sti_pci_match(struct device *parent, void *cf, void *aux)
|
|
{
|
|
struct pci_attach_args *paa = aux;
|
|
|
|
return pci_matchbyid(paa, sti_pci_devices,
|
|
sizeof(sti_pci_devices) / sizeof(sti_pci_devices[0]));
|
|
}
|
|
|
|
void
|
|
sti_pci_attach(struct device *parent, struct device *self, void *aux)
|
|
{
|
|
struct sti_pci_softc *spc = (void *)self;
|
|
struct pci_attach_args *paa = aux;
|
|
|
|
spc->sc_pc = paa->pa_pc;
|
|
spc->sc_tag = paa->pa_tag;
|
|
spc->sc_base.sc_enable_rom = sti_pci_enable_rom;
|
|
spc->sc_base.sc_disable_rom = sti_pci_disable_rom;
|
|
|
|
printf("\n");
|
|
|
|
if (sti_check_rom(spc, paa) != 0)
|
|
return;
|
|
|
|
printf("%s", self->dv_xname);
|
|
if (sti_pci_is_console(paa, spc->sc_base.bases) != 0)
|
|
spc->sc_base.sc_flags |= STI_CONSOLE;
|
|
if (sti_attach_common(&spc->sc_base, paa->pa_iot, paa->pa_memt,
|
|
spc->sc_romh, STI_CODEBASE_MAIN) == 0)
|
|
startuphook_establish(sti_end_attach, spc);
|
|
}
|
|
|
|
/*
|
|
* Enable PCI ROM.
|
|
*/
|
|
void
|
|
sti_pci_enable_rom(struct sti_softc *sc)
|
|
{
|
|
struct sti_pci_softc *spc = (struct sti_pci_softc *)sc;
|
|
pcireg_t address;
|
|
|
|
if (!ISSET(sc->sc_flags, STI_ROM_ENABLED)) {
|
|
address = pci_conf_read(spc->sc_pc, spc->sc_tag, PCI_ROM_REG);
|
|
address |= PCI_ROM_ENABLE;
|
|
pci_conf_write(spc->sc_pc, spc->sc_tag, PCI_ROM_REG, address);
|
|
SET(sc->sc_flags, STI_ROM_ENABLED);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Disable PCI ROM.
|
|
*/
|
|
void
|
|
sti_pci_disable_rom(struct sti_softc *sc)
|
|
{
|
|
struct sti_pci_softc *spc = (struct sti_pci_softc *)sc;
|
|
pcireg_t address;
|
|
|
|
if (ISSET(sc->sc_flags, STI_ROM_ENABLED)) {
|
|
address = pci_conf_read(spc->sc_pc, spc->sc_tag, PCI_ROM_REG);
|
|
address &= ~PCI_ROM_ENABLE;
|
|
pci_conf_write(spc->sc_pc, spc->sc_tag, PCI_ROM_REG, address);
|
|
|
|
CLR(sc->sc_flags, STI_ROM_ENABLED);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We have to be extremely careful with output in this file, as the
|
|
* device we are trying to attach might be the console, and we are
|
|
* still using the PDC routines for output at this point.
|
|
*
|
|
* On some devices, if not all, PDC routines assume the STI ROM is *NOT*
|
|
* mapped when they are invoked, and they will cause the system to freeze
|
|
* if it is mapped.
|
|
*
|
|
* As a result, we need to make sure the ROM is not mapped when invoking
|
|
* printf(). The following wrapper takes care of this to reduce the risk
|
|
* of making a mistake.
|
|
*/
|
|
|
|
static int
|
|
sti_local_printf(struct sti_softc *sc, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int rc;
|
|
int enabled = sc->sc_flags & STI_ROM_ENABLED;
|
|
|
|
if (enabled)
|
|
sti_pci_disable_rom(sc);
|
|
va_start(ap, fmt);
|
|
rc = vprintf(fmt, ap);
|
|
if (enabled)
|
|
sti_pci_enable_rom(sc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
#define printf(fmt, ...) sti_local_printf(sc, fmt, ## __VA_ARGS__)
|
|
|
|
/*
|
|
* PCI ROM Data Structure (6.3.1.2)
|
|
*/
|
|
|
|
struct pcirom_ds {
|
|
uint32_t signature;
|
|
uint16_t vid;
|
|
uint16_t pid;
|
|
uint16_t reserved_8;
|
|
uint16_t dslen;
|
|
uint8_t rev;
|
|
uint8_t class[3];
|
|
uint16_t romlen;
|
|
uint16_t level;
|
|
uint8_t arch;
|
|
uint8_t indicator;
|
|
uint16_t reserved_16;
|
|
};
|
|
|
|
/*
|
|
* Callback data used while walking PCI ROM images to pick the most
|
|
* appropriate image.
|
|
*/
|
|
|
|
struct sti_pcirom_walk_ctx {
|
|
#ifdef STIDEBUG
|
|
struct sti_softc *sc;
|
|
#endif
|
|
bus_addr_t romoffs;
|
|
};
|
|
|
|
int sti_pcirom_check(bus_space_tag_t, bus_space_handle_t, bus_addr_t,
|
|
const struct pcirom_ds *, void *);
|
|
int sti_pcirom_walk(struct sti_softc *, bus_space_tag_t, bus_space_handle_t,
|
|
bus_size_t, int (*)(bus_space_tag_t, bus_space_handle_t, bus_addr_t,
|
|
const struct pcirom_ds *, void *), void *);
|
|
|
|
/*
|
|
* Grovel the STI ROM image.
|
|
*/
|
|
|
|
int
|
|
sti_check_rom(struct sti_pci_softc *spc, struct pci_attach_args *pa)
|
|
{
|
|
struct sti_softc *sc = &spc->sc_base;
|
|
pcireg_t address, mask;
|
|
bus_space_handle_t romh;
|
|
bus_size_t romsize, stiromsize;
|
|
bus_addr_t offs;
|
|
uint8_t region_bars[STI_REGION_MAX];
|
|
int i;
|
|
int rc;
|
|
|
|
struct sti_pcirom_walk_ctx ctx;
|
|
|
|
/* sort of inline sti_pci_enable_rom(sc) */
|
|
address = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_ROM_REG);
|
|
pci_conf_write(pa->pa_pc, pa->pa_tag, PCI_ROM_REG, ~PCI_ROM_ENABLE);
|
|
mask = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_ROM_REG);
|
|
address |= PCI_ROM_ENABLE;
|
|
pci_conf_write(pa->pa_pc, pa->pa_tag, PCI_ROM_REG, address);
|
|
sc->sc_flags |= STI_ROM_ENABLED;
|
|
|
|
/*
|
|
* Map the complete ROM for now.
|
|
*/
|
|
|
|
romsize = PCI_ROM_SIZE(mask);
|
|
rc = bus_space_map(pa->pa_memt, PCI_ROM_ADDR(address), romsize,
|
|
0, &romh);
|
|
if (rc != 0) {
|
|
printf("%s: can't map PCI ROM (%d)\n",
|
|
sc->sc_dev.dv_xname, rc);
|
|
goto disable_return;
|
|
}
|
|
|
|
/*
|
|
* Iterate over the ROM images, pick the best candidate.
|
|
*/
|
|
|
|
#ifdef STIDEBUG
|
|
ctx.sc = sc;
|
|
#endif
|
|
ctx.romoffs = (bus_addr_t)-1;
|
|
rc = sti_pcirom_walk(sc, pa->pa_memt, romh, romsize, sti_pcirom_check,
|
|
&ctx);
|
|
|
|
if (ctx.romoffs == (bus_addr_t)-1) {
|
|
if (rc == 0) {
|
|
printf("%s: found no ROM with correct microcode"
|
|
" architecture\n", sc->sc_dev.dv_xname);
|
|
rc = ENOEXEC;
|
|
}
|
|
goto unmap_disable_return;
|
|
}
|
|
|
|
/*
|
|
* Read the STI region BAR assignments.
|
|
*/
|
|
|
|
offs = ctx.romoffs +
|
|
(bus_addr_t)bus_space_read_2(pa->pa_memt, romh, ctx.romoffs + 0x0e);
|
|
bus_space_read_region_1(pa->pa_memt, romh, offs, region_bars,
|
|
STI_REGION_MAX);
|
|
for (i = 0; i < STI_REGION_MAX; i++) {
|
|
rc = sti_readbar(sc, pa, i, region_bars[i]);
|
|
if (rc != 0)
|
|
goto unmap_disable_return;
|
|
}
|
|
|
|
/*
|
|
* Find out where the STI ROM itself lies within the PCI ROM,
|
|
* and its size.
|
|
*/
|
|
|
|
offs = ctx.romoffs +
|
|
(bus_addr_t)bus_space_read_4(pa->pa_memt, romh, ctx.romoffs + 0x08);
|
|
stiromsize = (bus_addr_t)bus_space_read_4(pa->pa_memt, romh,
|
|
offs + 0x18);
|
|
stiromsize = letoh32(stiromsize);
|
|
|
|
/*
|
|
* Replace our mapping with a smaller mapping of only the area
|
|
* we are interested in.
|
|
*/
|
|
|
|
bus_space_unmap(pa->pa_memt, romh, romsize);
|
|
rc = bus_space_map(pa->pa_memt, PCI_ROM_ADDR(address) + offs,
|
|
stiromsize, 0, &spc->sc_romh);
|
|
if (rc != 0) {
|
|
printf("%s: can't map STI ROM (%d)\n",
|
|
sc->sc_dev.dv_xname, rc);
|
|
goto disable_return;
|
|
}
|
|
|
|
sti_pci_disable_rom(sc);
|
|
return 0;
|
|
|
|
unmap_disable_return:
|
|
bus_space_unmap(pa->pa_memt, romh, romsize);
|
|
disable_return:
|
|
sti_pci_disable_rom(sc);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Decode a BAR register.
|
|
*/
|
|
int
|
|
sti_readbar(struct sti_softc *sc, struct pci_attach_args *pa, u_int region,
|
|
int bar)
|
|
{
|
|
bus_addr_t addr;
|
|
bus_size_t size;
|
|
pcireg_t type;
|
|
int rc;
|
|
|
|
if (bar == 0) {
|
|
sc->bases[region] = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (bar < PCI_MAPREG_START || bar > PCI_MAPREG_PPB_END) {
|
|
#ifdef DIAGNOSTIC
|
|
printf("%s: unexpected bar %02x for region %d\n",
|
|
sc->sc_dev.dv_xname, bar, region);
|
|
#endif
|
|
return EINVAL;
|
|
}
|
|
|
|
type = pci_mapreg_type(pa->pa_pc, pa->pa_tag, bar);
|
|
rc = pci_mapreg_info(pa->pa_pc, pa->pa_tag, bar, type, &addr, &size,
|
|
NULL);
|
|
if (rc != 0) {
|
|
printf("%s: invalid bar %02x for region %d\n",
|
|
sc->sc_dev.dv_xname, bar, region);
|
|
return rc;
|
|
}
|
|
|
|
sc->bases[region] = addr;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Check a PCI ROM image for an STI ROM.
|
|
*/
|
|
|
|
int
|
|
sti_pcirom_check(bus_space_tag_t romt, bus_space_handle_t romh, bus_addr_t offs,
|
|
const struct pcirom_ds *ds, void *v)
|
|
{
|
|
struct sti_pcirom_walk_ctx *ctx = v;
|
|
#ifdef STIDEBUG
|
|
struct sti_softc *sc = ctx->sc;
|
|
#endif
|
|
uint32_t tmp32;
|
|
|
|
/*
|
|
* Check for a valid STI ROM header.
|
|
*/
|
|
|
|
tmp32 = bus_space_read_4(romt, romh, offs + 0);
|
|
tmp32 = letoh32(tmp32);
|
|
if (tmp32 != 0x55aa0000) {
|
|
/* Not an STI ROM image, onto the next */
|
|
#ifdef STIDEBUG
|
|
printf("Invalid HP ROM signature (%08x)\n", tmp32);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Check ROM type.
|
|
*/
|
|
|
|
tmp32 = bus_space_read_4(romt, romh, offs + 4);
|
|
tmp32 = letoh32(tmp32);
|
|
if (tmp32 != 0x00000001) { /* 1 == STI ROM */
|
|
#ifdef STIDEBUG
|
|
printf("Unknown HP ROM type (%08x)\n", tmp32);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
#ifdef STIDEBUG
|
|
printf("ROM architecture code %02x", ds->arch);
|
|
#endif
|
|
switch (ds->arch) {
|
|
#ifdef __hppa__
|
|
/*
|
|
* The PCI specification assigns value 0x02 to PA-RISC, but
|
|
* according to the STI specification (and to hardware),
|
|
* the correct value to check for is 0x10.
|
|
*/
|
|
case 0x10:
|
|
if (ctx->romoffs == (bus_addr_t)-1)
|
|
ctx->romoffs = offs;
|
|
break;
|
|
#endif
|
|
#ifdef __i386__
|
|
case 0x00:
|
|
if (ctx->romoffs == (bus_addr_t)-1)
|
|
ctx->romoffs = offs;
|
|
break;
|
|
#endif
|
|
default:
|
|
#ifdef STIDEBUG
|
|
printf(" (wrong architecture)");
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
#ifdef STIDEBUG
|
|
if (ctx->romoffs == offs)
|
|
printf(" -> SELECTED");
|
|
printf("\n");
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Iterate over all PCI ROM images.
|
|
* This code is absolutely not related to sti(4) and could be moved
|
|
* elsewhere in case other drivers have a need for it, but is kept
|
|
* here for now due to the printf() wrapper requirement (which is why
|
|
* there is an otherwise unnecessary softc argument).
|
|
*/
|
|
|
|
int
|
|
sti_pcirom_walk(struct sti_softc *sc, bus_space_tag_t romt,
|
|
bus_space_handle_t romh, bus_size_t romsize, int (*cb)(bus_space_tag_t,
|
|
bus_space_handle_t, bus_addr_t, const struct pcirom_ds *, void *),
|
|
void *cbarg)
|
|
{
|
|
bus_addr_t offs;
|
|
bus_size_t subsize;
|
|
int rc = 0;
|
|
|
|
for (offs = 0; offs < romsize; offs += subsize) {
|
|
struct pcirom_ds ds;
|
|
bus_addr_t dsoffs;
|
|
uint16_t tmp16;
|
|
|
|
#ifdef STIDEBUG
|
|
printf("Checking for ROM image at offset %08lx/%08lx\n",
|
|
offs, romsize);
|
|
#endif
|
|
|
|
/*
|
|
* Check for a valid ROM header (6.3.1.1).
|
|
*/
|
|
|
|
tmp16 = bus_space_read_2(romt, romh, offs + 0);
|
|
tmp16 = letoh16(tmp16);
|
|
if (tmp16 != 0x55aa) {
|
|
if (offs == 0) {
|
|
printf("%s: invalid PCI ROM header signature"
|
|
" (%04x)\n",
|
|
sc->sc_dev.dv_xname, tmp16);
|
|
rc = EINVAL;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Check for a valid ROM data structure (6.3.1.2).
|
|
*/
|
|
|
|
dsoffs = (bus_addr_t)bus_space_read_2(romt, romh, offs + 0x18);
|
|
#ifdef STIDEBUG
|
|
printf("PCI DS offset %04lx\n", dsoffs);
|
|
#endif
|
|
if ((dsoffs & 0x03) != 0 || dsoffs < 0x1a ||
|
|
offs + dsoffs + sizeof(struct pcirom_ds) > romsize) {
|
|
if (offs == 0) {
|
|
printf("%s: ill-formed PCI Data Structure"
|
|
" (offset %04lx)\n",
|
|
sc->sc_dev.dv_xname, dsoffs);
|
|
rc = EINVAL;
|
|
} else {
|
|
#ifdef STIDEBUG
|
|
printf("Ill-formed PCI Data Structure"
|
|
" (offset %04lx)\n", dsoffs);
|
|
#endif
|
|
}
|
|
break;
|
|
}
|
|
|
|
bus_space_read_region_1(romt, romh, offs + dsoffs,
|
|
(uint8_t *)&ds, sizeof ds);
|
|
/* convert sizes to host endianness */
|
|
ds.dslen = letoh16(ds.dslen);
|
|
ds.romlen = letoh16(ds.romlen);
|
|
#if 0 /* not used in this code */
|
|
ds.vid = letoh16(ds.vid);
|
|
ds.pid = letoh16(ds.pid);
|
|
ds.level = letoh16(ds.level);
|
|
#endif
|
|
if (ds.signature != 0x50434952) { /* PCIR */
|
|
if (offs == 0) {
|
|
printf("%s: invalid PCI data signature"
|
|
" (%08x)\n",
|
|
sc->sc_dev.dv_xname, ds.signature);
|
|
rc = EINVAL;
|
|
} else {
|
|
#ifdef STIDEBUG
|
|
printf(" invalid PCI data signature %08x\n",
|
|
ds.signature);
|
|
#endif
|
|
}
|
|
break;
|
|
}
|
|
|
|
#ifdef STIDEBUG
|
|
printf("PCI DS length %04lx\n", ds.dslen);
|
|
#endif
|
|
if (ds.dslen < sizeof ds || dsoffs + ds.dslen > romsize) {
|
|
if (offs == 0) {
|
|
printf("%s: ill-formed PCI Data Structure"
|
|
" (size %04lx)\n",
|
|
sc->sc_dev.dv_xname, ds.dslen);
|
|
rc = EINVAL;
|
|
} else {
|
|
#ifdef STIDEBUG
|
|
printf("Ill-formed PCI Data Structure"
|
|
" (size %04lx)\n", ds.dslen);
|
|
#endif
|
|
}
|
|
break;
|
|
}
|
|
|
|
subsize = ((bus_size_t)ds.romlen) << 9;
|
|
#ifdef STIDEBUG
|
|
printf("ROM image size %08lx\n", subsize);
|
|
#endif
|
|
if (subsize == 0 || offs + subsize > romsize) {
|
|
if (offs == 0) {
|
|
printf("%s: invalid ROM image size"
|
|
" (%04lx)\n",
|
|
sc->sc_dev.dv_xname, subsize);
|
|
rc = EINVAL;
|
|
} else {
|
|
#ifdef STIDEBUG
|
|
printf("Invalid ROM image size"
|
|
" (%04lx)\n", subsize);
|
|
#endif
|
|
}
|
|
break;
|
|
}
|
|
|
|
rc = (*cb)(romt, romh, offs, &ds, cbarg);
|
|
if (rc != 0)
|
|
break;
|
|
|
|
if ((ds.indicator & 0x80) != 0) {
|
|
/* no more ROM images */
|
|
#ifdef STIDEBUG
|
|
printf("no more ROM images\n");
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|