FreeBSD-5.3/sys/alpha/alpha/mp_machdep.c
/*-
* Copyright (c) 2000 Doug Rabson
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD: src/sys/alpha/alpha/mp_machdep.c,v 1.52 2004/01/07 23:00:20 jhb Exp $");
#include "opt_kstack_pages.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/ktr.h>
#include <sys/proc.h>
#include <sys/cons.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/kernel.h>
#include <sys/pcpu.h>
#include <sys/smp.h>
#include <sys/sysctl.h>
#include <sys/bus.h>
#include <sys/user.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <vm/vm_map.h>
#include <machine/atomic.h>
#include <machine/clock.h>
#include <machine/md_var.h>
#include <machine/pmap.h>
#include <machine/prom.h>
#include <machine/rpb.h>
#include <machine/smp.h>
/* Set to 1 once we're ready to let the APs out of the pen. */
static volatile int aps_ready = 0;
static struct mtx ap_boot_mtx;
u_int boot_cpu_id;
static void release_aps(void *dummy);
static int smp_cpu_enabled(struct pcs *pcsp);
extern void smp_init_secondary_glue(void);
static int smp_send_secondary_command(const char *command, int cpuid);
static int smp_start_secondary(int cpuid);
/*
* Communicate with a console running on a secondary processor.
* Return 1 on failure.
*/
static int
smp_send_secondary_command(const char *command, int cpuid)
{
u_int64_t mask = 1L << cpuid;
struct pcs *cpu = LOCATE_PCS(hwrpb, cpuid);
int i, len;
/*
* Sanity check.
*/
len = strlen(command);
if (len > sizeof(cpu->pcs_buffer.rxbuf)) {
printf("smp_send_secondary_command: command '%s' too long\n",
command);
return 0;
}
/*
* Wait for the rx bit to clear.
*/
for (i = 0; i < 100000; i++) {
if (!(hwrpb->rpb_rxrdy & mask))
break;
DELAY(10);
}
if (hwrpb->rpb_rxrdy & mask)
return 0;
/*
* Write the command into the processor's buffer.
*/
bcopy(command, cpu->pcs_buffer.rxbuf, len);
cpu->pcs_buffer.rxlen = len;
/*
* Set the bit in the rxrdy mask and let the secondary try to
* handle the command.
*/
atomic_set_64(&hwrpb->rpb_rxrdy, mask);
/*
* Wait for the rx bit to clear.
*/
for (i = 0; i < 100000; i++) {
if (!(hwrpb->rpb_rxrdy & mask))
break;
DELAY(10);
}
if (hwrpb->rpb_rxrdy & mask)
return 0;
return 1;
}
void
smp_init_secondary(void)
{
struct pcs *cpu;
/* spin until all the AP's are ready */
while (!aps_ready)
/*spin*/ ;
/*
* Record the pcpu pointer in the per-cpu system value.
*/
alpha_pal_wrval((u_int64_t) pcpup);
/* Clear userland thread pointer. */
alpha_pal_wrunique(0);
/*
* Point interrupt/exception vectors to our own.
*/
alpha_pal_wrent(XentInt, ALPHA_KENTRY_INT);
alpha_pal_wrent(XentArith, ALPHA_KENTRY_ARITH);
alpha_pal_wrent(XentMM, ALPHA_KENTRY_MM);
alpha_pal_wrent(XentIF, ALPHA_KENTRY_IF);
alpha_pal_wrent(XentUna, ALPHA_KENTRY_UNA);
alpha_pal_wrent(XentSys, ALPHA_KENTRY_SYS);
/* lower the ipl and take any pending machine check */
mc_expected = 1;
alpha_mb(); alpha_mb();
alpha_pal_wrmces(7);
(void)alpha_pal_swpipl(ALPHA_PSL_IPL_HIGH);
mc_expected = 0;
/*
* Set flags in our per-CPU slot in the HWRPB.
*/
cpu = LOCATE_PCS(hwrpb, PCPU_GET(cpuid));
cpu->pcs_flags &= ~PCS_BIP;
cpu->pcs_flags |= PCS_RC;
alpha_mb();
/*
* XXX: doesn't idleproc already have a pcb from when it was
* kthread_create'd?
*
* cache idleproc's physical address.
*/
curthread->td_md.md_pcbpaddr = (struct pcb *)PCPU_GET(idlepcbphys);
/*
* and make idleproc's trapframe pointer point to its
* stack pointer for sanity.
*/
curthread->td_frame =
(struct trapframe *)PCPU_PTR(idlepcb)->apcb_ksp;
mtx_lock_spin(&ap_boot_mtx);
smp_cpus++;
CTR1(KTR_SMP, "SMP: AP CPU #%d Launched", PCPU_GET(cpuid));
/* Build our map of 'other' CPUs. */
PCPU_SET(other_cpus, all_cpus & ~PCPU_GET(cpumask));
printf("SMP: AP CPU #%d Launched!\n", PCPU_GET(cpuid));
if (smp_cpus == mp_ncpus) {
smp_started = 1;
smp_active = 1;
}
mtx_unlock_spin(&ap_boot_mtx);
while (smp_started == 0)
; /* nothing */
binuptime(PCPU_PTR(switchtime));
PCPU_SET(switchticks, ticks);
/* ok, now grab sched_lock and enter the scheduler */
mtx_lock_spin(&sched_lock);
cpu_throw(NULL, choosethread()); /* doesn't return */
panic("scheduler returned us to %s", __func__);
}
static int
smp_start_secondary(int cpuid)
{
struct pcs *cpu = LOCATE_PCS(hwrpb, cpuid);
struct pcs *bootcpu = LOCATE_PCS(hwrpb, boot_cpu_id);
struct alpha_pcb *pcb = (struct alpha_pcb *) cpu->pcs_hwpcb;
struct pcpu *pcpu;
int i;
size_t sz;
if ((cpu->pcs_flags & PCS_PV) == 0) {
printf("smp_start_secondary: cpu %d PALcode invalid\n", cpuid);
return 0;
}
if (bootverbose)
printf("smp_start_secondary: starting cpu %d\n", cpuid);
sz = round_page((UAREA_PAGES + KSTACK_PAGES) * PAGE_SIZE);
pcpu = malloc(sz, M_TEMP, M_NOWAIT);
if (!pcpu) {
printf("smp_start_secondary: can't allocate memory\n");
return 0;
}
pcpu_init(pcpu, cpuid, sz);
/*
* Copy the idle pcb and setup the address to start executing.
* Use the pcb unique value to point the secondary at its pcpu
* structure.
*/
*pcb = pcpu->pc_idlepcb;
pcb->apcb_unique = (u_int64_t)pcpu;
hwrpb->rpb_restart = (u_int64_t) smp_init_secondary_glue;
hwrpb->rpb_restart_val = (u_int64_t) smp_init_secondary_glue;
hwrpb->rpb_checksum = hwrpb_checksum();
/*
* Tell the cpu to start with the same PALcode as us.
*/
bcopy(&bootcpu->pcs_pal_rev, &cpu->pcs_pal_rev,
sizeof cpu->pcs_pal_rev);
/*
* Set flags in cpu structure and push out write buffers to
* make sure the secondary sees it.
*/
cpu->pcs_flags |= PCS_CV|PCS_RC;
cpu->pcs_flags &= ~PCS_BIP;
alpha_mb();
/*
* Fire it up and hope for the best.
*/
if (!smp_send_secondary_command("START\r\n", cpuid)) {
printf("smp_start_secondary: can't send START command\n");
pcpu_destroy(pcpu);
free(pcpu, M_TEMP);
return 0;
}
/*
* Wait for the secondary to set the BIP flag in its structure.
*/
for (i = 0; i < 100000; i++) {
if (cpu->pcs_flags & PCS_BIP)
break;
DELAY(10);
}
if (!(cpu->pcs_flags & PCS_BIP)) {
printf("smp_start_secondary: secondary did not respond\n");
pcpu_destroy(pcpu);
free(pcpu, M_TEMP);
return 0;
}
/*
* It worked (I think).
*/
if (bootverbose)
printf("smp_start_secondary: cpu %d started\n", cpuid);
return 1;
}
/* Other stuff */
static int
smp_cpu_enabled(struct pcs *pcsp)
{
/* Is this CPU present? */
if ((pcsp->pcs_flags & PCS_PP) == 0)
return (0);
/* Is this CPU available? */
if ((pcsp->pcs_flags & PCS_PA) == 0)
/*
* The TurboLaser PCS_PA bit doesn't seem to be set
* correctly.
*/
if (hwrpb->rpb_type != ST_DEC_21000)
return (0);
/* Is this CPU's PALcode valid? */
if ((pcsp->pcs_flags & PCS_PV) == 0)
return (0);
return (1);
}
void
cpu_mp_setmaxid(void)
{
int i;
mp_maxid = 0;
for (i = 0; i < hwrpb->rpb_pcs_cnt && i < MAXCPU; i++) {
if (i == PCPU_GET(cpuid))
continue;
if (!smp_cpu_enabled(LOCATE_PCS(hwrpb, i)))
continue;
mp_maxid = i;
}
}
int
cpu_mp_probe(void)
{
int i, cpus;
/* XXX: Need to check for valid platforms here. */
boot_cpu_id = PCPU_GET(cpuid);
KASSERT(boot_cpu_id == hwrpb->rpb_primary_cpu_id,
("cpu_mp_probe() called on non-primary CPU"));
all_cpus = PCPU_GET(cpumask);
mp_ncpus = 1;
/* Make sure we have at least one secondary CPU. */
cpus = 0;
for (i = 0; i < hwrpb->rpb_pcs_cnt; i++) {
if (i == PCPU_GET(cpuid))
continue;
if (!smp_cpu_enabled(LOCATE_PCS(hwrpb, i)))
continue;
if (i > MAXCPU)
continue;
cpus++;
}
return (cpus);
}
void
cpu_mp_start(void)
{
int i;
mtx_init(&ap_boot_mtx, "ap boot", NULL, MTX_SPIN);
for (i = 0; i < hwrpb->rpb_pcs_cnt; i++) {
struct pcs *pcsp;
if (i == boot_cpu_id)
continue;
pcsp = LOCATE_PCS(hwrpb, i);
if ((pcsp->pcs_flags & PCS_PP) == 0)
continue;
if ((pcsp->pcs_flags & PCS_PA) == 0) {
if (hwrpb->rpb_type == ST_DEC_21000) {
printf("Ignoring PA bit for CPU %d.\n", i);
} else {
if (bootverbose)
printf("CPU %d not available.\n", i);
continue;
}
}
if ((pcsp->pcs_flags & PCS_PV) == 0) {
if (bootverbose)
printf("CPU %d does not have valid PALcode.\n",
i);
continue;
}
if (i > MAXCPU) {
if (bootverbose) {
printf("CPU %d not supported.", i);
printf(" Only %d CPUs supported.\n", MAXCPU);
}
continue;
}
if (resource_disabled("cpu", i)) {
printf("CPU %d disabled by loader.\n", i);
continue;
}
all_cpus |= (1 << i);
mp_ncpus++;
}
PCPU_SET(other_cpus, all_cpus & ~PCPU_GET(cpumask));
for (i = 0; i < hwrpb->rpb_pcs_cnt; i++) {
if (i == boot_cpu_id)
continue;
if (!CPU_ABSENT(i))
smp_start_secondary(i);
}
}
void
cpu_mp_announce(void)
{
}
/*
* send an IPI to a set of cpus.
*/
void
ipi_selected(u_int32_t cpus, u_int64_t ipi)
{
struct pcpu *pcpu;
CTR2(KTR_SMP, "ipi_selected: cpus: %x ipi: %lx", cpus, ipi);
alpha_mb();
while (cpus) {
int cpuid = ffs(cpus) - 1;
cpus &= ~(1 << cpuid);
pcpu = pcpu_find(cpuid);
if (pcpu) {
atomic_set_64(&pcpu->pc_pending_ipis, ipi);
alpha_mb();
CTR1(KTR_SMP, "calling alpha_pal_wripir(%d)", cpuid);
alpha_pal_wripir(cpuid);
}
}
}
/*
* send an IPI INTerrupt containing 'vector' to all CPUs, including myself
*/
void
ipi_all(u_int64_t ipi)
{
ipi_selected(all_cpus, ipi);
}
/*
* send an IPI to all CPUs EXCEPT myself
*/
void
ipi_all_but_self(u_int64_t ipi)
{
ipi_selected(PCPU_GET(other_cpus), ipi);
}
/*
* send an IPI to myself
*/
void
ipi_self(u_int64_t ipi)
{
ipi_selected(PCPU_GET(cpumask), ipi);
}
/*
* Handle an IPI sent to this processor.
*/
void
smp_handle_ipi(struct trapframe *frame)
{
u_int64_t ipis = atomic_readandclear_64(PCPU_PTR(pending_ipis));
u_int64_t ipi;
int cpumask;
cpumask = PCPU_GET(cpumask);
CTR1(KTR_SMP, "smp_handle_ipi(), ipis=%lx", ipis);
while (ipis) {
/*
* Find the lowest set bit.
*/
ipi = ipis & ~(ipis - 1);
ipis &= ~ipi;
switch (ipi) {
case IPI_INVLTLB:
CTR0(KTR_SMP, "IPI_NVLTLB");
ALPHA_TBIA();
break;
case IPI_RENDEZVOUS:
CTR0(KTR_SMP, "IPI_RENDEZVOUS");
smp_rendezvous_action();
break;
case IPI_AST:
CTR0(KTR_SMP, "IPI_AST");
break;
case IPI_STOP:
CTR0(KTR_SMP, "IPI_STOP");
atomic_set_int(&stopped_cpus, cpumask);
while ((started_cpus & cpumask) == 0)
alpha_mb();
atomic_clear_int(&started_cpus, cpumask);
atomic_clear_int(&stopped_cpus, cpumask);
break;
}
}
/*
* Dump console messages to the console. XXX - we need to handle
* requests to provide PALcode to secondaries and to start up new
* secondaries that are added to the system on the fly.
*/
if (PCPU_GET(cpuid) == boot_cpu_id) {
u_int cpuid;
u_int64_t txrdy;
#ifdef DIAGNOSTIC
struct pcs *cpu;
char buf[81];
#endif
alpha_mb();
while (hwrpb->rpb_txrdy != 0) {
cpuid = ffs(hwrpb->rpb_txrdy) - 1;
#ifdef DIAGNOSTIC
cpu = LOCATE_PCS(hwrpb, cpuid);
bcopy(&cpu->pcs_buffer.txbuf, buf,
cpu->pcs_buffer.txlen);
buf[cpu->pcs_buffer.txlen] = '\0';
printf("SMP From CPU%d: %s\n", cpuid, buf);
#endif
do {
txrdy = hwrpb->rpb_txrdy;
} while (atomic_cmpset_64(&hwrpb->rpb_txrdy, txrdy,
txrdy & ~(1 << cpuid)) == 0);
}
}
}
static void
release_aps(void *dummy __unused)
{
if (bootverbose && mp_ncpus > 1)
printf("%s: releasing secondary CPUs\n", __func__);
atomic_store_rel_int(&aps_ready, 1);
while (mp_ncpus > 1 && smp_started == 0)
; /* nothing */
}
SYSINIT(start_aps, SI_SUB_SMP, SI_ORDER_FIRST, release_aps, NULL);