|  | /* | 
|  | * Written by: Patricia Gaughen, IBM Corporation | 
|  | * | 
|  | * Copyright (C) 2002, 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 <gone@us.ibm.com> | 
|  | */ | 
|  |  | 
|  | #include <linux/mm.h> | 
|  | #include <linux/bootmem.h> | 
|  | #include <linux/mmzone.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/nodemask.h> | 
|  | #include <asm/numaq.h> | 
|  | #include <asm/topology.h> | 
|  | #include <asm/processor.h> | 
|  | #include <asm/mpspec.h> | 
|  | #include <asm/e820.h> | 
|  | #include <asm/setup.h> | 
|  |  | 
|  | #define	MB_TO_PAGES(addr) ((addr) << (20 - PAGE_SHIFT)) | 
|  |  | 
|  | /* | 
|  | * 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) | 
|  | { | 
|  | int node; | 
|  | struct eachquadmem *eq; | 
|  | struct sys_cfg_data *scd = | 
|  | (struct sys_cfg_data *)__va(SYS_CFG_DATA_PRIV_ADDR); | 
|  |  | 
|  | nodes_clear(node_online_map); | 
|  | for_each_node(node) { | 
|  | if (scd->quads_present31_0 & (1 << node)) { | 
|  | node_set_online(node); | 
|  | eq = &scd->eq[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]); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | void __init 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; | 
|  | } | 
|  |  | 
|  | 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_config_translation { | 
|  | 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 struct mpc_config_translation *translation_table[MAX_MPC_ENTRY] | 
|  | __cpuinitdata; | 
|  |  | 
|  | 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_config_processor *m) | 
|  | { | 
|  | int quad = translation_table[mpc_record]->trans_quad; | 
|  | int logical_apicid = generate_logical_apicid(quad, m->mpc_apicid); | 
|  |  | 
|  | printk(KERN_DEBUG "Processor #%d %u:%u APIC version %d (quad %d, apic %d)\n", | 
|  | m->mpc_apicid, | 
|  | (m->mpc_cpufeature & CPU_FAMILY_MASK) >> 8, | 
|  | (m->mpc_cpufeature & CPU_MODEL_MASK) >> 4, | 
|  | m->mpc_apicver, quad, logical_apicid); | 
|  | return logical_apicid; | 
|  | } | 
|  |  | 
|  | int mp_bus_id_to_node[MAX_MP_BUSSES]; | 
|  |  | 
|  | int mp_bus_id_to_local[MAX_MP_BUSSES]; | 
|  |  | 
|  | /* x86_quirks member */ | 
|  | static void mpc_oem_bus_info(struct mpc_config_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->mpc_busid] = quad; | 
|  | mp_bus_id_to_local[m->mpc_busid] = local; | 
|  | printk(KERN_INFO "Bus #%d is %s (node %d)\n", | 
|  | m->mpc_busid, name, quad); | 
|  | } | 
|  |  | 
|  | int quad_local_to_mp_bus_id [NR_CPUS/4][4]; | 
|  |  | 
|  | /* x86_quirks member */ | 
|  | static void mpc_oem_pci_bus(struct mpc_config_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->mpc_busid; | 
|  | } | 
|  |  | 
|  | static void __init MP_translation_info(struct mpc_config_translation *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 mp_config_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->oem_signature, MPC_OEM_SIGNATURE, 4)) { | 
|  | printk(KERN_WARNING | 
|  | "SMP mpc oemtable: bad signature [%c%c%c%c]!\n", | 
|  | oemtable->oem_signature[0], oemtable->oem_signature[1], | 
|  | oemtable->oem_signature[2], oemtable->oem_signature[3]); | 
|  | return; | 
|  | } | 
|  | if (mpf_checksum((unsigned char *)oemtable, oemtable->oem_length)) { | 
|  | printk(KERN_WARNING "SMP oem mptable: checksum error!\n"); | 
|  | return; | 
|  | } | 
|  | while (count < oemtable->oem_length) { | 
|  | switch (*oemptr) { | 
|  | case MP_TRANSLATION: | 
|  | { | 
|  | struct mpc_config_translation *m = | 
|  | (struct mpc_config_translation *)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 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, | 
|  | }; | 
|  |  | 
|  | void numaq_mps_oem_check(struct mp_config_table *mpc, char *oem, | 
|  | char *productid) | 
|  | { | 
|  | if (strncmp(oem, "IBM NUMA", 8)) | 
|  | printk("Warning!  Not a NUMA-Q system!\n"); | 
|  | else | 
|  | found_numaq = 1; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } |