|  | /* | 
|  | * 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> | 
|  | * | 
|  | */ | 
|  |  | 
|  | #define pr_fmt(fmt) "summit: %s: " fmt, __func__ | 
|  |  | 
|  | #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/gfp.h> | 
|  | #include <linux/smp.h> | 
|  |  | 
|  | static unsigned summit_get_apic_id(unsigned long x) | 
|  | { | 
|  | return (x >> 24) & 0xFF; | 
|  | } | 
|  |  | 
|  | static inline void summit_send_IPI_mask(const struct cpumask *mask, int vector) | 
|  | { | 
|  | default_send_IPI_mask_sequence_logical(mask, vector); | 
|  | } | 
|  |  | 
|  | static void summit_send_IPI_allbutself(int vector) | 
|  | { | 
|  | default_send_IPI_mask_allbutself_logical(cpu_online_mask, vector); | 
|  | } | 
|  |  | 
|  | static void summit_send_IPI_all(int vector) | 
|  | { | 
|  | summit_send_IPI_mask(cpu_online_mask, vector); | 
|  | } | 
|  |  | 
|  | #include <asm/tsc.h> | 
|  |  | 
|  | extern int use_cyclone; | 
|  |  | 
|  | #ifdef CONFIG_X86_SUMMIT_NUMA | 
|  | static void setup_summit(void); | 
|  | #else | 
|  | static inline void setup_summit(void) {} | 
|  | #endif | 
|  |  | 
|  | static 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 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); | 
|  | } | 
|  |  | 
|  | #define SUMMIT_APIC_DFR_VALUE	(APIC_DFR_CLUSTER) | 
|  |  | 
|  | static const struct cpumask *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(0); | 
|  | } | 
|  |  | 
|  | static unsigned long summit_check_apicid_used(physid_mask_t *map, int apicid) | 
|  | { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* we don't use the phys_cpu_present_map to indicate apicid presence */ | 
|  | static unsigned long summit_check_apicid_present(int bit) | 
|  | { | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | static int summit_early_logical_apicid(int cpu) | 
|  | { | 
|  | int count = 0; | 
|  | u8 my_id = early_per_cpu(x86_cpu_to_apicid, cpu); | 
|  | 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 = early_per_cpu(x86_cpu_to_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); | 
|  | return my_cluster | (1UL << count); | 
|  | } | 
|  |  | 
|  | static void summit_init_apic_ldr(void) | 
|  | { | 
|  | int cpu = smp_processor_id(); | 
|  | unsigned long id = early_per_cpu(x86_cpu_to_logical_apicid, cpu); | 
|  | unsigned long val; | 
|  |  | 
|  | 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 int summit_apic_id_registered(void) | 
|  | { | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | static void summit_setup_apic_routing(void) | 
|  | { | 
|  | pr_info("Enabling APIC mode:  Summit.  Using %d I/O APICs\n", | 
|  | nr_ioapics); | 
|  | } | 
|  |  | 
|  | static 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 void summit_ioapic_phys_id_map(physid_mask_t *phys_id_map, physid_mask_t *retmap) | 
|  | { | 
|  | /* For clustered we don't have a good way to do this yet - hack */ | 
|  | physids_promote(0x0FL, retmap); | 
|  | } | 
|  |  | 
|  | static void summit_apicid_to_cpu_present(int apicid, physid_mask_t *retmap) | 
|  | { | 
|  | physid_set_mask_of_physid(0, retmap); | 
|  | } | 
|  |  | 
|  | static int summit_check_phys_apicid_present(int physical_apicid) | 
|  | { | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | static inline int | 
|  | summit_cpu_mask_to_apicid(const struct cpumask *cpumask, unsigned int *dest_id) | 
|  | { | 
|  | unsigned int round = 0; | 
|  | unsigned int cpu, apicid = 0; | 
|  |  | 
|  | /* | 
|  | * The cpus in the mask must all be on the apic cluster. | 
|  | */ | 
|  | for_each_cpu_and(cpu, cpumask, cpu_online_mask) { | 
|  | int new_apicid = early_per_cpu(x86_cpu_to_logical_apicid, cpu); | 
|  |  | 
|  | if (round && APIC_CLUSTER(apicid) != APIC_CLUSTER(new_apicid)) { | 
|  | pr_err("Not a valid mask!\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  | apicid |= new_apicid; | 
|  | round++; | 
|  | } | 
|  | if (!round) | 
|  | return -EINVAL; | 
|  | *dest_id = apicid; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int | 
|  | summit_cpu_mask_to_apicid_and(const struct cpumask *inmask, | 
|  | const struct cpumask *andmask, | 
|  | unsigned int *apicid) | 
|  | { | 
|  | cpumask_var_t cpumask; | 
|  | *apicid = early_per_cpu(x86_cpu_to_logical_apicid, 0); | 
|  |  | 
|  | if (!alloc_cpumask_var(&cpumask, GFP_ATOMIC)) | 
|  | return 0; | 
|  |  | 
|  | cpumask_and(cpumask, inmask, andmask); | 
|  | summit_cpu_mask_to_apicid(cpumask, apicid); | 
|  |  | 
|  | free_cpumask_var(cpumask); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * 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 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; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_X86_SUMMIT_NUMA | 
|  | static struct rio_table_hdr *rio_table_hdr; | 
|  | static struct scal_detail   *scal_devs[MAX_NUMNODES]; | 
|  | static struct rio_detail    *rio_devs[MAX_NUMNODES*4]; | 
|  |  | 
|  | #ifndef CONFIG_X86_NUMAQ | 
|  | static int mp_bus_id_to_node[MAX_MP_BUSSES]; | 
|  | #endif | 
|  |  | 
|  | static int 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) { | 
|  | pr_err("Couldn't find owner Cyclone for Winnipeg!\n"); | 
|  | 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) { | 
|  | pr_err("Couldn't find owner Twister for Cyclone!\n"); | 
|  | 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: | 
|  | pr_info("Unsupported Winnipeg type!\n"); | 
|  | return last_bus; | 
|  | } | 
|  |  | 
|  | for (bus = last_bus; bus < last_bus + num_buses; bus++) | 
|  | mp_bus_id_to_node[bus] = node; | 
|  | return bus; | 
|  | } | 
|  |  | 
|  | static int build_detail_arrays(void) | 
|  | { | 
|  | unsigned long ptr; | 
|  | int i, scal_detail_size, rio_detail_size; | 
|  |  | 
|  | if (rio_table_hdr->num_scal_dev > MAX_NUMNODES) { | 
|  | pr_warn("MAX_NUMNODES too low!  Defined as %d, but system has %d nodes\n", | 
|  | MAX_NUMNODES, rio_table_hdr->num_scal_dev); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | switch (rio_table_hdr->version) { | 
|  | default: | 
|  | pr_warn("Invalid Rio Grande Table Version: %d\n", | 
|  | 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 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) { | 
|  | pr_err("Unable to locate Rio Grande Table in EBDA - bailing!\n"); | 
|  | 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 | 
|  |  | 
|  | static struct apic apic_summit = { | 
|  |  | 
|  | .name				= "summit", | 
|  | .probe				= probe_summit, | 
|  | .acpi_madt_oem_check		= summit_acpi_madt_oem_check, | 
|  | .apic_id_valid			= default_apic_id_valid, | 
|  | .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	= flat_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, | 
|  | .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_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, | 
|  |  | 
|  | .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, | 
|  | .eoi_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, | 
|  |  | 
|  | .x86_32_early_logical_apicid	= summit_early_logical_apicid, | 
|  | }; | 
|  |  | 
|  | apic_driver(apic_summit); |