#include <sys/param.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <machine/bus.h>
#include <dev/bhnd/bhnd_eromvar.h>
#include <dev/bhnd/cores/chipc/chipcreg.h>
#include "sibareg.h"
#include "sibavar.h"
#include "siba_eromvar.h"
struct siba_erom;
struct siba_erom_io;
static int siba_eio_init(struct siba_erom_io *io,
struct bhnd_erom_io *eio, u_int ncores);
static uint32_t siba_eio_read_4(struct siba_erom_io *io,
u_int core_idx, bus_size_t offset);
static int siba_eio_read_core_id(struct siba_erom_io *io,
u_int core_idx, int unit,
struct siba_core_id *sid);
static int siba_eio_read_chipid(struct siba_erom_io *io,
bus_addr_t enum_addr,
struct bhnd_chipid *cid);
struct siba_erom_io {
struct bhnd_erom_io *eio;
bhnd_addr_t base_addr;
u_int ncores;
};
struct siba_erom {
struct bhnd_erom obj;
struct siba_erom_io io;
};
#define EROM_LOG(io, fmt, ...) do { \
printf("%s: " fmt, __FUNCTION__, ##__VA_ARGS__); \
} while(0)
static int
siba_erom_probe(bhnd_erom_class_t *cls, struct bhnd_erom_io *eio,
const struct bhnd_chipid *hint, struct bhnd_chipid *cid)
{
struct siba_erom_io io;
uint32_t idreg;
int error;
if ((error = siba_eio_init(&io, eio, 1)))
return (error);
if (hint != NULL) {
struct siba_core_id sid;
if (hint->chip_type != BHND_CHIPTYPE_SIBA)
return (ENXIO);
if ((error = siba_eio_read_core_id(&io, 0, 0, &sid)))
return (error);
if (sid.core_info.vendor != BHND_MFGID_BCM)
return (ENXIO);
if (sid.core_info.device == BHND_COREID_CC)
return (EINVAL);
*cid = *hint;
} else {
idreg = siba_eio_read_4(&io, 0, CHIPC_ID);
if (CHIPC_GET_BITS(idreg, CHIPC_ID_BUS) != BHND_CHIPTYPE_SIBA)
return (ENXIO);
if ((error = siba_eio_read_chipid(&io, SIBA_ENUM_ADDR, cid)))
return (error);
if (cid->chip_type != BHND_CHIPTYPE_SIBA)
return (ENXIO);
}
_Static_assert((2^sizeof(cid->ncores)) <= SIBA_MAX_CORES,
"ncores could result in over-read of backing resource");
return (0);
}
static int
siba_erom_init(bhnd_erom_t *erom, const struct bhnd_chipid *cid,
struct bhnd_erom_io *eio)
{
struct siba_erom *sc;
int error;
sc = (struct siba_erom *)erom;
error = bhnd_erom_io_map(eio, cid->enum_addr,
cid->ncores * SIBA_CORE_SIZE);
if (error) {
printf("%s: failed to map %u cores: %d\n", __FUNCTION__,
cid->ncores, error);
return (error);
}
return (siba_eio_init(&sc->io, eio, cid->ncores));
}
static void
siba_erom_fini(bhnd_erom_t *erom)
{
struct siba_erom *sc = (struct siba_erom *)erom;
bhnd_erom_io_fini(sc->io.eio);
}
static int
siba_eio_init(struct siba_erom_io *io, struct bhnd_erom_io *eio, u_int ncores)
{
io->eio = eio;
io->ncores = ncores;
return (0);
}
static uint32_t
siba_eio_read_4(struct siba_erom_io *io, u_int core_idx, bus_size_t offset)
{
if (core_idx >= io->ncores)
panic("core index %u out of range (ncores=%u)", core_idx,
io->ncores);
if (offset > SIBA_CORE_SIZE - sizeof(uint32_t))
panic("invalid core offset %#jx", (uintmax_t)offset);
return (bhnd_erom_io_read(io->eio, SIBA_CORE_OFFSET(core_idx) + offset,
4));
}
static int
siba_eio_read_core_id(struct siba_erom_io *io, u_int core_idx, int unit,
struct siba_core_id *sid)
{
struct siba_admatch admatch[SIBA_MAX_ADDRSPACE];
uint32_t idhigh, idlow;
uint32_t tpsflag;
uint16_t ocp_vendor;
uint8_t sonics_rev;
uint8_t num_admatch;
uint8_t num_admatch_en;
uint8_t num_cfg;
bool intr_en;
u_int intr_flag;
int error;
idhigh = siba_eio_read_4(io, core_idx, SB0_REG_ABS(SIBA_CFG0_IDHIGH));
idlow = siba_eio_read_4(io, core_idx, SB0_REG_ABS(SIBA_CFG0_IDLOW));
tpsflag = siba_eio_read_4(io, core_idx, SB0_REG_ABS(SIBA_CFG0_TPSFLAG));
ocp_vendor = SIBA_REG_GET(idhigh, IDH_VENDOR);
sonics_rev = SIBA_REG_GET(idlow, IDL_SBREV);
num_admatch = SIBA_REG_GET(idlow, IDL_NRADDR) + 1 ;
if (num_admatch > nitems(admatch)) {
printf("core%u: invalid admatch count %hhu\n", core_idx,
num_admatch);
return (EINVAL);
}
intr_en = ((tpsflag & SIBA_TPS_F0EN0) != 0);
intr_flag = SIBA_REG_GET(tpsflag, TPS_NUM0);
num_cfg = SIBA_CFG_NUM_2_2;
if (sonics_rev >= SIBA_IDL_SBREV_2_3)
num_cfg = SIBA_CFG_NUM_2_3;
num_admatch_en = 0;
for (uint8_t i = 0; i < num_admatch; i++) {
uint32_t am_value;
u_int am_offset;
KASSERT(i < nitems(admatch), ("invalid admatch index"));
am_offset = siba_admatch_offset(i);
if (am_offset == 0) {
printf("core%u: addrspace %hhu is unsupported",
core_idx, i);
return (ENODEV);
}
am_value = siba_eio_read_4(io, core_idx, am_offset);
error = siba_parse_admatch(am_value, &admatch[num_admatch_en]);
if (error) {
printf("core%u: failed to decode admatch[%hhu] "
"register value 0x%x\n", core_idx, i, am_value);
return (error);
}
if (!admatch[num_admatch_en].am_enabled)
continue;
if (admatch[num_admatch_en].am_negative) {
printf("core%u: unsupported negative admatch[%hhu] "
"value 0x%x\n", core_idx, i, am_value);
return (ENXIO);
}
num_admatch_en++;
}
*sid = (struct siba_core_id) {
.core_info = {
.vendor = siba_get_bhnd_mfgid(ocp_vendor),
.device = SIBA_REG_GET(idhigh, IDH_DEVICE),
.hwrev = SIBA_IDH_CORE_REV(idhigh),
.core_idx = core_idx,
.unit = unit
},
.sonics_vendor = ocp_vendor,
.sonics_rev = sonics_rev,
.intr_en = intr_en,
.intr_flag = intr_flag,
.num_admatch = num_admatch_en,
.num_cfg_blocks = num_cfg
};
memcpy(sid->admatch, admatch, num_admatch_en * sizeof(admatch[0]));
return (0);
}
int
siba_erom_get_core_id(struct siba_erom *sc, u_int core_idx,
struct siba_core_id *result)
{
struct siba_core_id sid;
int error;
if ((error = siba_eio_read_core_id(&sc->io, core_idx, 0, &sid)))
return (error);
for (u_int i = 0; i < core_idx; i++) {
struct siba_core_id prev;
if ((error = siba_eio_read_core_id(&sc->io, i, 0, &prev)))
return (error);
if (sid.core_info.vendor == prev.core_info.vendor &&
sid.core_info.device == prev.core_info.device)
sid.core_info.unit++;
}
*result = sid;
return (0);
}
static int
siba_eio_read_chipid(struct siba_erom_io *io, bus_addr_t enum_addr,
struct bhnd_chipid *cid)
{
struct siba_core_id ccid;
int error;
if ((error = siba_eio_read_core_id(io, 0, 0, &ccid)))
return (error);
if (ccid.core_info.vendor != BHND_MFGID_BCM ||
ccid.core_info.device != BHND_COREID_CC)
{
if (bootverbose) {
EROM_LOG(io, "first core not chipcommon "
"(vendor=%#hx, core=%#hx)\n", ccid.core_info.vendor,
ccid.core_info.device);
}
return (ENXIO);
}
if ((error = bhnd_erom_read_chipid(io->eio, cid)))
return (error);
if (CHIPC_NCORES_MIN_HWREV(ccid.core_info.hwrev))
return (0);
switch (cid->chip_id) {
case BHND_CHIPID_BCM4306:
cid->ncores = 6;
break;
case BHND_CHIPID_BCM4704:
cid->ncores = 9;
break;
case BHND_CHIPID_BCM5365:
cid->ncores = 7;
break;
default:
return (EINVAL);
}
return (0);
}
static int
siba_erom_lookup_core(bhnd_erom_t *erom, const struct bhnd_core_match *desc,
struct bhnd_core_info *core)
{
struct siba_erom *sc;
struct bhnd_core_match imatch;
int error;
sc = (struct siba_erom *)erom;
imatch = *desc;
imatch.m.match.core_unit = 0;
for (u_int i = 0; i < sc->io.ncores; i++) {
struct siba_core_id sid;
struct bhnd_core_info ci;
if ((error = siba_eio_read_core_id(&sc->io, i, 0, &sid)))
return (error);
ci = sid.core_info;
if (!bhnd_core_matches(&ci, &imatch))
continue;
for (u_int j = 0; j < i; j++) {
error = siba_eio_read_core_id(&sc->io, j, 0, &sid);
if (error)
return (error);
if (sid.core_info.vendor == ci.vendor &&
sid.core_info.device == ci.device)
ci.unit++;
}
if (!bhnd_core_matches(&ci, desc))
continue;
*core = ci;
return (0);
}
return (ENOENT);
}
static int
siba_erom_lookup_core_addr(bhnd_erom_t *erom, const struct bhnd_core_match *desc,
bhnd_port_type type, u_int port, u_int region, struct bhnd_core_info *info,
bhnd_addr_t *addr, bhnd_size_t *size)
{
struct siba_erom *sc;
struct bhnd_core_info core;
struct siba_core_id sid;
struct siba_admatch admatch;
uint32_t am;
u_int am_offset;
u_int addrspace, cfg;
int error;
sc = (struct siba_erom *)erom;
if ((error = siba_erom_lookup_core(erom, desc, &core)))
return (error);
error = siba_eio_read_core_id(&sc->io, core.core_idx, core.unit, &sid);
if (error)
return (error);
if (!siba_is_port_valid(&sid, type, port))
return (ENOENT);
if (region >= siba_port_region_count(&sid, type, port))
return (ENOENT);
error = siba_cfg_index(&sid, type, port, region, &cfg);
if (!error) {
bhnd_addr_t region_addr;
bhnd_addr_t region_size;
bhnd_size_t cfg_offset, cfg_size;
cfg_offset = SIBA_CFG_OFFSET(cfg);
cfg_size = SIBA_CFG_SIZE;
error = siba_erom_lookup_core_addr(erom, desc, BHND_PORT_DEVICE,
0, 0, NULL, ®ion_addr, ®ion_size);
if (error)
return (error);
if (region_size < cfg_size) {
printf("%s%u.%u offset %ju exceeds %s0.0 size %ju\n",
bhnd_port_type_name(type), port, region, cfg_offset,
bhnd_port_type_name(BHND_PORT_DEVICE), region_size);
return (ENXIO);
}
if (BHND_ADDR_MAX - region_addr < cfg_offset) {
printf("%s%u.%u offset %ju would overflow %s0.0 addr "
"%ju\n", bhnd_port_type_name(type), port, region,
cfg_offset, bhnd_port_type_name(BHND_PORT_DEVICE),
region_addr);
return (ENXIO);
}
if (info != NULL)
*info = core;
*addr = region_addr + cfg_offset;
*size = cfg_size;
return (0);
}
error = siba_addrspace_index(&sid, type, port, region, &addrspace);
if (error)
return (error);
am_offset = siba_admatch_offset(addrspace);
if (am_offset == 0) {
printf("addrspace %u is unsupported", addrspace);
return (ENODEV);
}
am = siba_eio_read_4(&sc->io, core.core_idx, am_offset);
if ((error = siba_parse_admatch(am, &admatch))) {
printf("failed to decode address match register value 0x%x\n",
am);
return (error);
}
if (info != NULL)
*info = core;
*addr = admatch.am_base;
*size = admatch.am_size;
return (0);
}
static int
siba_erom_get_core_table(bhnd_erom_t *erom, struct bhnd_core_info **cores,
u_int *num_cores)
{
struct siba_erom *sc;
struct bhnd_core_info *out;
int error;
sc = (struct siba_erom *)erom;
out = mallocarray(sc->io.ncores, sizeof(*out), M_BHND, M_NOWAIT);
if (out == NULL)
return (ENOMEM);
*cores = out;
*num_cores = sc->io.ncores;
for (u_int i = 0; i < sc->io.ncores; i++) {
struct siba_core_id sid;
if ((error = siba_eio_read_core_id(&sc->io, i, 0, &sid)))
return (error);
out[i] = sid.core_info;
for (u_int j = 0; j < i; j++) {
if (out[j].vendor == out[i].vendor &&
out[j].device == out[i].device)
out[i].unit++;
}
}
return (0);
}
static void
siba_erom_free_core_table(bhnd_erom_t *erom, struct bhnd_core_info *cores)
{
free(cores, M_BHND);
}
static int
siba_erom_dump(bhnd_erom_t *erom)
{
struct siba_erom *sc;
int error;
sc = (struct siba_erom *)erom;
for (u_int i = 0; i < sc->io.ncores; i++) {
uint32_t idhigh, idlow;
uint32_t nraddr;
idhigh = siba_eio_read_4(&sc->io, i,
SB0_REG_ABS(SIBA_CFG0_IDHIGH));
idlow = siba_eio_read_4(&sc->io, i,
SB0_REG_ABS(SIBA_CFG0_IDLOW));
printf("siba core %u:\n", i);
printf("\tvendor:\t0x%04x\n", SIBA_REG_GET(idhigh, IDH_VENDOR));
printf("\tdevice:\t0x%04x\n", SIBA_REG_GET(idhigh, IDH_DEVICE));
printf("\trev:\t0x%04x\n", SIBA_IDH_CORE_REV(idhigh));
printf("\tsbrev:\t0x%02x\n", SIBA_REG_GET(idlow, IDL_SBREV));
nraddr = SIBA_REG_GET(idlow, IDL_NRADDR);
printf("\tnraddr\t0x%04x\n", nraddr);
for (size_t addrspace = 0; addrspace < nraddr; addrspace++) {
struct siba_admatch admatch;
uint32_t am;
u_int am_offset;
am_offset = siba_admatch_offset(addrspace);
if (am_offset == 0) {
printf("addrspace %zu unsupported",
addrspace);
break;
}
am = siba_eio_read_4(&sc->io, i, am_offset);
if ((error = siba_parse_admatch(am, &admatch))) {
printf("failed to decode address match "
"register value 0x%x\n", am);
continue;
}
printf("\taddrspace %zu\n", addrspace);
printf("\t\taddr: 0x%08x\n", admatch.am_base);
printf("\t\tsize: 0x%08x\n", admatch.am_size);
}
}
return (0);
}
static kobj_method_t siba_erom_methods[] = {
KOBJMETHOD(bhnd_erom_probe, siba_erom_probe),
KOBJMETHOD(bhnd_erom_init, siba_erom_init),
KOBJMETHOD(bhnd_erom_fini, siba_erom_fini),
KOBJMETHOD(bhnd_erom_get_core_table, siba_erom_get_core_table),
KOBJMETHOD(bhnd_erom_free_core_table, siba_erom_free_core_table),
KOBJMETHOD(bhnd_erom_lookup_core, siba_erom_lookup_core),
KOBJMETHOD(bhnd_erom_lookup_core_addr, siba_erom_lookup_core_addr),
KOBJMETHOD(bhnd_erom_dump, siba_erom_dump),
KOBJMETHOD_END
};
BHND_EROM_DEFINE_CLASS(siba_erom, siba_erom_parser, siba_erom_methods, sizeof(struct siba_erom));