diff options
Diffstat (limited to 'arch/s390/kernel/topology.c')
-rw-r--r-- | arch/s390/kernel/topology.c | 272 |
1 files changed, 272 insertions, 0 deletions
diff --git a/arch/s390/kernel/topology.c b/arch/s390/kernel/topology.c new file mode 100644 index 00000000000..369dc1c3bd1 --- /dev/null +++ b/arch/s390/kernel/topology.c @@ -0,0 +1,272 @@ +/* + * arch/s390/kernel/topology.c + * + * Copyright IBM Corp. 2007 + * Author(s): Heiko Carstens <heiko.carstens@de.ibm.com> + */ + +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/bootmem.h> +#include <linux/sched.h> +#include <linux/workqueue.h> +#include <linux/cpu.h> +#include <linux/smp.h> +#include <asm/delay.h> +#include <asm/s390_ext.h> +#include <asm/sysinfo.h> + +#define CPU_BITS 64 + +struct tl_cpu { + unsigned char reserved[6]; + unsigned short origin; + unsigned long mask[CPU_BITS / BITS_PER_LONG]; +}; + +struct tl_container { + unsigned char reserved[8]; +}; + +union tl_entry { + unsigned char nl; + struct tl_cpu cpu; + struct tl_container container; +}; + +#define NR_MAG 6 + +struct tl_info { + unsigned char reserved0[2]; + unsigned short length; + unsigned char mag[NR_MAG]; + unsigned char reserved1; + unsigned char mnest; + unsigned char reserved2[4]; + union tl_entry tle[0]; +}; + +struct core_info { + struct core_info *next; + cpumask_t mask; +}; + +static void topology_work_fn(struct work_struct *work); +static struct tl_info *tl_info; +static struct core_info core_info; +static int machine_has_topology; +static int machine_has_topology_irq; +static struct timer_list topology_timer; +static void set_topology_timer(void); +static DECLARE_WORK(topology_work, topology_work_fn); + +cpumask_t cpu_coregroup_map(unsigned int cpu) +{ + struct core_info *core = &core_info; + cpumask_t mask; + + cpus_clear(mask); + if (!machine_has_topology) + return cpu_present_map; + mutex_lock(&smp_cpu_state_mutex); + while (core) { + if (cpu_isset(cpu, core->mask)) { + mask = core->mask; + break; + } + core = core->next; + } + mutex_unlock(&smp_cpu_state_mutex); + if (cpus_empty(mask)) + mask = cpumask_of_cpu(cpu); + return mask; +} + +static void add_cpus_to_core(struct tl_cpu *tl_cpu, struct core_info *core) +{ + unsigned int cpu; + + for (cpu = find_first_bit(&tl_cpu->mask[0], CPU_BITS); + cpu < CPU_BITS; + cpu = find_next_bit(&tl_cpu->mask[0], CPU_BITS, cpu + 1)) + { + unsigned int rcpu, lcpu; + + rcpu = CPU_BITS - 1 - cpu + tl_cpu->origin; + for_each_present_cpu(lcpu) { + if (__cpu_logical_map[lcpu] == rcpu) + cpu_set(lcpu, core->mask); + } + } +} + +static void clear_cores(void) +{ + struct core_info *core = &core_info; + + while (core) { + cpus_clear(core->mask); + core = core->next; + } +} + +static union tl_entry *next_tle(union tl_entry *tle) +{ + if (tle->nl) + return (union tl_entry *)((struct tl_container *)tle + 1); + else + return (union tl_entry *)((struct tl_cpu *)tle + 1); +} + +static void tl_to_cores(struct tl_info *info) +{ + union tl_entry *tle, *end; + struct core_info *core = &core_info; + + mutex_lock(&smp_cpu_state_mutex); + clear_cores(); + tle = (union tl_entry *)&info->tle; + end = (union tl_entry *)((unsigned long)info + info->length); + while (tle < end) { + switch (tle->nl) { + case 5: + case 4: + case 3: + case 2: + break; + case 1: + core = core->next; + break; + case 0: + add_cpus_to_core(&tle->cpu, core); + break; + default: + clear_cores(); + machine_has_topology = 0; + return; + } + tle = next_tle(tle); + } + mutex_unlock(&smp_cpu_state_mutex); +} + +static int ptf(void) +{ + int rc; + + asm volatile( + " .insn rre,0xb9a20000,%1,%1\n" + " ipm %0\n" + " srl %0,28\n" + : "=d" (rc) + : "d" (2UL) : "cc"); + return rc; +} + +void arch_update_cpu_topology(void) +{ + struct tl_info *info = tl_info; + struct sys_device *sysdev; + int cpu; + + if (!machine_has_topology) + return; + ptf(); + stsi(info, 15, 1, 2); + tl_to_cores(info); + for_each_online_cpu(cpu) { + sysdev = get_cpu_sysdev(cpu); + kobject_uevent(&sysdev->kobj, KOBJ_CHANGE); + } +} + +static void topology_work_fn(struct work_struct *work) +{ + arch_reinit_sched_domains(); +} + +static void topology_timer_fn(unsigned long ignored) +{ + if (ptf()) + schedule_work(&topology_work); + set_topology_timer(); +} + +static void set_topology_timer(void) +{ + topology_timer.function = topology_timer_fn; + topology_timer.data = 0; + topology_timer.expires = jiffies + 60 * HZ; + add_timer(&topology_timer); +} + +static void topology_interrupt(__u16 code) +{ + schedule_work(&topology_work); +} + +static int __init init_topology_update(void) +{ + int rc; + + if (!machine_has_topology) + return 0; + init_timer(&topology_timer); + if (machine_has_topology_irq) { + rc = register_external_interrupt(0x2005, topology_interrupt); + if (rc) + return rc; + ctl_set_bit(0, 8); + } + else + set_topology_timer(); + return 0; +} +__initcall(init_topology_update); + +void __init s390_init_cpu_topology(void) +{ + unsigned long long facility_bits; + struct tl_info *info; + struct core_info *core; + int nr_cores; + int i; + + if (stfle(&facility_bits, 1) <= 0) + return; + if (!(facility_bits & (1ULL << 52)) || !(facility_bits & (1ULL << 61))) + return; + machine_has_topology = 1; + + if (facility_bits & (1ULL << 51)) + machine_has_topology_irq = 1; + + tl_info = alloc_bootmem_pages(PAGE_SIZE); + if (!tl_info) + goto error; + info = tl_info; + stsi(info, 15, 1, 2); + + nr_cores = info->mag[NR_MAG - 2]; + for (i = 0; i < info->mnest - 2; i++) + nr_cores *= info->mag[NR_MAG - 3 - i]; + + printk(KERN_INFO "CPU topology:"); + for (i = 0; i < NR_MAG; i++) + printk(" %d", info->mag[i]); + printk(" / %d\n", info->mnest); + + core = &core_info; + for (i = 0; i < nr_cores; i++) { + core->next = alloc_bootmem(sizeof(struct core_info)); + core = core->next; + if (!core) + goto error; + } + return; +error: + machine_has_topology = 0; + machine_has_topology_irq = 0; +} |