#include <sys/obpdefs.h>
#include <sys/types.h>
#include <sys/cmn_err.h>
#include <sys/cpuvar.h>
#include <sys/membar.h>
#include <sys/x_call.h>
#include <sys/machsystm.h>
#include <sys/cpu_sgnblk_defs.h>
#include <sys/pte.h>
#include <vm/hat_sfmmu.h>
#include <sys/promif.h>
#include <sys/note.h>
#include <sys/vmsystm.h>
#include <vm/seg_kmem.h>
#include <sys/sbd_ioctl.h>
#include <sys/sbd.h>
#include <sys/sbdp_priv.h>
#include <sys/sbdp_mem.h>
#include <sys/sbdp_error.h>
#include <sys/sgsbbc_iosram.h>
#include <sys/prom_plat.h>
#include <sys/cheetahregs.h>
uint64_t *sbdp_valp;
extern uint64_t va_to_pa(void *);
static int sbdp_cpu_ntries = 50000;
static int sbdp_cpu_delay = 100;
void sbdp_get_cpu_sram_addr(uint64_t, uint64_t);
static int cpusram_map(caddr_t *, pgcnt_t *);
static void cpusram_unmap(caddr_t *, pgcnt_t);
extern int prom_serengeti_wakeupcpu(pnode_t);
extern int prom_serengeti_cpu_off(pnode_t);
extern sbdp_wnode_t *sbdp_get_wnodep(int);
extern caddr_t sbdp_shutdown_va;
static int sbdp_prom_get_cpu(void *arg, int changed);
static void sbdp_cpu_shutdown_self(void);
int
sbdp_disconnect_cpu(sbdp_handle_t *hp, dev_info_t *dip, processorid_t cpuid)
{
pnode_t nodeid;
int bd, wnode;
sbdp_wnode_t *wnodep;
sbdp_bd_t *bdp = NULL;
int rv = 0;
processorid_t cpu = cpuid;
processorid_t portid;
static fn_t f = "sbdp_disconnect_cpu";
SBDP_DBG_FUNC("%s\n", f);
nodeid = ddi_get_nodeid(dip);
if (SBDP_INJECT_ERROR(f, 0) ||
sbdp_get_bd_and_wnode_num(nodeid, &bd, &wnode) != 0) {
rv = -1;
goto out;
}
wnodep = sbdp_get_wnodep(wnode);
bdp = &wnodep->bds[bd];
ASSERT(bdp);
mutex_enter(&bdp->bd_mutex);
sbdp_cpu_in_reset(wnode, bd, SG_CPUID_TO_CPU_UNIT(cpuid), 1);
if (SBDP_INJECT_ERROR(f, 1) || prom_serengeti_cpu_off(nodeid) != 0) {
rv = -1;
goto out;
}
portid = SG_CPUID_TO_PORTID(cpuid);
if (!SBDP_IS_CPU_PRESENT(bdp, SG_CPUID_TO_CPU_UNIT(portid))) {
cpu = portid;
}
if (SBDP_INJECT_ERROR(f, 2) || sbdp_stop_cpu(cpu) != 0) {
rv = -1;
goto out;
}
out:
if (bdp != NULL) {
mutex_exit(&bdp->bd_mutex);
}
if (rv != 0) {
sbdp_set_err(hp->h_err, ESGT_STOPCPU, NULL);
}
return (rv);
}
int
sbdp_connect_cpu(sbdp_handle_t *hp, dev_info_t *dip, processorid_t cpuid)
{
pnode_t nodeid;
sbd_error_t *sep;
int i;
int bd, wnode;
int rv = 0;
static fn_t f = "sbdp_connect_cpu";
SBDP_DBG_FUNC("%s\n", f);
sep = hp->h_err;
nodeid = ddi_get_nodeid(dip);
if (SBDP_INJECT_ERROR(f, 0) ||
sbdp_get_bd_and_wnode_num(nodeid, &bd, &wnode) != 0) {
rv = -1;
goto out;
}
if (sbdp_is_cpu_in_reset(wnode, bd, SG_CPUID_TO_CPU_UNIT(cpuid))) {
sbdp_wnode_t *wnodep;
sbdp_bd_t *bdp = NULL;
processorid_t cpu = cpuid;
processorid_t portid;
wnodep = sbdp_get_wnodep(wnode);
bdp = &wnodep->bds[bd];
ASSERT(bdp);
portid = SG_CPUID_TO_PORTID(cpuid);
if (!SBDP_IS_CPU_PRESENT(bdp, SG_CPUID_TO_CPU_UNIT(portid))) {
cpu = portid;
}
if (SBDP_INJECT_ERROR(f, 1) || sbdp_start_cpu(cpu) != 0) {
rv = -1;
goto out;
}
if (SBDP_INJECT_ERROR(f, 2) ||
prom_serengeti_wakeupcpu(nodeid) != 0) {
rv = -1;
goto out;
}
}
sbdp_cpu_in_reset(wnode, bd, SG_CPUID_TO_CPU_UNIT(cpuid), 0);
for (i = 0; i < SG_MAX_CPUS_PER_BD; i++)
if (sbdp_is_cpu_present(wnode, bd, i) &&
sbdp_is_cpu_in_reset(wnode, bd, i) == 1) {
break;
}
if (i == SG_MAX_CPUS_PER_BD) {
sbdp_add_new_bd_info(wnode, bd);
}
out:
if (rv != 0)
sbdp_set_err(sep, ESGT_WAKEUPCPU, NULL);
return (rv);
}
int
sbdp_cpu_poweron(struct cpu *cp)
{
int cpuid;
int ntries;
pnode_t nodeid;
extern void restart_other_cpu(int);
static fn_t f = "sbdp_cpu_poweron";
SBDP_DBG_FUNC("%s\n", f);
ASSERT(MUTEX_HELD(&cpu_lock));
ntries = sbdp_cpu_ntries;
cpuid = cp->cpu_id;
nodeid = cpunodes[cpuid].nodeid;
ASSERT(nodeid != (pnode_t)0);
if (SBDP_INJECT_ERROR(f, 0) ||
prom_serengeti_wakeupcpu(nodeid) != 0) {
return (EBUSY);
}
cp->cpu_flags &= ~CPU_POWEROFF;
SBDP_DBG_CPU("%s: COLD START for cpu (%d)\n", f, cpuid);
restart_other_cpu(cpuid);
SBDP_DBG_CPU("after restarting other cpus\n");
while ((cp->cpu_thread != cp->cpu_idle_thread) && (ntries > 0)) {
DELAY(sbdp_cpu_delay);
ntries--;
}
SBDP_DBG_CPU("%s: waited %d out of %d loops for cpu %d\n",
f, sbdp_cpu_ntries - ntries, sbdp_cpu_ntries, cpuid);
return (0);
}
#define SBDP_CPU_SRAM_ADDR 0x7fff0900000ull
#define SBDP_CPU_SRAM_SIZE 0x20000ull
static const char cpyren_key[] = "COPYREN";
static uint64_t bbsram_pa;
static uint_t bbsram_size;
typedef struct {
caddr_t vaddr;
pgcnt_t npages;
uint64_t *pa;
uint_t *size;
} sbdp_cpu_sram_map_t;
int
sbdp_cpu_poweroff(struct cpu *cp)
{
processorid_t cpuid;
pnode_t nodeid;
sbdp_cpu_sram_map_t map;
static fn_t f = "sbdp_cpu_poweroff";
SBDP_DBG_FUNC("%s\n", f);
ASSERT(MUTEX_HELD(&cpu_lock));
cpuid = cp->cpu_id;
nodeid = cpunodes[cpuid].nodeid;
ASSERT(nodeid != (pnode_t)0);
*sbdp_valp = 0ull;
if (SBDP_INJECT_ERROR(f, 0) ||
cpusram_map(&map.vaddr, &map.npages) != DDI_SUCCESS) {
return (EBUSY);
}
map.pa = &bbsram_pa;
map.size = &bbsram_size;
xc_one(cpuid, sbdp_get_cpu_sram_addr, (uint64_t)&map,
(uint64_t)NULL);
cpusram_unmap(&map.vaddr, map.npages);
if (SBDP_INJECT_ERROR(f, 1) || bbsram_size == 0) {
cmn_err(CE_WARN, "cpu%d: Key \"%s\" missing from CPU SRAM TOC",
cpuid, cpyren_key);
return (EBUSY);
}
if ((bbsram_pa & MMU_PAGEOFFSET) != 0) {
cmn_err(CE_WARN, "cpu%d: CPU SRAM key \"%s\" not page aligned, "
"offset = 0x%lx", cpuid, cpyren_key,
(bbsram_pa - (uint64_t)SBDP_CPU_SRAM_ADDR));
return (EBUSY);
}
if (bbsram_size < MMU_PAGESIZE) {
cmn_err(CE_WARN, "cpu%d: CPU SRAM key \"%s\" too small, "
"size = 0x%x", cpuid, cpyren_key, bbsram_size);
return (EBUSY);
}
promsafe_pause_cpus();
mp_cpu_quiesce(cp);
if (SBDP_INJECT_ERROR(f, 2) || prom_serengeti_cpu_off(nodeid) != 0)
return (EBUSY);
xt_one_unchecked(cp->cpu_id, (xcfunc_t *)idle_stop_xcall,
(uint64_t)sbdp_cpu_shutdown_self, 0);
*sbdp_valp = 3ull;
start_cpus();
if (SBDP_INJECT_ERROR(f, 3) ||
prom_serengeti_wakeupcpu(nodeid) != 0) {
cmn_err(CE_WARN, "cpu%d: CPU failed to enter OBP idle loop.\n",
cpuid);
}
ASSERT(!(CPU_IN_SET(cpu_ready_set, cpuid)));
bbsram_pa = 0;
bbsram_size = 0;
return (0);
}
processorid_t
sbdp_get_cpuid(sbdp_handle_t *hp, dev_info_t *dip)
{
int cpuid;
char type[OBP_MAXPROPNAME];
pnode_t nodeid;
sbd_error_t *sep;
static fn_t f = "sbdp_get_cpuid";
SBDP_DBG_FUNC("%s\n", f);
nodeid = ddi_get_nodeid(dip);
if (sbdp_is_node_bad(nodeid))
return (-1);
sep = hp->h_err;
if (prom_getproplen(nodeid, "device_type") < OBP_MAXPROPNAME)
(void) prom_getprop(nodeid, "device_type", (caddr_t)type);
else {
sbdp_set_err(sep, ESGT_NO_DEV_TYPE, NULL);
return (-1);
}
if (strcmp(type, "cpu") != 0) {
sbdp_set_err(sep, ESGT_NOT_CPUTYPE, NULL);
return (-1);
}
if (prom_getprop(nodeid, "cpuid", (caddr_t)&cpuid) == -1)
if (prom_getprop(nodeid, "portid", (caddr_t)&cpuid) == -1) {
return (-1);
}
return ((processorid_t)cpuid & SG_CPU_ID_MASK);
}
int
sbdp_cpu_get_impl(sbdp_handle_t *hp, dev_info_t *dip)
{
int impl;
char type[OBP_MAXPROPNAME];
pnode_t nodeid;
sbd_error_t *sep;
static fn_t f = "sbdp_cpu_get_impl";
SBDP_DBG_FUNC("%s\n", f);
nodeid = ddi_get_nodeid(dip);
if (sbdp_is_node_bad(nodeid))
return (-1);
sep = hp->h_err;
if (prom_getproplen(nodeid, "device_type") < OBP_MAXPROPNAME)
(void) prom_getprop(nodeid, "device_type", (caddr_t)type);
else {
sbdp_set_err(sep, ESGT_NO_DEV_TYPE, NULL);
return (-1);
}
if (strcmp(type, "cpu") != 0) {
sbdp_set_err(sep, ESGT_NOT_CPUTYPE, NULL);
return (-1);
}
if (prom_getprop(nodeid, "implementation#", (caddr_t)&impl) == -1)
return (-1);
return (impl);
}
struct sbdp_prom_get_node_args {
pnode_t node;
processorid_t portid;
pnode_t result_node;
};
pnode_t
sbdp_find_nearby_cpu_by_portid(pnode_t nodeid, processorid_t portid)
{
struct sbdp_prom_get_node_args arg;
static fn_t f = "sbdp_find_nearby_cpu_by_portid";
SBDP_DBG_FUNC("%s\n", f);
arg.node = nodeid;
arg.portid = portid;
(void) prom_tree_access(sbdp_prom_get_cpu, &arg, NULL);
return (arg.result_node);
}
static int
sbdp_prom_get_cpu(void *arg, int changed)
{
int portid;
pnode_t parent, cur_node;
struct sbdp_prom_get_node_args *argp = arg;
static fn_t f = "sbdp_prom_get_cpu";
SBDP_DBG_FUNC("%s\n", f);
parent = prom_parentnode(argp->node);
for (cur_node = prom_childnode(parent); cur_node != OBP_NONODE;
cur_node = prom_nextnode(cur_node)) {
if (prom_getprop(cur_node, OBP_PORTID, (caddr_t)&portid) < 0)
continue;
if ((portid == argp->portid) && (cur_node != argp->node))
break;
}
argp->result_node = cur_node;
return (0);
}
static void
sbdp_cpu_stop_self(uint64_t pa)
{
cpu_t *cp = CPU;
int cpuid = cp->cpu_id;
tte_t tte;
volatile uint_t *src, *dst;
size_t funclen;
sbdp_shutdown_t sht;
uint_t bbsram_pfn;
uint64_t bbsram_addr;
void (*bbsram_func)(sbdp_shutdown_t *);
extern void sbdp_shutdown_asm(sbdp_shutdown_t *);
extern void sbdp_shutdown_asm_end(void);
funclen = (uintptr_t)sbdp_shutdown_asm_end -
(uintptr_t)sbdp_shutdown_asm;
ASSERT(funclen <= MMU_PAGESIZE);
ASSERT(bbsram_pa != 0);
ASSERT((bbsram_pa & MMU_PAGEOFFSET) == 0);
ASSERT(bbsram_size >= MMU_PAGESIZE);
stdphys(pa, 3);
bbsram_pfn = (uint_t)(bbsram_pa >> MMU_PAGESHIFT);
bbsram_addr = (uint64_t)sbdp_shutdown_va;
sht.estack = bbsram_addr + MMU_PAGESIZE;
sht.flushaddr = ecache_flushaddr;
tte.tte_inthi = TTE_VALID_INT | TTE_SZ_INT(TTE8K) |
TTE_PFN_INTHI(bbsram_pfn);
tte.tte_intlo = TTE_PFN_INTLO(bbsram_pfn) |
TTE_HWWR_INT | TTE_PRIV_INT | TTE_LCK_INT;
sfmmu_dtlb_ld_kva(sbdp_shutdown_va, &tte);
sfmmu_itlb_ld_kva(sbdp_shutdown_va, &tte);
for (src = (uint_t *)sbdp_shutdown_asm, dst = (uint_t *)bbsram_addr;
src < (uint_t *)sbdp_shutdown_asm_end; src++, dst++)
*dst = *src;
bbsram_func = (void (*)())bbsram_addr;
sht.size = (uint32_t)cpunodes[cpuid].ecache_size << 1;
sht.linesize = (uint32_t)cpunodes[cpuid].ecache_linesize;
sht.physaddr = pa;
cp->cpu_m.in_prom = 1;
stdphys(pa, 4);
(*bbsram_func)(&sht);
}
void
sbdp_get_cpu_sram_addr(uint64_t arg1, uint64_t arg2)
{
uint64_t *pap;
uint_t *sizep;
struct iosram_toc *tocp;
uint_t offset;
uint_t size;
sbdp_cpu_sram_map_t *map;
int i;
fn_t f = "sbdp_get_cpu_sram_addr";
SBDP_DBG_FUNC("%s\n", f);
map = (sbdp_cpu_sram_map_t *)arg1;
tocp = (struct iosram_toc *)map->vaddr;
pap = map->pa;
sizep = map->size;
for (i = 0; i < tocp->iosram_tagno; i++) {
if (strcmp(tocp->iosram_keys[i].key, cpyren_key) == 0)
break;
}
if (i == tocp->iosram_tagno) {
*pap = 0;
*sizep = 0;
return;
}
offset = tocp->iosram_keys[i].offset;
size = tocp->iosram_keys[i].size;
*pap = SBDP_CPU_SRAM_ADDR + offset;
*sizep = size;
}
static int
cpusram_map(caddr_t *vaddrp, pgcnt_t *npp)
{
uint_t pgoffset;
pgcnt_t npages;
pfn_t pfn;
uint64_t base;
caddr_t kaddr;
uint_t mapping_attr;
base = (uint64_t)SBDP_CPU_SRAM_ADDR & (~MMU_PAGEOFFSET);
pfn = mmu_btop(base);
if (pf_is_memory(pfn))
return (DDI_FAILURE);
pgoffset = (ulong_t)SBDP_CPU_SRAM_ADDR & MMU_PAGEOFFSET;
npages = mmu_btopr(SBDP_CPU_SRAM_SIZE + pgoffset);
kaddr = vmem_alloc(heap_arena, ptob(npages), VM_NOSLEEP);
if (kaddr == NULL)
return (DDI_ME_NORESOURCES);
mapping_attr = PROT_READ;
hat_devload(kas.a_hat, kaddr, ptob(npages), pfn, mapping_attr,
HAT_LOAD_LOCK);
*vaddrp = kaddr + pgoffset;
*npp = npages;
return (DDI_SUCCESS);
}
static void
cpusram_unmap(caddr_t *vaddrp, pgcnt_t npages)
{
uint_t pgoffset;
caddr_t base;
caddr_t addr = *vaddrp;
pgoffset = (ulong_t)SBDP_CPU_SRAM_ADDR & MMU_PAGEOFFSET;
base = addr - pgoffset;
hat_unload(kas.a_hat, base, ptob(npages), HAT_UNLOAD_UNLOCK);
vmem_free(heap_arena, base, ptob(npages));
*vaddrp = 0;
}
static void
sbdp_cpu_shutdown_self(void)
{
cpu_t *cp = CPU;
int cpuid = cp->cpu_id;
extern void flush_windows(void);
uint64_t pa = va_to_pa((void *)sbdp_valp);
stdphys(pa, 8);
flush_windows();
(void) spl8();
stdphys(pa, 6);
ASSERT(cp->cpu_intr_actv == 0);
ASSERT(cp->cpu_thread == cp->cpu_idle_thread ||
cp->cpu_thread == cp->cpu_startup_thread);
cp->cpu_flags = CPU_OFFLINE | CPU_QUIESCED | CPU_POWEROFF;
CPU_SIGNATURE(OS_SIG, SIGST_DETACHED, SIGSUBST_NULL, cpuid);
stdphys(pa, 7);
sbdp_cpu_stop_self(pa);
cmn_err(CE_PANIC, "sbdp_cpu_shutdown_self: CPU %d FAILED TO SHUTDOWN",
cpuid);
}
typedef struct {
int node;
int board;
int non_panther_cpus;
} sbdp_node_walk_t;
static int
sbdp_find_non_panther_cpus(dev_info_t *dip, void *node_args)
{
int impl, cpuid, portid;
int buflen;
char buf[OBP_MAXPROPNAME];
sbdp_node_walk_t *args = (sbdp_node_walk_t *)node_args;
if (ddi_getlongprop_buf(DDI_DEV_T_ANY, dip,
DDI_PROP_DONTPASS, OBP_DEVICETYPE, (caddr_t)buf,
&buflen) != DDI_PROP_SUCCESS) {
return (DDI_WALK_CONTINUE);
}
if (strcmp(buf, "cpu") != 0) {
return (DDI_WALK_CONTINUE);
}
if ((impl = ddi_getprop(DDI_DEV_T_ANY, dip,
DDI_PROP_DONTPASS, "implementation#", -1)) == -1) {
return (DDI_WALK_CONTINUE);
}
if ((cpuid = ddi_getprop(DDI_DEV_T_ANY, dip,
DDI_PROP_DONTPASS, "cpuid", -1)) == -1) {
return (DDI_WALK_CONTINUE);
}
portid = SG_CPUID_TO_PORTID(cpuid);
if (SG_PORTID_TO_BOARD_NUM(portid) != args->board ||
SG_PORTID_TO_NODEID(portid) != args->node) {
return (DDI_WALK_PRUNECHILD);
}
switch (impl) {
case CHEETAH_IMPL:
case CHEETAH_PLUS_IMPL:
case JAGUAR_IMPL:
args->non_panther_cpus++;
break;
case PANTHER_IMPL:
break;
default:
ASSERT(0);
args->non_panther_cpus++;
break;
}
SBDP_DBG_CPU("cpuid=0x%x, portid=0x%x, impl=0x%x, device_type=%s",
cpuid, portid, impl, buf);
return (DDI_WALK_CONTINUE);
}
int
sbdp_board_non_panther_cpus(int node, int board)
{
sbdp_node_walk_t arg = {0};
arg.node = node;
arg.board = board;
ddi_walk_devs(ddi_root_node(), sbdp_find_non_panther_cpus,
(void *)&arg);
return (arg.non_panther_cpus);
}