diff options
| author | Ingo Molnar <mingo@elte.hu> | 2009-02-17 20:35:16 +0100 | 
|---|---|---|
| committer | Ingo Molnar <mingo@elte.hu> | 2009-02-17 20:35:47 +0100 | 
| commit | 2a05180fe2e5b414f0cb2ccfc80e6c90563e3c67 (patch) | |
| tree | 5c14afab81ee44b44ec7ff7faf08fee2c165bf50 /arch/x86/kernel/apic | |
| parent | f62bae5009c1ba596cd475cafbc83e0570a36e26 (diff) | |
| download | linux-2a05180fe2e5b414f0cb2ccfc80e6c90563e3c67.tar.bz2 | |
x86, apic: move remaining APIC drivers to arch/x86/kernel/apic/*
Move the 32-bit extended-arch APIC drivers to arch/x86/kernel/apic/
too, and rename apic_64.c to probe_64.c.
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Diffstat (limited to 'arch/x86/kernel/apic')
| -rw-r--r-- | arch/x86/kernel/apic/Makefile | 9 | ||||
| -rw-r--r-- | arch/x86/kernel/apic/bigsmp_32.c | 274 | ||||
| -rw-r--r-- | arch/x86/kernel/apic/es7000_32.c | 757 | ||||
| -rw-r--r-- | arch/x86/kernel/apic/numaq_32.c | 565 | ||||
| -rw-r--r-- | arch/x86/kernel/apic/probe_32.c | 424 | ||||
| -rw-r--r-- | arch/x86/kernel/apic/probe_64.c (renamed from arch/x86/kernel/apic/apic_64.c) | 0 | ||||
| -rw-r--r-- | arch/x86/kernel/apic/summit_32.c | 601 | 
7 files changed, 2628 insertions, 2 deletions
| diff --git a/arch/x86/kernel/apic/Makefile b/arch/x86/kernel/apic/Makefile index da20b70c4000..97f558db5c31 100644 --- a/arch/x86/kernel/apic/Makefile +++ b/arch/x86/kernel/apic/Makefile @@ -2,14 +2,19 @@  # Makefile for local APIC drivers and for the IO-APIC code  # -obj-y				:= apic.o ipi.o nmi.o +obj-y				:= apic.o probe_$(BITS).o ipi.o nmi.o  obj-$(CONFIG_X86_IO_APIC)	+= io_apic.o  obj-$(CONFIG_SMP)		+= ipi.o +obj-$  ifeq ($(CONFIG_X86_64),y) -obj-y				+= apic_64.o apic_flat_64.o +obj-y				+= apic_flat_64.o  obj-$(CONFIG_X86_X2APIC)	+= x2apic_cluster.o  obj-$(CONFIG_X86_X2APIC)	+= x2apic_phys.o  obj-$(CONFIG_X86_UV)		+= x2apic_uv_x.o  endif +obj-$(CONFIG_X86_BIGSMP)	+= bigsmp_32.o +obj-$(CONFIG_X86_NUMAQ)		+= numaq_32.o +obj-$(CONFIG_X86_ES7000)	+= es7000_32.o +obj-$(CONFIG_X86_SUMMIT)	+= summit_32.o diff --git a/arch/x86/kernel/apic/bigsmp_32.c b/arch/x86/kernel/apic/bigsmp_32.c new file mode 100644 index 000000000000..0b1093394fdf --- /dev/null +++ b/arch/x86/kernel/apic/bigsmp_32.c @@ -0,0 +1,274 @@ +/* + * APIC driver for "bigsmp" xAPIC machines with more than 8 virtual CPUs. + * + * Drives the local APIC in "clustered mode". + */ +#include <linux/threads.h> +#include <linux/cpumask.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/dmi.h> +#include <linux/smp.h> + +#include <asm/apicdef.h> +#include <asm/fixmap.h> +#include <asm/mpspec.h> +#include <asm/apic.h> +#include <asm/ipi.h> + +static inline unsigned bigsmp_get_apic_id(unsigned long x) +{ +	return (x >> 24) & 0xFF; +} + +static inline int bigsmp_apic_id_registered(void) +{ +	return 1; +} + +static inline const cpumask_t *bigsmp_target_cpus(void) +{ +#ifdef CONFIG_SMP +	return &cpu_online_map; +#else +	return &cpumask_of_cpu(0); +#endif +} + +static inline unsigned long +bigsmp_check_apicid_used(physid_mask_t bitmap, int apicid) +{ +	return 0; +} + +static inline unsigned long bigsmp_check_apicid_present(int bit) +{ +	return 1; +} + +static inline unsigned long calculate_ldr(int cpu) +{ +	unsigned long val, id; + +	val = apic_read(APIC_LDR) & ~APIC_LDR_MASK; +	id = per_cpu(x86_bios_cpu_apicid, cpu); +	val |= SET_APIC_LOGICAL_ID(id); + +	return val; +} + +/* + * Set up the logical destination ID. + * + * Intel recommends to set DFR, LDR and TPR before enabling + * an APIC.  See e.g. "AP-388 82489DX User's Manual" (Intel + * document number 292116).  So here it goes... + */ +static inline void bigsmp_init_apic_ldr(void) +{ +	unsigned long val; +	int cpu = smp_processor_id(); + +	apic_write(APIC_DFR, APIC_DFR_FLAT); +	val = calculate_ldr(cpu); +	apic_write(APIC_LDR, val); +} + +static inline void bigsmp_setup_apic_routing(void) +{ +	printk(KERN_INFO +		"Enabling APIC mode:  Physflat.  Using %d I/O APICs\n", +		nr_ioapics); +} + +static inline int bigsmp_apicid_to_node(int logical_apicid) +{ +	return apicid_2_node[hard_smp_processor_id()]; +} + +static inline int bigsmp_cpu_present_to_apicid(int mps_cpu) +{ +	if (mps_cpu < nr_cpu_ids) +		return (int) per_cpu(x86_bios_cpu_apicid, mps_cpu); + +	return BAD_APICID; +} + +static inline physid_mask_t bigsmp_apicid_to_cpu_present(int phys_apicid) +{ +	return physid_mask_of_physid(phys_apicid); +} + +/* Mapping from cpu number to logical apicid */ +static inline int bigsmp_cpu_to_logical_apicid(int cpu) +{ +	if (cpu >= nr_cpu_ids) +		return BAD_APICID; +	return cpu_physical_id(cpu); +} + +static inline physid_mask_t bigsmp_ioapic_phys_id_map(physid_mask_t phys_map) +{ +	/* For clustered we don't have a good way to do this yet - hack */ +	return physids_promote(0xFFL); +} + +static inline void bigsmp_setup_portio_remap(void) +{ +} + +static inline int bigsmp_check_phys_apicid_present(int boot_cpu_physical_apicid) +{ +	return 1; +} + +/* As we are using single CPU as destination, pick only one CPU here */ +static inline unsigned int bigsmp_cpu_mask_to_apicid(const cpumask_t *cpumask) +{ +	return bigsmp_cpu_to_logical_apicid(first_cpu(*cpumask)); +} + +static inline unsigned int +bigsmp_cpu_mask_to_apicid_and(const struct cpumask *cpumask, +			      const struct cpumask *andmask) +{ +	int cpu; + +	/* +	 * We're using fixed IRQ delivery, can only return one phys APIC ID. +	 * May as well be the first. +	 */ +	for_each_cpu_and(cpu, cpumask, andmask) { +		if (cpumask_test_cpu(cpu, cpu_online_mask)) +			break; +	} +	if (cpu < nr_cpu_ids) +		return bigsmp_cpu_to_logical_apicid(cpu); + +	return BAD_APICID; +} + +static inline int bigsmp_phys_pkg_id(int cpuid_apic, int index_msb) +{ +	return cpuid_apic >> index_msb; +} + +static inline void bigsmp_send_IPI_mask(const struct cpumask *mask, int vector) +{ +	default_send_IPI_mask_sequence_phys(mask, vector); +} + +static inline void bigsmp_send_IPI_allbutself(int vector) +{ +	default_send_IPI_mask_allbutself_phys(cpu_online_mask, vector); +} + +static inline void bigsmp_send_IPI_all(int vector) +{ +	bigsmp_send_IPI_mask(cpu_online_mask, vector); +} + +static int dmi_bigsmp; /* can be set by dmi scanners */ + +static int hp_ht_bigsmp(const struct dmi_system_id *d) +{ +	printk(KERN_NOTICE "%s detected: force use of apic=bigsmp\n", d->ident); +	dmi_bigsmp = 1; + +	return 0; +} + + +static const struct dmi_system_id bigsmp_dmi_table[] = { +	{ hp_ht_bigsmp, "HP ProLiant DL760 G2", +		{	DMI_MATCH(DMI_BIOS_VENDOR, "HP"), +			DMI_MATCH(DMI_BIOS_VERSION, "P44-"), +		} +	}, + +	{ hp_ht_bigsmp, "HP ProLiant DL740", +		{	DMI_MATCH(DMI_BIOS_VENDOR, "HP"), +			DMI_MATCH(DMI_BIOS_VERSION, "P47-"), +		} +	}, +	{ } /* NULL entry stops DMI scanning */ +}; + +static void bigsmp_vector_allocation_domain(int cpu, cpumask_t *retmask) +{ +	cpus_clear(*retmask); +	cpu_set(cpu, *retmask); +} + +static int probe_bigsmp(void) +{ +	if (def_to_bigsmp) +		dmi_bigsmp = 1; +	else +		dmi_check_system(bigsmp_dmi_table); + +	return dmi_bigsmp; +} + +struct apic apic_bigsmp = { + +	.name				= "bigsmp", +	.probe				= probe_bigsmp, +	.acpi_madt_oem_check		= NULL, +	.apic_id_registered		= bigsmp_apic_id_registered, + +	.irq_delivery_mode		= dest_Fixed, +	/* phys delivery to target CPU: */ +	.irq_dest_mode			= 0, + +	.target_cpus			= bigsmp_target_cpus, +	.disable_esr			= 1, +	.dest_logical			= 0, +	.check_apicid_used		= bigsmp_check_apicid_used, +	.check_apicid_present		= bigsmp_check_apicid_present, + +	.vector_allocation_domain	= bigsmp_vector_allocation_domain, +	.init_apic_ldr			= bigsmp_init_apic_ldr, + +	.ioapic_phys_id_map		= bigsmp_ioapic_phys_id_map, +	.setup_apic_routing		= bigsmp_setup_apic_routing, +	.multi_timer_check		= NULL, +	.apicid_to_node			= bigsmp_apicid_to_node, +	.cpu_to_logical_apicid		= bigsmp_cpu_to_logical_apicid, +	.cpu_present_to_apicid		= bigsmp_cpu_present_to_apicid, +	.apicid_to_cpu_present		= bigsmp_apicid_to_cpu_present, +	.setup_portio_remap		= NULL, +	.check_phys_apicid_present	= bigsmp_check_phys_apicid_present, +	.enable_apic_mode		= NULL, +	.phys_pkg_id			= bigsmp_phys_pkg_id, +	.mps_oem_check			= NULL, + +	.get_apic_id			= bigsmp_get_apic_id, +	.set_apic_id			= NULL, +	.apic_id_mask			= 0xFF << 24, + +	.cpu_mask_to_apicid		= bigsmp_cpu_mask_to_apicid, +	.cpu_mask_to_apicid_and		= bigsmp_cpu_mask_to_apicid_and, + +	.send_IPI_mask			= bigsmp_send_IPI_mask, +	.send_IPI_mask_allbutself	= NULL, +	.send_IPI_allbutself		= bigsmp_send_IPI_allbutself, +	.send_IPI_all			= bigsmp_send_IPI_all, +	.send_IPI_self			= default_send_IPI_self, + +	.wakeup_cpu			= NULL, +	.trampoline_phys_low		= DEFAULT_TRAMPOLINE_PHYS_LOW, +	.trampoline_phys_high		= DEFAULT_TRAMPOLINE_PHYS_HIGH, + +	.wait_for_init_deassert		= default_wait_for_init_deassert, + +	.smp_callin_clear_local_apic	= NULL, +	.inquire_remote_apic		= default_inquire_remote_apic, + +	.read				= native_apic_mem_read, +	.write				= native_apic_mem_write, +	.icr_read			= native_apic_icr_read, +	.icr_write			= native_apic_icr_write, +	.wait_icr_idle			= native_apic_wait_icr_idle, +	.safe_wait_icr_idle		= native_safe_apic_wait_icr_idle, +}; diff --git a/arch/x86/kernel/apic/es7000_32.c b/arch/x86/kernel/apic/es7000_32.c new file mode 100644 index 000000000000..320f2d2e4e54 --- /dev/null +++ b/arch/x86/kernel/apic/es7000_32.c @@ -0,0 +1,757 @@ +/* + * Written by: Garry Forsgren, Unisys Corporation + *             Natalie Protasevich, Unisys Corporation + * + * This file contains the code to configure and interface + * with Unisys ES7000 series hardware system manager. + * + * Copyright (c) 2003 Unisys Corporation. + * Copyright (C) 2009, Red Hat, Inc., Ingo Molnar + * + *   All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston MA 02111-1307, USA. + * + * Contact information: Unisys Corporation, Township Line & Union Meeting + * Roads-A, Unisys Way, Blue Bell, Pennsylvania, 19424, or: + * + * http://www.unisys.com + */ +#include <linux/notifier.h> +#include <linux/spinlock.h> +#include <linux/cpumask.h> +#include <linux/threads.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/reboot.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/acpi.h> +#include <linux/init.h> +#include <linux/nmi.h> +#include <linux/smp.h> +#include <linux/io.h> + +#include <asm/apicdef.h> +#include <asm/atomic.h> +#include <asm/fixmap.h> +#include <asm/mpspec.h> +#include <asm/setup.h> +#include <asm/apic.h> +#include <asm/ipi.h> + +/* + * ES7000 chipsets + */ + +#define NON_UNISYS			0 +#define ES7000_CLASSIC			1 +#define ES7000_ZORRO			2 + +#define	MIP_REG				1 +#define	MIP_PSAI_REG			4 + +#define	MIP_BUSY			1 +#define	MIP_SPIN			0xf0000 +#define	MIP_VALID			0x0100000000000000ULL +#define	MIP_SW_APIC			0x1020b + +#define	MIP_PORT(val)			((val >> 32) & 0xffff) + +#define	MIP_RD_LO(val)			(val & 0xffffffff) + +struct mip_reg { +	unsigned long long		off_0x00; +	unsigned long long		off_0x08; +	unsigned long long		off_0x10; +	unsigned long long		off_0x18; +	unsigned long long		off_0x20; +	unsigned long long		off_0x28; +	unsigned long long		off_0x30; +	unsigned long long		off_0x38; +}; + +struct mip_reg_info { +	unsigned long long		mip_info; +	unsigned long long		delivery_info; +	unsigned long long		host_reg; +	unsigned long long		mip_reg; +}; + +struct psai { +	unsigned long long		entry_type; +	unsigned long long		addr; +	unsigned long long		bep_addr; +}; + +#ifdef CONFIG_ACPI + +struct es7000_oem_table { +	struct acpi_table_header	Header; +	u32				OEMTableAddr; +	u32				OEMTableSize; +}; + +static unsigned long			oem_addrX; +static unsigned long			oem_size; + +#endif + +/* + * ES7000 Globals + */ + +static volatile unsigned long		*psai; +static struct mip_reg			*mip_reg; +static struct mip_reg			*host_reg; +static int 				mip_port; +static unsigned long			mip_addr; +static unsigned long			host_addr; + +int					es7000_plat; + +/* + * GSI override for ES7000 platforms. + */ + +static unsigned int			base; + +static int +es7000_rename_gsi(int ioapic, int gsi) +{ +	if (es7000_plat == ES7000_ZORRO) +		return gsi; + +	if (!base) { +		int i; +		for (i = 0; i < nr_ioapics; i++) +			base += nr_ioapic_registers[i]; +	} + +	if (!ioapic && (gsi < 16)) +		gsi += base; + +	return gsi; +} + +static int wakeup_secondary_cpu_via_mip(int cpu, unsigned long eip) +{ +	unsigned long vect = 0, psaival = 0; + +	if (psai == NULL) +		return -1; + +	vect = ((unsigned long)__pa(eip)/0x1000) << 16; +	psaival = (0x1000000 | vect | cpu); + +	while (*psai & 0x1000000) +		; + +	*psai = psaival; + +	return 0; +} + +static int __init es7000_update_apic(void) +{ +	apic->wakeup_cpu = wakeup_secondary_cpu_via_mip; + +	/* MPENTIUMIII */ +	if (boot_cpu_data.x86 == 6 && +	    (boot_cpu_data.x86_model >= 7 || boot_cpu_data.x86_model <= 11)) { +		es7000_update_apic_to_cluster(); +		apic->wait_for_init_deassert = NULL; +		apic->wakeup_cpu = wakeup_secondary_cpu_via_mip; +	} + +	return 0; +} + +static void __init setup_unisys(void) +{ +	/* +	 * Determine the generation of the ES7000 currently running. +	 * +	 * es7000_plat = 1 if the machine is a 5xx ES7000 box +	 * es7000_plat = 2 if the machine is a x86_64 ES7000 box +	 * +	 */ +	if (!(boot_cpu_data.x86 <= 15 && boot_cpu_data.x86_model <= 2)) +		es7000_plat = ES7000_ZORRO; +	else +		es7000_plat = ES7000_CLASSIC; +	ioapic_renumber_irq = es7000_rename_gsi; + +	x86_quirks->update_apic = es7000_update_apic; +} + +/* + * Parse the OEM Table: + */ +static int __init parse_unisys_oem(char *oemptr) +{ +	int			i; +	int 			success = 0; +	unsigned char		type, size; +	unsigned long		val; +	char			*tp = NULL; +	struct psai		*psaip = NULL; +	struct mip_reg_info 	*mi; +	struct mip_reg		*host, *mip; + +	tp = oemptr; + +	tp += 8; + +	for (i = 0; i <= 6; i++) { +		type = *tp++; +		size = *tp++; +		tp -= 2; +		switch (type) { +		case MIP_REG: +			mi = (struct mip_reg_info *)tp; +			val = MIP_RD_LO(mi->host_reg); +			host_addr = val; +			host = (struct mip_reg *)val; +			host_reg = __va(host); +			val = MIP_RD_LO(mi->mip_reg); +			mip_port = MIP_PORT(mi->mip_info); +			mip_addr = val; +			mip = (struct mip_reg *)val; +			mip_reg = __va(mip); +			pr_debug("es7000_mipcfg: host_reg = 0x%lx \n", +				 (unsigned long)host_reg); +			pr_debug("es7000_mipcfg: mip_reg = 0x%lx \n", +				 (unsigned long)mip_reg); +			success++; +			break; +		case MIP_PSAI_REG: +			psaip = (struct psai *)tp; +			if (tp != NULL) { +				if (psaip->addr) +					psai = __va(psaip->addr); +				else +					psai = NULL; +				success++; +			} +			break; +		default: +			break; +		} +		tp += size; +	} + +	if (success < 2) +		es7000_plat = NON_UNISYS; +	else +		setup_unisys(); + +	return es7000_plat; +} + +#ifdef CONFIG_ACPI +static int __init find_unisys_acpi_oem_table(unsigned long *oem_addr) +{ +	struct acpi_table_header *header = NULL; +	struct es7000_oem_table *table; +	acpi_size tbl_size; +	acpi_status ret; +	int i = 0; + +	for (;;) { +		ret = acpi_get_table_with_size("OEM1", i++, &header, &tbl_size); +		if (!ACPI_SUCCESS(ret)) +			return -1; + +		if (!memcmp((char *) &header->oem_id, "UNISYS", 6)) +			break; + +		early_acpi_os_unmap_memory(header, tbl_size); +	} + +	table = (void *)header; + +	oem_addrX	= table->OEMTableAddr; +	oem_size	= table->OEMTableSize; + +	early_acpi_os_unmap_memory(header, tbl_size); + +	*oem_addr	= (unsigned long)__acpi_map_table(oem_addrX, oem_size); + +	return 0; +} + +static void __init unmap_unisys_acpi_oem_table(unsigned long oem_addr) +{ +	if (!oem_addr) +		return; + +	__acpi_unmap_table((char *)oem_addr, oem_size); +} + +static int es7000_check_dsdt(void) +{ +	struct acpi_table_header header; + +	if (ACPI_SUCCESS(acpi_get_table_header(ACPI_SIG_DSDT, 0, &header)) && +	    !strncmp(header.oem_id, "UNISYS", 6)) +		return 1; +	return 0; +} + +/* Hook from generic ACPI tables.c */ +static int __init es7000_acpi_madt_oem_check(char *oem_id, char *oem_table_id) +{ +	unsigned long oem_addr = 0; +	int check_dsdt; +	int ret = 0; + +	/* check dsdt at first to avoid clear fix_map for oem_addr */ +	check_dsdt = es7000_check_dsdt(); + +	if (!find_unisys_acpi_oem_table(&oem_addr)) { +		if (check_dsdt) { +			ret = parse_unisys_oem((char *)oem_addr); +		} else { +			setup_unisys(); +			ret = 1; +		} +		/* +		 * we need to unmap it +		 */ +		unmap_unisys_acpi_oem_table(oem_addr); +	} +	return ret; +} +#else /* !CONFIG_ACPI: */ +static int __init es7000_acpi_madt_oem_check(char *oem_id, char *oem_table_id) +{ +	return 0; +} +#endif /* !CONFIG_ACPI */ + +static void es7000_spin(int n) +{ +	int i = 0; + +	while (i++ < n) +		rep_nop(); +} + +static int __init +es7000_mip_write(struct mip_reg *mip_reg) +{ +	int status = 0; +	int spin; + +	spin = MIP_SPIN; +	while ((host_reg->off_0x38 & MIP_VALID) != 0) { +		if (--spin <= 0) { +			WARN(1,	"Timeout waiting for Host Valid Flag\n"); +			return -1; +		} +		es7000_spin(MIP_SPIN); +	} + +	memcpy(host_reg, mip_reg, sizeof(struct mip_reg)); +	outb(1, mip_port); + +	spin = MIP_SPIN; + +	while ((mip_reg->off_0x38 & MIP_VALID) == 0) { +		if (--spin <= 0) { +			WARN(1,	"Timeout waiting for MIP Valid Flag\n"); +			return -1; +		} +		es7000_spin(MIP_SPIN); +	} + +	status = (mip_reg->off_0x00 & 0xffff0000000000ULL) >> 48; +	mip_reg->off_0x38 &= ~MIP_VALID; + +	return status; +} + +static void __init es7000_enable_apic_mode(void) +{ +	struct mip_reg es7000_mip_reg; +	int mip_status; + +	if (!es7000_plat) +		return; + +	printk(KERN_INFO "ES7000: Enabling APIC mode.\n"); +	memset(&es7000_mip_reg, 0, sizeof(struct mip_reg)); +	es7000_mip_reg.off_0x00 = MIP_SW_APIC; +	es7000_mip_reg.off_0x38 = MIP_VALID; + +	while ((mip_status = es7000_mip_write(&es7000_mip_reg)) != 0) +		WARN(1, "Command failed, status = %x\n", mip_status); +} + +static void es7000_vector_allocation_domain(int cpu, cpumask_t *retmask) +{ +	/* Careful. Some cpus do not strictly honor the set of cpus +	 * specified in the interrupt destination when using lowest +	 * priority interrupt delivery mode. +	 * +	 * In particular there was a hyperthreading cpu observed to +	 * deliver interrupts to the wrong hyperthread when only one +	 * hyperthread was specified in the interrupt desitination. +	 */ +	*retmask = (cpumask_t){ { [0] = APIC_ALL_CPUS, } }; +} + + +static void es7000_wait_for_init_deassert(atomic_t *deassert) +{ +#ifndef CONFIG_ES7000_CLUSTERED_APIC +	while (!atomic_read(deassert)) +		cpu_relax(); +#endif +	return; +} + +static unsigned int es7000_get_apic_id(unsigned long x) +{ +	return (x >> 24) & 0xFF; +} + +static void es7000_send_IPI_mask(const struct cpumask *mask, int vector) +{ +	default_send_IPI_mask_sequence_phys(mask, vector); +} + +static void es7000_send_IPI_allbutself(int vector) +{ +	default_send_IPI_mask_allbutself_phys(cpu_online_mask, vector); +} + +static void es7000_send_IPI_all(int vector) +{ +	es7000_send_IPI_mask(cpu_online_mask, vector); +} + +static int es7000_apic_id_registered(void) +{ +	return 1; +} + +static const cpumask_t *target_cpus_cluster(void) +{ +	return &CPU_MASK_ALL; +} + +static const cpumask_t *es7000_target_cpus(void) +{ +	return &cpumask_of_cpu(smp_processor_id()); +} + +static unsigned long +es7000_check_apicid_used(physid_mask_t bitmap, int apicid) +{ +	return 0; +} +static unsigned long es7000_check_apicid_present(int bit) +{ +	return physid_isset(bit, phys_cpu_present_map); +} + +static unsigned long calculate_ldr(int cpu) +{ +	unsigned long id = per_cpu(x86_bios_cpu_apicid, cpu); + +	return SET_APIC_LOGICAL_ID(id); +} + +/* + * Set up the logical destination ID. + * + * Intel recommends to set DFR, LdR and TPR before enabling + * an APIC.  See e.g. "AP-388 82489DX User's Manual" (Intel + * document number 292116).  So here it goes... + */ +static void es7000_init_apic_ldr_cluster(void) +{ +	unsigned long val; +	int cpu = smp_processor_id(); + +	apic_write(APIC_DFR, APIC_DFR_CLUSTER); +	val = calculate_ldr(cpu); +	apic_write(APIC_LDR, val); +} + +static void es7000_init_apic_ldr(void) +{ +	unsigned long val; +	int cpu = smp_processor_id(); + +	apic_write(APIC_DFR, APIC_DFR_FLAT); +	val = calculate_ldr(cpu); +	apic_write(APIC_LDR, val); +} + +static void es7000_setup_apic_routing(void) +{ +	int apic = per_cpu(x86_bios_cpu_apicid, smp_processor_id()); + +	printk(KERN_INFO +	  "Enabling APIC mode:  %s. Using %d I/O APICs, target cpus %lx\n", +		(apic_version[apic] == 0x14) ? +			"Physical Cluster" : "Logical Cluster", +		nr_ioapics, cpus_addr(*es7000_target_cpus())[0]); +} + +static int es7000_apicid_to_node(int logical_apicid) +{ +	return 0; +} + + +static int es7000_cpu_present_to_apicid(int mps_cpu) +{ +	if (!mps_cpu) +		return boot_cpu_physical_apicid; +	else if (mps_cpu < nr_cpu_ids) +		return per_cpu(x86_bios_cpu_apicid, mps_cpu); +	else +		return BAD_APICID; +} + +static int cpu_id; + +static physid_mask_t es7000_apicid_to_cpu_present(int phys_apicid) +{ +	physid_mask_t mask; + +	mask = physid_mask_of_physid(cpu_id); +	++cpu_id; + +	return mask; +} + +/* Mapping from cpu number to logical apicid */ +static int es7000_cpu_to_logical_apicid(int cpu) +{ +#ifdef CONFIG_SMP +	if (cpu >= nr_cpu_ids) +		return BAD_APICID; +	return cpu_2_logical_apicid[cpu]; +#else +	return logical_smp_processor_id(); +#endif +} + +static physid_mask_t es7000_ioapic_phys_id_map(physid_mask_t phys_map) +{ +	/* For clustered we don't have a good way to do this yet - hack */ +	return physids_promote(0xff); +} + +static int es7000_check_phys_apicid_present(int cpu_physical_apicid) +{ +	boot_cpu_physical_apicid = read_apic_id(); +	return 1; +} + +static unsigned int +es7000_cpu_mask_to_apicid_cluster(const struct cpumask *cpumask) +{ +	int cpus_found = 0; +	int num_bits_set; +	int apicid; +	int cpu; + +	num_bits_set = cpumask_weight(cpumask); +	/* Return id to all */ +	if (num_bits_set == nr_cpu_ids) +		return 0xFF; +	/* +	 * The cpus in the mask must all be on the apic cluster.  If are not +	 * on the same apicid cluster return default value of target_cpus(): +	 */ +	cpu = cpumask_first(cpumask); +	apicid = es7000_cpu_to_logical_apicid(cpu); + +	while (cpus_found < num_bits_set) { +		if (cpumask_test_cpu(cpu, cpumask)) { +			int new_apicid = es7000_cpu_to_logical_apicid(cpu); + +			if (APIC_CLUSTER(apicid) != APIC_CLUSTER(new_apicid)) { +				WARN(1, "Not a valid mask!"); + +				return 0xFF; +			} +			apicid = new_apicid; +			cpus_found++; +		} +		cpu++; +	} +	return apicid; +} + +static unsigned int es7000_cpu_mask_to_apicid(const cpumask_t *cpumask) +{ +	int cpus_found = 0; +	int num_bits_set; +	int apicid; +	int cpu; + +	num_bits_set = cpus_weight(*cpumask); +	/* Return id to all */ +	if (num_bits_set == nr_cpu_ids) +		return es7000_cpu_to_logical_apicid(0); +	/* +	 * The cpus in the mask must all be on the apic cluster.  If are not +	 * on the same apicid cluster return default value of target_cpus(): +	 */ +	cpu = first_cpu(*cpumask); +	apicid = es7000_cpu_to_logical_apicid(cpu); +	while (cpus_found < num_bits_set) { +		if (cpu_isset(cpu, *cpumask)) { +			int new_apicid = es7000_cpu_to_logical_apicid(cpu); + +			if (APIC_CLUSTER(apicid) != APIC_CLUSTER(new_apicid)) { +				printk("%s: Not a valid mask!\n", __func__); + +				return es7000_cpu_to_logical_apicid(0); +			} +			apicid = new_apicid; +			cpus_found++; +		} +		cpu++; +	} +	return apicid; +} + +static unsigned int +es7000_cpu_mask_to_apicid_and(const struct cpumask *inmask, +			      const struct cpumask *andmask) +{ +	int apicid = es7000_cpu_to_logical_apicid(0); +	cpumask_var_t cpumask; + +	if (!alloc_cpumask_var(&cpumask, GFP_ATOMIC)) +		return apicid; + +	cpumask_and(cpumask, inmask, andmask); +	cpumask_and(cpumask, cpumask, cpu_online_mask); +	apicid = es7000_cpu_mask_to_apicid(cpumask); + +	free_cpumask_var(cpumask); + +	return apicid; +} + +static int es7000_phys_pkg_id(int cpuid_apic, int index_msb) +{ +	return cpuid_apic >> index_msb; +} + +void __init es7000_update_apic_to_cluster(void) +{ +	apic->target_cpus = target_cpus_cluster; +	apic->irq_delivery_mode = dest_LowestPrio; +	/* logical delivery broadcast to all procs: */ +	apic->irq_dest_mode = 1; + +	apic->init_apic_ldr = es7000_init_apic_ldr_cluster; + +	apic->cpu_mask_to_apicid = es7000_cpu_mask_to_apicid_cluster; +} + +static int probe_es7000(void) +{ +	/* probed later in mptable/ACPI hooks */ +	return 0; +} + +static __init int +es7000_mps_oem_check(struct mpc_table *mpc, char *oem, char *productid) +{ +	if (mpc->oemptr) { +		struct mpc_oemtable *oem_table = +			(struct mpc_oemtable *)mpc->oemptr; + +		if (!strncmp(oem, "UNISYS", 6)) +			return parse_unisys_oem((char *)oem_table); +	} +	return 0; +} + + +struct apic apic_es7000 = { + +	.name				= "es7000", +	.probe				= probe_es7000, +	.acpi_madt_oem_check		= es7000_acpi_madt_oem_check, +	.apic_id_registered		= es7000_apic_id_registered, + +	.irq_delivery_mode		= dest_Fixed, +	/* phys delivery to target CPUs: */ +	.irq_dest_mode			= 0, + +	.target_cpus			= es7000_target_cpus, +	.disable_esr			= 1, +	.dest_logical			= 0, +	.check_apicid_used		= es7000_check_apicid_used, +	.check_apicid_present		= es7000_check_apicid_present, + +	.vector_allocation_domain	= es7000_vector_allocation_domain, +	.init_apic_ldr			= es7000_init_apic_ldr, + +	.ioapic_phys_id_map		= es7000_ioapic_phys_id_map, +	.setup_apic_routing		= es7000_setup_apic_routing, +	.multi_timer_check		= NULL, +	.apicid_to_node			= es7000_apicid_to_node, +	.cpu_to_logical_apicid		= es7000_cpu_to_logical_apicid, +	.cpu_present_to_apicid		= es7000_cpu_present_to_apicid, +	.apicid_to_cpu_present		= es7000_apicid_to_cpu_present, +	.setup_portio_remap		= NULL, +	.check_phys_apicid_present	= es7000_check_phys_apicid_present, +	.enable_apic_mode		= es7000_enable_apic_mode, +	.phys_pkg_id			= es7000_phys_pkg_id, +	.mps_oem_check			= es7000_mps_oem_check, + +	.get_apic_id			= es7000_get_apic_id, +	.set_apic_id			= NULL, +	.apic_id_mask			= 0xFF << 24, + +	.cpu_mask_to_apicid		= es7000_cpu_mask_to_apicid, +	.cpu_mask_to_apicid_and		= es7000_cpu_mask_to_apicid_and, + +	.send_IPI_mask			= es7000_send_IPI_mask, +	.send_IPI_mask_allbutself	= NULL, +	.send_IPI_allbutself		= es7000_send_IPI_allbutself, +	.send_IPI_all			= es7000_send_IPI_all, +	.send_IPI_self			= default_send_IPI_self, + +	.wakeup_cpu			= NULL, + +	.trampoline_phys_low		= 0x467, +	.trampoline_phys_high		= 0x469, + +	.wait_for_init_deassert		= es7000_wait_for_init_deassert, + +	/* Nothing to do for most platforms, since cleared by the INIT cycle: */ +	.smp_callin_clear_local_apic	= NULL, +	.inquire_remote_apic		= default_inquire_remote_apic, + +	.read				= native_apic_mem_read, +	.write				= native_apic_mem_write, +	.icr_read			= native_apic_icr_read, +	.icr_write			= native_apic_icr_write, +	.wait_icr_idle			= native_apic_wait_icr_idle, +	.safe_wait_icr_idle		= native_safe_apic_wait_icr_idle, +}; diff --git a/arch/x86/kernel/apic/numaq_32.c b/arch/x86/kernel/apic/numaq_32.c new file mode 100644 index 000000000000..d9d6d61eed82 --- /dev/null +++ b/arch/x86/kernel/apic/numaq_32.c @@ -0,0 +1,565 @@ +/* + * Written by: Patricia Gaughen, IBM Corporation + * + * Copyright (C) 2002, IBM Corp. + * Copyright (C) 2009, Red Hat, Inc., Ingo Molnar + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT.  See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Send feedback to <gone@us.ibm.com> + */ +#include <linux/nodemask.h> +#include <linux/topology.h> +#include <linux/bootmem.h> +#include <linux/threads.h> +#include <linux/cpumask.h> +#include <linux/kernel.h> +#include <linux/mmzone.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/numa.h> +#include <linux/smp.h> +#include <linux/io.h> +#include <linux/mm.h> + +#include <asm/processor.h> +#include <asm/fixmap.h> +#include <asm/mpspec.h> +#include <asm/numaq.h> +#include <asm/setup.h> +#include <asm/apic.h> +#include <asm/e820.h> +#include <asm/ipi.h> + +#define	MB_TO_PAGES(addr)		((addr) << (20 - PAGE_SHIFT)) + +int found_numaq; + +/* + * Have to match translation table entries to main table entries by counter + * hence the mpc_record variable .... can't see a less disgusting way of + * doing this .... + */ +struct mpc_trans { +	unsigned char			mpc_type; +	unsigned char			trans_len; +	unsigned char			trans_type; +	unsigned char			trans_quad; +	unsigned char			trans_global; +	unsigned char			trans_local; +	unsigned short			trans_reserved; +}; + +/* x86_quirks member */ +static int				mpc_record; + +static __cpuinitdata struct mpc_trans	*translation_table[MAX_MPC_ENTRY]; + +int					mp_bus_id_to_node[MAX_MP_BUSSES]; +int					mp_bus_id_to_local[MAX_MP_BUSSES]; +int					quad_local_to_mp_bus_id[NR_CPUS/4][4]; + + +static inline void numaq_register_node(int node, struct sys_cfg_data *scd) +{ +	struct eachquadmem *eq = scd->eq + node; + +	node_set_online(node); + +	/* Convert to pages */ +	node_start_pfn[node] = +		 MB_TO_PAGES(eq->hi_shrd_mem_start - eq->priv_mem_size); + +	node_end_pfn[node] = +		 MB_TO_PAGES(eq->hi_shrd_mem_start + eq->hi_shrd_mem_size); + +	e820_register_active_regions(node, node_start_pfn[node], +						node_end_pfn[node]); + +	memory_present(node, node_start_pfn[node], node_end_pfn[node]); + +	node_remap_size[node] = node_memmap_size_bytes(node, +					node_start_pfn[node], +					node_end_pfn[node]); +} + +/* + * Function: smp_dump_qct() + * + * Description: gets memory layout from the quad config table.  This + * function also updates node_online_map with the nodes (quads) present. + */ +static void __init smp_dump_qct(void) +{ +	struct sys_cfg_data *scd; +	int node; + +	scd = (void *)__va(SYS_CFG_DATA_PRIV_ADDR); + +	nodes_clear(node_online_map); +	for_each_node(node) { +		if (scd->quads_present31_0 & (1 << node)) +			numaq_register_node(node, scd); +	} +} + +void __cpuinit numaq_tsc_disable(void) +{ +	if (!found_numaq) +		return; + +	if (num_online_nodes() > 1) { +		printk(KERN_DEBUG "NUMAQ: disabling TSC\n"); +		setup_clear_cpu_cap(X86_FEATURE_TSC); +	} +} + +static int __init numaq_pre_time_init(void) +{ +	numaq_tsc_disable(); +	return 0; +} + +static inline int generate_logical_apicid(int quad, int phys_apicid) +{ +	return (quad << 4) + (phys_apicid ? phys_apicid << 1 : 1); +} + +/* x86_quirks member */ +static int mpc_apic_id(struct mpc_cpu *m) +{ +	int quad = translation_table[mpc_record]->trans_quad; +	int logical_apicid = generate_logical_apicid(quad, m->apicid); + +	printk(KERN_DEBUG +		"Processor #%d %u:%u APIC version %d (quad %d, apic %d)\n", +		 m->apicid, (m->cpufeature & CPU_FAMILY_MASK) >> 8, +		(m->cpufeature & CPU_MODEL_MASK) >> 4, +		 m->apicver, quad, logical_apicid); + +	return logical_apicid; +} + +/* x86_quirks member */ +static void mpc_oem_bus_info(struct mpc_bus *m, char *name) +{ +	int quad = translation_table[mpc_record]->trans_quad; +	int local = translation_table[mpc_record]->trans_local; + +	mp_bus_id_to_node[m->busid] = quad; +	mp_bus_id_to_local[m->busid] = local; + +	printk(KERN_INFO "Bus #%d is %s (node %d)\n", m->busid, name, quad); +} + +/* x86_quirks member */ +static void mpc_oem_pci_bus(struct mpc_bus *m) +{ +	int quad = translation_table[mpc_record]->trans_quad; +	int local = translation_table[mpc_record]->trans_local; + +	quad_local_to_mp_bus_id[quad][local] = m->busid; +} + +static void __init MP_translation_info(struct mpc_trans *m) +{ +	printk(KERN_INFO +	    "Translation: record %d, type %d, quad %d, global %d, local %d\n", +	       mpc_record, m->trans_type, m->trans_quad, m->trans_global, +	       m->trans_local); + +	if (mpc_record >= MAX_MPC_ENTRY) +		printk(KERN_ERR "MAX_MPC_ENTRY exceeded!\n"); +	else +		translation_table[mpc_record] = m; /* stash this for later */ + +	if (m->trans_quad < MAX_NUMNODES && !node_online(m->trans_quad)) +		node_set_online(m->trans_quad); +} + +static int __init mpf_checksum(unsigned char *mp, int len) +{ +	int sum = 0; + +	while (len--) +		sum += *mp++; + +	return sum & 0xFF; +} + +/* + * Read/parse the MPC oem tables + */ +static void __init + smp_read_mpc_oem(struct mpc_oemtable *oemtable, unsigned short oemsize) +{ +	int count = sizeof(*oemtable);	/* the header size */ +	unsigned char *oemptr = ((unsigned char *)oemtable) + count; + +	mpc_record = 0; +	printk(KERN_INFO +		"Found an OEM MPC table at %8p - parsing it ... \n", oemtable); + +	if (memcmp(oemtable->signature, MPC_OEM_SIGNATURE, 4)) { +		printk(KERN_WARNING +		       "SMP mpc oemtable: bad signature [%c%c%c%c]!\n", +		       oemtable->signature[0], oemtable->signature[1], +		       oemtable->signature[2], oemtable->signature[3]); +		return; +	} + +	if (mpf_checksum((unsigned char *)oemtable, oemtable->length)) { +		printk(KERN_WARNING "SMP oem mptable: checksum error!\n"); +		return; +	} + +	while (count < oemtable->length) { +		switch (*oemptr) { +		case MP_TRANSLATION: +			{ +				struct mpc_trans *m = (void *)oemptr; + +				MP_translation_info(m); +				oemptr += sizeof(*m); +				count += sizeof(*m); +				++mpc_record; +				break; +			} +		default: +			printk(KERN_WARNING +			       "Unrecognised OEM table entry type! - %d\n", +			       (int)*oemptr); +			return; +		} +	} +} + +static int __init numaq_setup_ioapic_ids(void) +{ +	/* so can skip it */ +	return 1; +} + +static int __init numaq_update_apic(void) +{ +	apic->wakeup_cpu = wakeup_secondary_cpu_via_nmi; + +	return 0; +} + +static struct x86_quirks numaq_x86_quirks __initdata = { +	.arch_pre_time_init		= numaq_pre_time_init, +	.arch_time_init			= NULL, +	.arch_pre_intr_init		= NULL, +	.arch_memory_setup		= NULL, +	.arch_intr_init			= NULL, +	.arch_trap_init			= NULL, +	.mach_get_smp_config		= NULL, +	.mach_find_smp_config		= NULL, +	.mpc_record			= &mpc_record, +	.mpc_apic_id			= mpc_apic_id, +	.mpc_oem_bus_info		= mpc_oem_bus_info, +	.mpc_oem_pci_bus		= mpc_oem_pci_bus, +	.smp_read_mpc_oem		= smp_read_mpc_oem, +	.setup_ioapic_ids		= numaq_setup_ioapic_ids, +	.update_apic			= numaq_update_apic, +}; + +static __init void early_check_numaq(void) +{ +	/* +	 * Find possible boot-time SMP configuration: +	 */ +	early_find_smp_config(); + +	/* +	 * get boot-time SMP configuration: +	 */ +	if (smp_found_config) +		early_get_smp_config(); + +	if (found_numaq) +		x86_quirks = &numaq_x86_quirks; +} + +int __init get_memcfg_numaq(void) +{ +	early_check_numaq(); +	if (!found_numaq) +		return 0; +	smp_dump_qct(); + +	return 1; +} + +#define NUMAQ_APIC_DFR_VALUE	(APIC_DFR_CLUSTER) + +static inline unsigned int numaq_get_apic_id(unsigned long x) +{ +	return (x >> 24) & 0x0F; +} + +static inline void numaq_send_IPI_mask(const struct cpumask *mask, int vector) +{ +	default_send_IPI_mask_sequence_logical(mask, vector); +} + +static inline void numaq_send_IPI_allbutself(int vector) +{ +	default_send_IPI_mask_allbutself_logical(cpu_online_mask, vector); +} + +static inline void numaq_send_IPI_all(int vector) +{ +	numaq_send_IPI_mask(cpu_online_mask, vector); +} + +#define NUMAQ_TRAMPOLINE_PHYS_LOW	(0x8) +#define NUMAQ_TRAMPOLINE_PHYS_HIGH	(0xa) + +/* + * Because we use NMIs rather than the INIT-STARTUP sequence to + * bootstrap the CPUs, the APIC may be in a weird state. Kick it: + */ +static inline void numaq_smp_callin_clear_local_apic(void) +{ +	clear_local_APIC(); +} + +static inline const cpumask_t *numaq_target_cpus(void) +{ +	return &CPU_MASK_ALL; +} + +static inline unsigned long +numaq_check_apicid_used(physid_mask_t bitmap, int apicid) +{ +	return physid_isset(apicid, bitmap); +} + +static inline unsigned long numaq_check_apicid_present(int bit) +{ +	return physid_isset(bit, phys_cpu_present_map); +} + +static inline int numaq_apic_id_registered(void) +{ +	return 1; +} + +static inline void numaq_init_apic_ldr(void) +{ +	/* Already done in NUMA-Q firmware */ +} + +static inline void numaq_setup_apic_routing(void) +{ +	printk(KERN_INFO +		"Enabling APIC mode:  NUMA-Q.  Using %d I/O APICs\n", +		nr_ioapics); +} + +/* + * Skip adding the timer int on secondary nodes, which causes + * a small but painful rift in the time-space continuum. + */ +static inline int numaq_multi_timer_check(int apic, int irq) +{ +	return apic != 0 && irq == 0; +} + +static inline physid_mask_t numaq_ioapic_phys_id_map(physid_mask_t phys_map) +{ +	/* We don't have a good way to do this yet - hack */ +	return physids_promote(0xFUL); +} + +static inline int numaq_cpu_to_logical_apicid(int cpu) +{ +	if (cpu >= nr_cpu_ids) +		return BAD_APICID; +	return cpu_2_logical_apicid[cpu]; +} + +/* + * Supporting over 60 cpus on NUMA-Q requires a locality-dependent + * cpu to APIC ID relation to properly interact with the intelligent + * mode of the cluster controller. + */ +static inline int numaq_cpu_present_to_apicid(int mps_cpu) +{ +	if (mps_cpu < 60) +		return ((mps_cpu >> 2) << 4) | (1 << (mps_cpu & 0x3)); +	else +		return BAD_APICID; +} + +static inline int numaq_apicid_to_node(int logical_apicid) +{ +	return logical_apicid >> 4; +} + +static inline physid_mask_t numaq_apicid_to_cpu_present(int logical_apicid) +{ +	int node = numaq_apicid_to_node(logical_apicid); +	int cpu = __ffs(logical_apicid & 0xf); + +	return physid_mask_of_physid(cpu + 4*node); +} + +/* Where the IO area was mapped on multiquad, always 0 otherwise */ +void *xquad_portio; + +static inline int numaq_check_phys_apicid_present(int boot_cpu_physical_apicid) +{ +	return 1; +} + +/* + * We use physical apicids here, not logical, so just return the default + * physical broadcast to stop people from breaking us + */ +static inline unsigned int numaq_cpu_mask_to_apicid(const cpumask_t *cpumask) +{ +	return 0x0F; +} + +static inline unsigned int +numaq_cpu_mask_to_apicid_and(const struct cpumask *cpumask, +			     const struct cpumask *andmask) +{ +	return 0x0F; +} + +/* No NUMA-Q box has a HT CPU, but it can't hurt to use the default code. */ +static inline int numaq_phys_pkg_id(int cpuid_apic, int index_msb) +{ +	return cpuid_apic >> index_msb; +} + +static int +numaq_mps_oem_check(struct mpc_table *mpc, char *oem, char *productid) +{ +	if (strncmp(oem, "IBM NUMA", 8)) +		printk(KERN_ERR "Warning! Not a NUMA-Q system!\n"); +	else +		found_numaq = 1; + +	return found_numaq; +} + +static int probe_numaq(void) +{ +	/* already know from get_memcfg_numaq() */ +	return found_numaq; +} + +static void numaq_vector_allocation_domain(int cpu, cpumask_t *retmask) +{ +	/* Careful. Some cpus do not strictly honor the set of cpus +	 * specified in the interrupt destination when using lowest +	 * priority interrupt delivery mode. +	 * +	 * In particular there was a hyperthreading cpu observed to +	 * deliver interrupts to the wrong hyperthread when only one +	 * hyperthread was specified in the interrupt desitination. +	 */ +	*retmask = (cpumask_t){ { [0] = APIC_ALL_CPUS, } }; +} + +static void numaq_setup_portio_remap(void) +{ +	int num_quads = num_online_nodes(); + +	if (num_quads <= 1) +		return; + +	printk(KERN_INFO +		"Remapping cross-quad port I/O for %d quads\n", num_quads); + +	xquad_portio = ioremap(XQUAD_PORTIO_BASE, num_quads*XQUAD_PORTIO_QUAD); + +	printk(KERN_INFO +		"xquad_portio vaddr 0x%08lx, len %08lx\n", +		(u_long) xquad_portio, (u_long) num_quads*XQUAD_PORTIO_QUAD); +} + +struct apic apic_numaq = { + +	.name				= "NUMAQ", +	.probe				= probe_numaq, +	.acpi_madt_oem_check		= NULL, +	.apic_id_registered		= numaq_apic_id_registered, + +	.irq_delivery_mode		= dest_LowestPrio, +	/* physical delivery on LOCAL quad: */ +	.irq_dest_mode			= 0, + +	.target_cpus			= numaq_target_cpus, +	.disable_esr			= 1, +	.dest_logical			= APIC_DEST_LOGICAL, +	.check_apicid_used		= numaq_check_apicid_used, +	.check_apicid_present		= numaq_check_apicid_present, + +	.vector_allocation_domain	= numaq_vector_allocation_domain, +	.init_apic_ldr			= numaq_init_apic_ldr, + +	.ioapic_phys_id_map		= numaq_ioapic_phys_id_map, +	.setup_apic_routing		= numaq_setup_apic_routing, +	.multi_timer_check		= numaq_multi_timer_check, +	.apicid_to_node			= numaq_apicid_to_node, +	.cpu_to_logical_apicid		= numaq_cpu_to_logical_apicid, +	.cpu_present_to_apicid		= numaq_cpu_present_to_apicid, +	.apicid_to_cpu_present		= numaq_apicid_to_cpu_present, +	.setup_portio_remap		= numaq_setup_portio_remap, +	.check_phys_apicid_present	= numaq_check_phys_apicid_present, +	.enable_apic_mode		= NULL, +	.phys_pkg_id			= numaq_phys_pkg_id, +	.mps_oem_check			= numaq_mps_oem_check, + +	.get_apic_id			= numaq_get_apic_id, +	.set_apic_id			= NULL, +	.apic_id_mask			= 0x0F << 24, + +	.cpu_mask_to_apicid		= numaq_cpu_mask_to_apicid, +	.cpu_mask_to_apicid_and		= numaq_cpu_mask_to_apicid_and, + +	.send_IPI_mask			= numaq_send_IPI_mask, +	.send_IPI_mask_allbutself	= NULL, +	.send_IPI_allbutself		= numaq_send_IPI_allbutself, +	.send_IPI_all			= numaq_send_IPI_all, +	.send_IPI_self			= default_send_IPI_self, + +	.wakeup_cpu			= NULL, +	.trampoline_phys_low		= NUMAQ_TRAMPOLINE_PHYS_LOW, +	.trampoline_phys_high		= NUMAQ_TRAMPOLINE_PHYS_HIGH, + +	/* We don't do anything here because we use NMI's to boot instead */ +	.wait_for_init_deassert		= NULL, + +	.smp_callin_clear_local_apic	= numaq_smp_callin_clear_local_apic, +	.inquire_remote_apic		= NULL, + +	.read				= native_apic_mem_read, +	.write				= native_apic_mem_write, +	.icr_read			= native_apic_icr_read, +	.icr_write			= native_apic_icr_write, +	.wait_icr_idle			= native_apic_wait_icr_idle, +	.safe_wait_icr_idle		= native_safe_apic_wait_icr_idle, +}; diff --git a/arch/x86/kernel/apic/probe_32.c b/arch/x86/kernel/apic/probe_32.c new file mode 100644 index 000000000000..5fa48332c5c8 --- /dev/null +++ b/arch/x86/kernel/apic/probe_32.c @@ -0,0 +1,424 @@ +/* + * Default generic APIC driver. This handles up to 8 CPUs. + * + * Copyright 2003 Andi Kleen, SuSE Labs. + * Subject to the GNU Public License, v.2 + * + * Generic x86 APIC driver probe layer. + */ +#include <linux/threads.h> +#include <linux/cpumask.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/ctype.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <asm/fixmap.h> +#include <asm/mpspec.h> +#include <asm/apicdef.h> +#include <asm/apic.h> +#include <asm/setup.h> + +#include <linux/threads.h> +#include <linux/cpumask.h> +#include <asm/mpspec.h> +#include <asm/fixmap.h> +#include <asm/apicdef.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/smp.h> +#include <linux/init.h> +#include <asm/ipi.h> + +#include <linux/smp.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <asm/acpi.h> +#include <asm/arch_hooks.h> +#include <asm/e820.h> +#include <asm/setup.h> + +#ifdef CONFIG_HOTPLUG_CPU +#define DEFAULT_SEND_IPI	(1) +#else +#define DEFAULT_SEND_IPI	(0) +#endif + +int no_broadcast = DEFAULT_SEND_IPI; + +#ifdef CONFIG_X86_LOCAL_APIC + +void default_setup_apic_routing(void) +{ +#ifdef CONFIG_X86_IO_APIC +	printk(KERN_INFO +		"Enabling APIC mode:  Flat.  Using %d I/O APICs\n", +		nr_ioapics); +#endif +} + +static void default_vector_allocation_domain(int cpu, struct cpumask *retmask) +{ +	/* +	 * Careful. Some cpus do not strictly honor the set of cpus +	 * specified in the interrupt destination when using lowest +	 * priority interrupt delivery mode. +	 * +	 * In particular there was a hyperthreading cpu observed to +	 * deliver interrupts to the wrong hyperthread when only one +	 * hyperthread was specified in the interrupt desitination. +	 */ +	*retmask = (cpumask_t) { { [0] = APIC_ALL_CPUS } }; +} + +/* should be called last. */ +static int probe_default(void) +{ +	return 1; +} + +struct apic apic_default = { + +	.name				= "default", +	.probe				= probe_default, +	.acpi_madt_oem_check		= NULL, +	.apic_id_registered		= default_apic_id_registered, + +	.irq_delivery_mode		= dest_LowestPrio, +	/* logical delivery broadcast to all CPUs: */ +	.irq_dest_mode			= 1, + +	.target_cpus			= default_target_cpus, +	.disable_esr			= 0, +	.dest_logical			= APIC_DEST_LOGICAL, +	.check_apicid_used		= default_check_apicid_used, +	.check_apicid_present		= default_check_apicid_present, + +	.vector_allocation_domain	= default_vector_allocation_domain, +	.init_apic_ldr			= default_init_apic_ldr, + +	.ioapic_phys_id_map		= default_ioapic_phys_id_map, +	.setup_apic_routing		= default_setup_apic_routing, +	.multi_timer_check		= NULL, +	.apicid_to_node			= default_apicid_to_node, +	.cpu_to_logical_apicid		= default_cpu_to_logical_apicid, +	.cpu_present_to_apicid		= default_cpu_present_to_apicid, +	.apicid_to_cpu_present		= default_apicid_to_cpu_present, +	.setup_portio_remap		= NULL, +	.check_phys_apicid_present	= default_check_phys_apicid_present, +	.enable_apic_mode		= NULL, +	.phys_pkg_id			= default_phys_pkg_id, +	.mps_oem_check			= NULL, + +	.get_apic_id			= default_get_apic_id, +	.set_apic_id			= NULL, +	.apic_id_mask			= 0x0F << 24, + +	.cpu_mask_to_apicid		= default_cpu_mask_to_apicid, +	.cpu_mask_to_apicid_and		= default_cpu_mask_to_apicid_and, + +	.send_IPI_mask			= default_send_IPI_mask_logical, +	.send_IPI_mask_allbutself	= default_send_IPI_mask_allbutself_logical, +	.send_IPI_allbutself		= default_send_IPI_allbutself, +	.send_IPI_all			= default_send_IPI_all, +	.send_IPI_self			= default_send_IPI_self, + +	.wakeup_cpu			= NULL, +	.trampoline_phys_low		= DEFAULT_TRAMPOLINE_PHYS_LOW, +	.trampoline_phys_high		= DEFAULT_TRAMPOLINE_PHYS_HIGH, + +	.wait_for_init_deassert		= default_wait_for_init_deassert, + +	.smp_callin_clear_local_apic	= NULL, +	.inquire_remote_apic		= default_inquire_remote_apic, + +	.read				= native_apic_mem_read, +	.write				= native_apic_mem_write, +	.icr_read			= native_apic_icr_read, +	.icr_write			= native_apic_icr_write, +	.wait_icr_idle			= native_apic_wait_icr_idle, +	.safe_wait_icr_idle		= native_safe_apic_wait_icr_idle, +}; + +extern struct apic apic_numaq; +extern struct apic apic_summit; +extern struct apic apic_bigsmp; +extern struct apic apic_es7000; +extern struct apic apic_default; + +struct apic *apic = &apic_default; +EXPORT_SYMBOL_GPL(apic); + +static struct apic *apic_probe[] __initdata = { +#ifdef CONFIG_X86_NUMAQ +	&apic_numaq, +#endif +#ifdef CONFIG_X86_SUMMIT +	&apic_summit, +#endif +#ifdef CONFIG_X86_BIGSMP +	&apic_bigsmp, +#endif +#ifdef CONFIG_X86_ES7000 +	&apic_es7000, +#endif +	&apic_default,	/* must be last */ +	NULL, +}; + +static int cmdline_apic __initdata; +static int __init parse_apic(char *arg) +{ +	int i; + +	if (!arg) +		return -EINVAL; + +	for (i = 0; apic_probe[i]; i++) { +		if (!strcmp(apic_probe[i]->name, arg)) { +			apic = apic_probe[i]; +			cmdline_apic = 1; +			return 0; +		} +	} + +	if (x86_quirks->update_apic) +		x86_quirks->update_apic(); + +	/* Parsed again by __setup for debug/verbose */ +	return 0; +} +early_param("apic", parse_apic); + +void __init generic_bigsmp_probe(void) +{ +#ifdef CONFIG_X86_BIGSMP +	/* +	 * This routine is used to switch to bigsmp mode when +	 * - There is no apic= option specified by the user +	 * - generic_apic_probe() has chosen apic_default as the sub_arch +	 * - we find more than 8 CPUs in acpi LAPIC listing with xAPIC support +	 */ + +	if (!cmdline_apic && apic == &apic_default) { +		if (apic_bigsmp.probe()) { +			apic = &apic_bigsmp; +			if (x86_quirks->update_apic) +				x86_quirks->update_apic(); +			printk(KERN_INFO "Overriding APIC driver with %s\n", +			       apic->name); +		} +	} +#endif +} + +void __init generic_apic_probe(void) +{ +	if (!cmdline_apic) { +		int i; +		for (i = 0; apic_probe[i]; i++) { +			if (apic_probe[i]->probe()) { +				apic = apic_probe[i]; +				break; +			} +		} +		/* Not visible without early console */ +		if (!apic_probe[i]) +			panic("Didn't find an APIC driver"); + +		if (x86_quirks->update_apic) +			x86_quirks->update_apic(); +	} +	printk(KERN_INFO "Using APIC driver %s\n", apic->name); +} + +/* These functions can switch the APIC even after the initial ->probe() */ + +int __init +generic_mps_oem_check(struct mpc_table *mpc, char *oem, char *productid) +{ +	int i; + +	for (i = 0; apic_probe[i]; ++i) { +		if (!apic_probe[i]->mps_oem_check) +			continue; +		if (!apic_probe[i]->mps_oem_check(mpc, oem, productid)) +			continue; + +		if (!cmdline_apic) { +			apic = apic_probe[i]; +			if (x86_quirks->update_apic) +				x86_quirks->update_apic(); +			printk(KERN_INFO "Switched to APIC driver `%s'.\n", +			       apic->name); +		} +		return 1; +	} +	return 0; +} + +int __init default_acpi_madt_oem_check(char *oem_id, char *oem_table_id) +{ +	int i; + +	for (i = 0; apic_probe[i]; ++i) { +		if (!apic_probe[i]->acpi_madt_oem_check) +			continue; +		if (!apic_probe[i]->acpi_madt_oem_check(oem_id, oem_table_id)) +			continue; + +		if (!cmdline_apic) { +			apic = apic_probe[i]; +			if (x86_quirks->update_apic) +				x86_quirks->update_apic(); +			printk(KERN_INFO "Switched to APIC driver `%s'.\n", +			       apic->name); +		} +		return 1; +	} +	return 0; +} + +#endif /* CONFIG_X86_LOCAL_APIC */ + +/** + * pre_intr_init_hook - initialisation prior to setting up interrupt vectors + * + * Description: + *	Perform any necessary interrupt initialisation prior to setting up + *	the "ordinary" interrupt call gates.  For legacy reasons, the ISA + *	interrupts should be initialised here if the machine emulates a PC + *	in any way. + **/ +void __init pre_intr_init_hook(void) +{ +	if (x86_quirks->arch_pre_intr_init) { +		if (x86_quirks->arch_pre_intr_init()) +			return; +	} +	init_ISA_irqs(); +} + +/** + * intr_init_hook - post gate setup interrupt initialisation + * + * Description: + *	Fill in any interrupts that may have been left out by the general + *	init_IRQ() routine.  interrupts having to do with the machine rather + *	than the devices on the I/O bus (like APIC interrupts in intel MP + *	systems) are started here. + **/ +void __init intr_init_hook(void) +{ +	if (x86_quirks->arch_intr_init) { +		if (x86_quirks->arch_intr_init()) +			return; +	} +} + +/** + * pre_setup_arch_hook - hook called prior to any setup_arch() execution + * + * Description: + *	generally used to activate any machine specific identification + *	routines that may be needed before setup_arch() runs.  On Voyager + *	this is used to get the board revision and type. + **/ +void __init pre_setup_arch_hook(void) +{ +} + +/** + * trap_init_hook - initialise system specific traps + * + * Description: + *	Called as the final act of trap_init().  Used in VISWS to initialise + *	the various board specific APIC traps. + **/ +void __init trap_init_hook(void) +{ +	if (x86_quirks->arch_trap_init) { +		if (x86_quirks->arch_trap_init()) +			return; +	} +} + +static struct irqaction irq0  = { +	.handler = timer_interrupt, +	.flags = IRQF_DISABLED | IRQF_NOBALANCING | IRQF_IRQPOLL, +	.mask = CPU_MASK_NONE, +	.name = "timer" +}; + +/** + * pre_time_init_hook - do any specific initialisations before. + * + **/ +void __init pre_time_init_hook(void) +{ +	if (x86_quirks->arch_pre_time_init) +		x86_quirks->arch_pre_time_init(); +} + +/** + * time_init_hook - do any specific initialisations for the system timer. + * + * Description: + *	Must plug the system timer interrupt source at HZ into the IRQ listed + *	in irq_vectors.h:TIMER_IRQ + **/ +void __init time_init_hook(void) +{ +	if (x86_quirks->arch_time_init) { +		/* +		 * A nonzero return code does not mean failure, it means +		 * that the architecture quirk does not want any +		 * generic (timer) setup to be performed after this: +		 */ +		if (x86_quirks->arch_time_init()) +			return; +	} + +	irq0.mask = cpumask_of_cpu(0); +	setup_irq(0, &irq0); +} + +#ifdef CONFIG_MCA +/** + * mca_nmi_hook - hook into MCA specific NMI chain + * + * Description: + *	The MCA (Microchannel Architecture) has an NMI chain for NMI sources + *	along the MCA bus.  Use this to hook into that chain if you will need + *	it. + **/ +void mca_nmi_hook(void) +{ +	/* +	 * If I recall correctly, there's a whole bunch of other things that +	 * we can do to check for NMI problems, but that's all I know about +	 * at the moment. +	 */ +	pr_warning("NMI generated from unknown source!\n"); +} +#endif + +static __init int no_ipi_broadcast(char *str) +{ +	get_option(&str, &no_broadcast); +	pr_info("Using %s mode\n", +		no_broadcast ? "No IPI Broadcast" : "IPI Broadcast"); +	return 1; +} +__setup("no_ipi_broadcast=", no_ipi_broadcast); + +static int __init print_ipi_mode(void) +{ +	pr_info("Using IPI %s mode\n", +		no_broadcast ? "No-Shortcut" : "Shortcut"); +	return 0; +} + +late_initcall(print_ipi_mode); + diff --git a/arch/x86/kernel/apic/apic_64.c b/arch/x86/kernel/apic/probe_64.c index 70935dd904db..70935dd904db 100644 --- a/arch/x86/kernel/apic/apic_64.c +++ b/arch/x86/kernel/apic/probe_64.c diff --git a/arch/x86/kernel/apic/summit_32.c b/arch/x86/kernel/apic/summit_32.c new file mode 100644 index 000000000000..cfe7b09015d8 --- /dev/null +++ b/arch/x86/kernel/apic/summit_32.c @@ -0,0 +1,601 @@ +/* + * IBM Summit-Specific Code + * + * Written By: Matthew Dobson, IBM Corporation + * + * Copyright (c) 2003 IBM Corp. + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT.  See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Send feedback to <colpatch@us.ibm.com> + * + */ + +#include <linux/mm.h> +#include <linux/init.h> +#include <asm/io.h> +#include <asm/bios_ebda.h> + +/* + * APIC driver for the IBM "Summit" chipset. + */ +#include <linux/threads.h> +#include <linux/cpumask.h> +#include <asm/mpspec.h> +#include <asm/apic.h> +#include <asm/smp.h> +#include <asm/fixmap.h> +#include <asm/apicdef.h> +#include <asm/ipi.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/gfp.h> +#include <linux/smp.h> + +static inline unsigned summit_get_apic_id(unsigned long x) +{ +	return (x >> 24) & 0xFF; +} + +static inline void summit_send_IPI_mask(const cpumask_t *mask, int vector) +{ +	default_send_IPI_mask_sequence_logical(mask, vector); +} + +static inline void summit_send_IPI_allbutself(int vector) +{ +	cpumask_t mask = cpu_online_map; +	cpu_clear(smp_processor_id(), mask); + +	if (!cpus_empty(mask)) +		summit_send_IPI_mask(&mask, vector); +} + +static inline void summit_send_IPI_all(int vector) +{ +	summit_send_IPI_mask(&cpu_online_map, vector); +} + +#include <asm/tsc.h> + +extern int use_cyclone; + +#ifdef CONFIG_X86_SUMMIT_NUMA +extern void setup_summit(void); +#else +#define setup_summit()	{} +#endif + +static inline int +summit_mps_oem_check(struct mpc_table *mpc, char *oem, char *productid) +{ +	if (!strncmp(oem, "IBM ENSW", 8) && +			(!strncmp(productid, "VIGIL SMP", 9) +			 || !strncmp(productid, "EXA", 3) +			 || !strncmp(productid, "RUTHLESS SMP", 12))){ +		mark_tsc_unstable("Summit based system"); +		use_cyclone = 1; /*enable cyclone-timer*/ +		setup_summit(); +		return 1; +	} +	return 0; +} + +/* Hook from generic ACPI tables.c */ +static inline int summit_acpi_madt_oem_check(char *oem_id, char *oem_table_id) +{ +	if (!strncmp(oem_id, "IBM", 3) && +	    (!strncmp(oem_table_id, "SERVIGIL", 8) +	     || !strncmp(oem_table_id, "EXA", 3))){ +		mark_tsc_unstable("Summit based system"); +		use_cyclone = 1; /*enable cyclone-timer*/ +		setup_summit(); +		return 1; +	} +	return 0; +} + +struct rio_table_hdr { +	unsigned char version;      /* Version number of this data structure           */ +	                            /* Version 3 adds chassis_num & WP_index           */ +	unsigned char num_scal_dev; /* # of Scalability devices (Twisters for Vigil)   */ +	unsigned char num_rio_dev;  /* # of RIO I/O devices (Cyclones and Winnipegs)   */ +} __attribute__((packed)); + +struct scal_detail { +	unsigned char node_id;      /* Scalability Node ID                             */ +	unsigned long CBAR;         /* Address of 1MB register space                   */ +	unsigned char port0node;    /* Node ID port connected to: 0xFF=None            */ +	unsigned char port0port;    /* Port num port connected to: 0,1,2, or 0xFF=None */ +	unsigned char port1node;    /* Node ID port connected to: 0xFF = None          */ +	unsigned char port1port;    /* Port num port connected to: 0,1,2, or 0xFF=None */ +	unsigned char port2node;    /* Node ID port connected to: 0xFF = None          */ +	unsigned char port2port;    /* Port num port connected to: 0,1,2, or 0xFF=None */ +	unsigned char chassis_num;  /* 1 based Chassis number (1 = boot node)          */ +} __attribute__((packed)); + +struct rio_detail { +	unsigned char node_id;      /* RIO Node ID                                     */ +	unsigned long BBAR;         /* Address of 1MB register space                   */ +	unsigned char type;         /* Type of device                                  */ +	unsigned char owner_id;     /* For WPEG: Node ID of Cyclone that owns this WPEG*/ +	                            /* For CYC:  Node ID of Twister that owns this CYC */ +	unsigned char port0node;    /* Node ID port connected to: 0xFF=None            */ +	unsigned char port0port;    /* Port num port connected to: 0,1,2, or 0xFF=None */ +	unsigned char port1node;    /* Node ID port connected to: 0xFF=None            */ +	unsigned char port1port;    /* Port num port connected to: 0,1,2, or 0xFF=None */ +	unsigned char first_slot;   /* For WPEG: Lowest slot number below this WPEG    */ +	                            /* For CYC:  0                                     */ +	unsigned char status;       /* For WPEG: Bit 0 = 1 : the XAPIC is used         */ +	                            /*                 = 0 : the XAPIC is not used, ie:*/ +	                            /*                     ints fwded to another XAPIC */ +	                            /*           Bits1:7 Reserved                      */ +	                            /* For CYC:  Bits0:7 Reserved                      */ +	unsigned char WP_index;     /* For WPEG: WPEG instance index - lower ones have */ +	                            /*           lower slot numbers/PCI bus numbers    */ +	                            /* For CYC:  No meaning                            */ +	unsigned char chassis_num;  /* 1 based Chassis number                          */ +	                            /* For LookOut WPEGs this field indicates the      */ +	                            /* Expansion Chassis #, enumerated from Boot       */ +	                            /* Node WPEG external port, then Boot Node CYC     */ +	                            /* external port, then Next Vigil chassis WPEG     */ +	                            /* external port, etc.                             */ +	                            /* Shared Lookouts have only 1 chassis number (the */ +	                            /* first one assigned)                             */ +} __attribute__((packed)); + + +typedef enum { +	CompatTwister = 0,  /* Compatibility Twister               */ +	AltTwister    = 1,  /* Alternate Twister of internal 8-way */ +	CompatCyclone = 2,  /* Compatibility Cyclone               */ +	AltCyclone    = 3,  /* Alternate Cyclone of internal 8-way */ +	CompatWPEG    = 4,  /* Compatibility WPEG                  */ +	AltWPEG       = 5,  /* Second Planar WPEG                  */ +	LookOutAWPEG  = 6,  /* LookOut WPEG                        */ +	LookOutBWPEG  = 7,  /* LookOut WPEG                        */ +} node_type; + +static inline int is_WPEG(struct rio_detail *rio){ +	return (rio->type == CompatWPEG || rio->type == AltWPEG || +		rio->type == LookOutAWPEG || rio->type == LookOutBWPEG); +} + + +/* In clustered mode, the high nibble of APIC ID is a cluster number. + * The low nibble is a 4-bit bitmap. */ +#define XAPIC_DEST_CPUS_SHIFT	4 +#define XAPIC_DEST_CPUS_MASK	((1u << XAPIC_DEST_CPUS_SHIFT) - 1) +#define XAPIC_DEST_CLUSTER_MASK	(XAPIC_DEST_CPUS_MASK << XAPIC_DEST_CPUS_SHIFT) + +#define SUMMIT_APIC_DFR_VALUE	(APIC_DFR_CLUSTER) + +static inline const cpumask_t *summit_target_cpus(void) +{ +	/* CPU_MASK_ALL (0xff) has undefined behaviour with +	 * dest_LowestPrio mode logical clustered apic interrupt routing +	 * Just start on cpu 0.  IRQ balancing will spread load +	 */ +	return &cpumask_of_cpu(0); +} + +static inline unsigned long +summit_check_apicid_used(physid_mask_t bitmap, int apicid) +{ +	return 0; +} + +/* we don't use the phys_cpu_present_map to indicate apicid presence */ +static inline unsigned long summit_check_apicid_present(int bit) +{ +	return 1; +} + +static inline void summit_init_apic_ldr(void) +{ +	unsigned long val, id; +	int count = 0; +	u8 my_id = (u8)hard_smp_processor_id(); +	u8 my_cluster = APIC_CLUSTER(my_id); +#ifdef CONFIG_SMP +	u8 lid; +	int i; + +	/* Create logical APIC IDs by counting CPUs already in cluster. */ +	for (count = 0, i = nr_cpu_ids; --i >= 0; ) { +		lid = cpu_2_logical_apicid[i]; +		if (lid != BAD_APICID && APIC_CLUSTER(lid) == my_cluster) +			++count; +	} +#endif +	/* We only have a 4 wide bitmap in cluster mode.  If a deranged +	 * BIOS puts 5 CPUs in one APIC cluster, we're hosed. */ +	BUG_ON(count >= XAPIC_DEST_CPUS_SHIFT); +	id = my_cluster | (1UL << count); +	apic_write(APIC_DFR, SUMMIT_APIC_DFR_VALUE); +	val = apic_read(APIC_LDR) & ~APIC_LDR_MASK; +	val |= SET_APIC_LOGICAL_ID(id); +	apic_write(APIC_LDR, val); +} + +static inline int summit_apic_id_registered(void) +{ +	return 1; +} + +static inline void summit_setup_apic_routing(void) +{ +	printk("Enabling APIC mode:  Summit.  Using %d I/O APICs\n", +						nr_ioapics); +} + +static inline int summit_apicid_to_node(int logical_apicid) +{ +#ifdef CONFIG_SMP +	return apicid_2_node[hard_smp_processor_id()]; +#else +	return 0; +#endif +} + +/* Mapping from cpu number to logical apicid */ +static inline int summit_cpu_to_logical_apicid(int cpu) +{ +#ifdef CONFIG_SMP +	if (cpu >= nr_cpu_ids) +		return BAD_APICID; +	return cpu_2_logical_apicid[cpu]; +#else +	return logical_smp_processor_id(); +#endif +} + +static inline int summit_cpu_present_to_apicid(int mps_cpu) +{ +	if (mps_cpu < nr_cpu_ids) +		return (int)per_cpu(x86_bios_cpu_apicid, mps_cpu); +	else +		return BAD_APICID; +} + +static inline physid_mask_t +summit_ioapic_phys_id_map(physid_mask_t phys_id_map) +{ +	/* For clustered we don't have a good way to do this yet - hack */ +	return physids_promote(0x0F); +} + +static inline physid_mask_t summit_apicid_to_cpu_present(int apicid) +{ +	return physid_mask_of_physid(0); +} + +static inline void summit_setup_portio_remap(void) +{ +} + +static inline int summit_check_phys_apicid_present(int boot_cpu_physical_apicid) +{ +	return 1; +} + +static inline unsigned int summit_cpu_mask_to_apicid(const cpumask_t *cpumask) +{ +	int cpus_found = 0; +	int num_bits_set; +	int apicid; +	int cpu; + +	num_bits_set = cpus_weight(*cpumask); +	/* Return id to all */ +	if (num_bits_set >= nr_cpu_ids) +		return 0xFF; +	/* +	 * The cpus in the mask must all be on the apic cluster.  If are not +	 * on the same apicid cluster return default value of target_cpus(): +	 */ +	cpu = first_cpu(*cpumask); +	apicid = summit_cpu_to_logical_apicid(cpu); + +	while (cpus_found < num_bits_set) { +		if (cpu_isset(cpu, *cpumask)) { +			int new_apicid = summit_cpu_to_logical_apicid(cpu); + +			if (APIC_CLUSTER(apicid) != APIC_CLUSTER(new_apicid)) { +				printk ("%s: Not a valid mask!\n", __func__); + +				return 0xFF; +			} +			apicid = apicid | new_apicid; +			cpus_found++; +		} +		cpu++; +	} +	return apicid; +} + +static inline unsigned int +summit_cpu_mask_to_apicid_and(const struct cpumask *inmask, +			      const struct cpumask *andmask) +{ +	int apicid = summit_cpu_to_logical_apicid(0); +	cpumask_var_t cpumask; + +	if (!alloc_cpumask_var(&cpumask, GFP_ATOMIC)) +		return apicid; + +	cpumask_and(cpumask, inmask, andmask); +	cpumask_and(cpumask, cpumask, cpu_online_mask); +	apicid = summit_cpu_mask_to_apicid(cpumask); + +	free_cpumask_var(cpumask); + +	return apicid; +} + +/* + * cpuid returns the value latched in the HW at reset, not the APIC ID + * register's value.  For any box whose BIOS changes APIC IDs, like + * clustered APIC systems, we must use hard_smp_processor_id. + * + * See Intel's IA-32 SW Dev's Manual Vol2 under CPUID. + */ +static inline int summit_phys_pkg_id(int cpuid_apic, int index_msb) +{ +	return hard_smp_processor_id() >> index_msb; +} + +static int probe_summit(void) +{ +	/* probed later in mptable/ACPI hooks */ +	return 0; +} + +static void summit_vector_allocation_domain(int cpu, cpumask_t *retmask) +{ +	/* Careful. Some cpus do not strictly honor the set of cpus +	 * specified in the interrupt destination when using lowest +	 * priority interrupt delivery mode. +	 * +	 * In particular there was a hyperthreading cpu observed to +	 * deliver interrupts to the wrong hyperthread when only one +	 * hyperthread was specified in the interrupt desitination. +	 */ +	*retmask = (cpumask_t){ { [0] = APIC_ALL_CPUS, } }; +} + +#ifdef CONFIG_X86_SUMMIT_NUMA +static struct rio_table_hdr *rio_table_hdr __initdata; +static struct scal_detail   *scal_devs[MAX_NUMNODES] __initdata; +static struct rio_detail    *rio_devs[MAX_NUMNODES*4] __initdata; + +#ifndef CONFIG_X86_NUMAQ +static int mp_bus_id_to_node[MAX_MP_BUSSES] __initdata; +#endif + +static int __init setup_pci_node_map_for_wpeg(int wpeg_num, int last_bus) +{ +	int twister = 0, node = 0; +	int i, bus, num_buses; + +	for (i = 0; i < rio_table_hdr->num_rio_dev; i++) { +		if (rio_devs[i]->node_id == rio_devs[wpeg_num]->owner_id) { +			twister = rio_devs[i]->owner_id; +			break; +		} +	} +	if (i == rio_table_hdr->num_rio_dev) { +		printk(KERN_ERR "%s: Couldn't find owner Cyclone for Winnipeg!\n", __func__); +		return last_bus; +	} + +	for (i = 0; i < rio_table_hdr->num_scal_dev; i++) { +		if (scal_devs[i]->node_id == twister) { +			node = scal_devs[i]->node_id; +			break; +		} +	} +	if (i == rio_table_hdr->num_scal_dev) { +		printk(KERN_ERR "%s: Couldn't find owner Twister for Cyclone!\n", __func__); +		return last_bus; +	} + +	switch (rio_devs[wpeg_num]->type) { +	case CompatWPEG: +		/* +		 * The Compatibility Winnipeg controls the 2 legacy buses, +		 * the 66MHz PCI bus [2 slots] and the 2 "extra" buses in case +		 * a PCI-PCI bridge card is used in either slot: total 5 buses. +		 */ +		num_buses = 5; +		break; +	case AltWPEG: +		/* +		 * The Alternate Winnipeg controls the 2 133MHz buses [1 slot +		 * each], their 2 "extra" buses, the 100MHz bus [2 slots] and +		 * the "extra" buses for each of those slots: total 7 buses. +		 */ +		num_buses = 7; +		break; +	case LookOutAWPEG: +	case LookOutBWPEG: +		/* +		 * A Lookout Winnipeg controls 3 100MHz buses [2 slots each] +		 * & the "extra" buses for each of those slots: total 9 buses. +		 */ +		num_buses = 9; +		break; +	default: +		printk(KERN_INFO "%s: Unsupported Winnipeg type!\n", __func__); +		return last_bus; +	} + +	for (bus = last_bus; bus < last_bus + num_buses; bus++) +		mp_bus_id_to_node[bus] = node; +	return bus; +} + +static int __init build_detail_arrays(void) +{ +	unsigned long ptr; +	int i, scal_detail_size, rio_detail_size; + +	if (rio_table_hdr->num_scal_dev > MAX_NUMNODES) { +		printk(KERN_WARNING "%s: MAX_NUMNODES too low!  Defined as %d, but system has %d nodes.\n", __func__, MAX_NUMNODES, rio_table_hdr->num_scal_dev); +		return 0; +	} + +	switch (rio_table_hdr->version) { +	default: +		printk(KERN_WARNING "%s: Invalid Rio Grande Table Version: %d\n", __func__, rio_table_hdr->version); +		return 0; +	case 2: +		scal_detail_size = 11; +		rio_detail_size = 13; +		break; +	case 3: +		scal_detail_size = 12; +		rio_detail_size = 15; +		break; +	} + +	ptr = (unsigned long)rio_table_hdr + 3; +	for (i = 0; i < rio_table_hdr->num_scal_dev; i++, ptr += scal_detail_size) +		scal_devs[i] = (struct scal_detail *)ptr; + +	for (i = 0; i < rio_table_hdr->num_rio_dev; i++, ptr += rio_detail_size) +		rio_devs[i] = (struct rio_detail *)ptr; + +	return 1; +} + +void __init setup_summit(void) +{ +	unsigned long		ptr; +	unsigned short		offset; +	int			i, next_wpeg, next_bus = 0; + +	/* The pointer to the EBDA is stored in the word @ phys 0x40E(40:0E) */ +	ptr = get_bios_ebda(); +	ptr = (unsigned long)phys_to_virt(ptr); + +	rio_table_hdr = NULL; +	offset = 0x180; +	while (offset) { +		/* The block id is stored in the 2nd word */ +		if (*((unsigned short *)(ptr + offset + 2)) == 0x4752) { +			/* set the pointer past the offset & block id */ +			rio_table_hdr = (struct rio_table_hdr *)(ptr + offset + 4); +			break; +		} +		/* The next offset is stored in the 1st word.  0 means no more */ +		offset = *((unsigned short *)(ptr + offset)); +	} +	if (!rio_table_hdr) { +		printk(KERN_ERR "%s: Unable to locate Rio Grande Table in EBDA - bailing!\n", __func__); +		return; +	} + +	if (!build_detail_arrays()) +		return; + +	/* The first Winnipeg we're looking for has an index of 0 */ +	next_wpeg = 0; +	do { +		for (i = 0; i < rio_table_hdr->num_rio_dev; i++) { +			if (is_WPEG(rio_devs[i]) && rio_devs[i]->WP_index == next_wpeg) { +				/* It's the Winnipeg we're looking for! */ +				next_bus = setup_pci_node_map_for_wpeg(i, next_bus); +				next_wpeg++; +				break; +			} +		} +		/* +		 * If we go through all Rio devices and don't find one with +		 * the next index, it means we've found all the Winnipegs, +		 * and thus all the PCI buses. +		 */ +		if (i == rio_table_hdr->num_rio_dev) +			next_wpeg = 0; +	} while (next_wpeg != 0); +} +#endif + +struct apic apic_summit = { + +	.name				= "summit", +	.probe				= probe_summit, +	.acpi_madt_oem_check		= summit_acpi_madt_oem_check, +	.apic_id_registered		= summit_apic_id_registered, + +	.irq_delivery_mode		= dest_LowestPrio, +	/* logical delivery broadcast to all CPUs: */ +	.irq_dest_mode			= 1, + +	.target_cpus			= summit_target_cpus, +	.disable_esr			= 1, +	.dest_logical			= APIC_DEST_LOGICAL, +	.check_apicid_used		= summit_check_apicid_used, +	.check_apicid_present		= summit_check_apicid_present, + +	.vector_allocation_domain	= summit_vector_allocation_domain, +	.init_apic_ldr			= summit_init_apic_ldr, + +	.ioapic_phys_id_map		= summit_ioapic_phys_id_map, +	.setup_apic_routing		= summit_setup_apic_routing, +	.multi_timer_check		= NULL, +	.apicid_to_node			= summit_apicid_to_node, +	.cpu_to_logical_apicid		= summit_cpu_to_logical_apicid, +	.cpu_present_to_apicid		= summit_cpu_present_to_apicid, +	.apicid_to_cpu_present		= summit_apicid_to_cpu_present, +	.setup_portio_remap		= NULL, +	.check_phys_apicid_present	= summit_check_phys_apicid_present, +	.enable_apic_mode		= NULL, +	.phys_pkg_id			= summit_phys_pkg_id, +	.mps_oem_check			= summit_mps_oem_check, + +	.get_apic_id			= summit_get_apic_id, +	.set_apic_id			= NULL, +	.apic_id_mask			= 0xFF << 24, + +	.cpu_mask_to_apicid		= summit_cpu_mask_to_apicid, +	.cpu_mask_to_apicid_and		= summit_cpu_mask_to_apicid_and, + +	.send_IPI_mask			= summit_send_IPI_mask, +	.send_IPI_mask_allbutself	= NULL, +	.send_IPI_allbutself		= summit_send_IPI_allbutself, +	.send_IPI_all			= summit_send_IPI_all, +	.send_IPI_self			= default_send_IPI_self, + +	.wakeup_cpu			= NULL, +	.trampoline_phys_low		= DEFAULT_TRAMPOLINE_PHYS_LOW, +	.trampoline_phys_high		= DEFAULT_TRAMPOLINE_PHYS_HIGH, + +	.wait_for_init_deassert		= default_wait_for_init_deassert, + +	.smp_callin_clear_local_apic	= NULL, +	.inquire_remote_apic		= default_inquire_remote_apic, + +	.read				= native_apic_mem_read, +	.write				= native_apic_mem_write, +	.icr_read			= native_apic_icr_read, +	.icr_write			= native_apic_icr_write, +	.wait_icr_idle			= native_apic_wait_icr_idle, +	.safe_wait_icr_idle		= native_safe_apic_wait_icr_idle, +}; |