| /* |
| * Secondary cpu support |
| * |
| * Copyright 2016 Suraj Jitindar Singh, IBM. |
| * |
| * This work is licensed under the terms of the GNU LGPL, version 2. |
| */ |
| |
| #include <alloc.h> |
| #include <devicetree.h> |
| #include <asm/atomic.h> |
| #include <asm/barrier.h> |
| #include <asm/processor.h> |
| #include <asm/time.h> |
| #include <asm/setup.h> |
| #include <asm/opal.h> |
| #include <asm/hcall.h> |
| #include <asm/rtas.h> |
| #include <asm/smp.h> |
| |
| struct secondary_entry_data { |
| secondary_entry_fn entry; |
| }; |
| |
| int nr_cpus_online = 1; |
| |
| static void stop_self(int cpu_id) |
| { |
| if (machine_is_powernv()) { |
| if (opal_call(OPAL_RETURN_CPU, 0, 0, 0) != OPAL_SUCCESS) { |
| printf("OPAL_RETURN_CPU failed\n"); |
| } |
| } else { |
| rtas_stop_self(); |
| } |
| |
| printf("failed to stop cpu %d\n", cpu_id); |
| assert(0); |
| } |
| |
| void main_secondary(struct cpu *cpu); |
| void main_secondary(struct cpu *cpu) |
| { |
| mtspr(SPR_SPRG0, (unsigned long)cpu); |
| __current_cpu = cpu; |
| |
| enable_mcheck(); |
| |
| cpu_init_ipis(); |
| |
| atomic_fetch_inc(&nr_cpus_online); |
| |
| cpu->entry(cpu->server_no); |
| |
| mb(); |
| atomic_fetch_dec(&nr_cpus_online); |
| |
| stop_self(cpu->server_no); |
| } |
| |
| enum OpalThreadStatus { |
| OPAL_THREAD_INACTIVE = 0x0, |
| OPAL_THREAD_STARTED = 0x1, |
| OPAL_THREAD_UNAVAILABLE = 0x2 /* opal-v3 */ |
| }; |
| |
| #define H_EOI 0x64 |
| #define H_CPPR 0x68 |
| #define H_IPI 0x6c |
| #define H_XIRR 0x74 |
| |
| static void (*ipi_fn)(struct pt_regs *regs, void *data); |
| |
| static void dbell_handler(struct pt_regs *regs, void *data) |
| { |
| /* sync */ |
| ipi_fn(regs, data); |
| } |
| |
| static void extint_handler(struct pt_regs *regs, void *data) |
| { |
| int32_t xirr; |
| int32_t xisr; |
| int64_t rc; |
| |
| asm volatile("mr r3,%1 ; sc 1 ; mr %0,r4" : "=r"(xirr) : "r"(H_XIRR)); |
| xisr = xirr & 0xffffff; |
| |
| if (xisr == 2) { /* IPI */ |
| rc = hcall(H_IPI, smp_processor_id(), 0xff); |
| assert(rc == H_SUCCESS); |
| } |
| |
| xirr |= (5 << 24); |
| rc = hcall(H_EOI, xirr); |
| assert(rc == H_SUCCESS); |
| |
| /* lower IPI */ |
| ipi_fn(regs, data); |
| } |
| |
| void cpu_init_ipis(void) |
| { |
| if (machine_is_powernv()) { |
| /* skiboot can leave some messages set */ |
| unsigned long rb = (5 << (63-36)); |
| asm volatile("msgclr %0" :: "r"(rb) : "memory"); |
| } |
| } |
| |
| void local_ipi_enable(void) |
| { |
| if (machine_is_pseries()) { |
| hcall(H_CPPR, 5); |
| } |
| } |
| |
| void local_ipi_disable(void) |
| { |
| if (machine_is_pseries()) { |
| hcall(H_CPPR, 0); |
| } |
| } |
| |
| void register_ipi(void (*fn)(struct pt_regs *, void *), void *data) |
| { |
| ipi_fn = fn; |
| if (machine_is_powernv()) { |
| handle_exception(0xe80, &dbell_handler, data); |
| } else { |
| handle_exception(0x500, &extint_handler, data); |
| } |
| } |
| |
| void unregister_ipi(void) |
| { |
| if (machine_is_powernv()) { |
| handle_exception(0xe80, NULL, NULL); |
| } else { |
| handle_exception(0x500, NULL, NULL); |
| } |
| } |
| |
| void send_ipi(int cpu_id) |
| { |
| if (machine_is_powernv()) { |
| unsigned long rb = (5 << (63-36)) | cpu_id; |
| asm volatile("lwsync" ::: "memory"); |
| asm volatile("msgsnd %0" :: "r"(rb) : "memory"); |
| } else { |
| hcall(H_IPI, cpu_id, 4); |
| } |
| } |
| |
| static int nr_started = 1; |
| |
| extern void start_secondary(uint64_t server_no); /* asm entry point */ |
| |
| static bool cpu_is_running(int cpu_id) |
| { |
| if (machine_is_powernv()) { |
| int64_t ret; |
| uint8_t status; |
| |
| ret = opal_call(OPAL_QUERY_CPU_STATUS, cpu_id, (unsigned long)&status, 0); |
| if (ret != OPAL_SUCCESS) { |
| printf("OPAL_QUERY_CPU_STATUS failed for cpu %d\n", cpu_id); |
| return false; |
| } |
| return (status != OPAL_THREAD_INACTIVE); |
| } else { |
| uint32_t query_token; |
| int outputs[1], ret; |
| |
| ret = rtas_token("query-cpu-stopped-state", &query_token); |
| if (ret != 0) { |
| printf("rtas token query-cpu-stopped-state failed\n"); |
| return false; |
| } |
| |
| ret = rtas_call(query_token, 1, 2, outputs, cpu_id); |
| if (ret) { |
| printf("query-cpu-stopped-state failed for cpu %d\n", cpu_id); |
| return ret; |
| } |
| if (outputs[0]) /* cpu not in stopped state */ |
| return true; |
| return false; |
| } |
| } |
| |
| /* |
| * Start stopped thread cpu_id at entry |
| * Returns: <0 on failure to start stopped cpu |
| * 0 on success |
| * >0 on cpu not in stopped state |
| */ |
| static int start_thread(int cpu_id, secondary_entry_fn entry) |
| { |
| struct cpu *cpu; |
| uint64_t tb; |
| |
| if (nr_started >= NR_CPUS) { |
| /* Reached limit */ |
| return -1; |
| } |
| |
| if (cpu_id == smp_processor_id()) { |
| /* Boot CPU already started */ |
| return -1; |
| } |
| |
| tb = get_tb(); |
| while (cpu_is_running(cpu_id)) { |
| if (get_tb() - tb > 3*tb_hz) { |
| printf("Unable to start running CPU:%d\n", cpu_id); |
| return 1; |
| } |
| } |
| |
| cpu = &cpus[nr_started]; |
| nr_started++; |
| |
| cpu_init(cpu, cpu_id); |
| cpu->entry = entry; |
| |
| if (machine_is_powernv()) { |
| if (opal_call(OPAL_START_CPU, cpu_id, (unsigned long)start_secondary, 0) != OPAL_SUCCESS) { |
| printf("failed to start cpu %d\n", cpu_id); |
| return -1; |
| } |
| } else { |
| uint32_t start_token; |
| int ret; |
| |
| ret = rtas_token("start-cpu", &start_token); |
| assert(ret == 0); |
| |
| ret = rtas_call(start_token, 3, 1, NULL, cpu_id, start_secondary, cpu_id); |
| if (ret) { |
| printf("failed to start cpu %d\n", cpu_id); |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Start all stopped threads (vcpus) on cpu_node |
| * Returns: Number of stopped cpus which were successfully started |
| */ |
| static void start_core(int cpu_node, secondary_entry_fn entry) |
| { |
| int len, i, nr_threads; |
| const struct fdt_property *prop; |
| u32 *threads; |
| |
| /* Get the id array of threads on this cpu_node */ |
| prop = fdt_get_property(dt_fdt(), cpu_node, |
| "ibm,ppc-interrupt-server#s", &len); |
| assert(prop); |
| |
| nr_threads = len >> 2; /* Divide by 4 since 4 bytes per thread */ |
| |
| threads = (u32 *)prop->data; /* Array of valid ids */ |
| |
| for (i = 0; i < nr_threads; i++) |
| start_thread(fdt32_to_cpu(threads[i]), entry); |
| } |
| |
| static void start_each_secondary(int fdtnode, u64 regval __unused, void *info) |
| { |
| struct secondary_entry_data *datap = info; |
| |
| start_core(fdtnode, datap->entry); |
| } |
| |
| /* |
| * Start all stopped cpus on the guest at entry with register 3 set to r3 |
| * We expect that we come in with only one thread currently started |
| * Returns: TRUE on success |
| * FALSE on failure |
| */ |
| bool start_all_cpus(secondary_entry_fn entry) |
| { |
| struct secondary_entry_data data = { entry }; |
| uint64_t tb; |
| int ret; |
| |
| assert(nr_cpus_online == 1); |
| assert(nr_started == 1); |
| ret = dt_for_each_cpu_node(start_each_secondary, &data); |
| assert(ret == 0); |
| assert(nr_started == nr_cpus_present); |
| |
| tb = get_tb(); |
| while (nr_cpus_online < nr_cpus_present) { |
| if (get_tb() - tb > 3*tb_hz) { |
| printf("failed to start all secondaries\n"); |
| assert(0); |
| } |
| cpu_relax(); |
| } |
| |
| return 1; |
| } |
| |
| /* |
| * Start stopped thread cpu_id at entry |
| * Returns: <0 on failure to start stopped cpu |
| * 0 on success |
| * >0 on cpu not in stopped state |
| */ |
| static int wait_thread(int cpu_id) |
| { |
| uint64_t tb; |
| |
| /* Skip the caller */ |
| if (cpu_id == smp_processor_id()) { |
| return 0; |
| } |
| |
| tb = get_tb(); |
| while (cpu_is_running(cpu_id)) { |
| if (get_tb() - tb > 3*tb_hz) { |
| printf("Timeout waiting to stop CPU:%d\n", cpu_id); |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Wait for running threads (vcpus) on cpu_node to stop |
| */ |
| static void wait_core(int cpu_node) |
| { |
| int len, i, nr_threads; |
| const struct fdt_property *prop; |
| u32 *threads; |
| |
| /* Get the id array of threads on this cpu_node */ |
| prop = fdt_get_property(dt_fdt(), cpu_node, |
| "ibm,ppc-interrupt-server#s", &len); |
| assert(prop); |
| |
| nr_threads = len >> 2; /* Divide by 4 since 4 bytes per thread */ |
| |
| threads = (u32 *)prop->data; /* Array of valid ids */ |
| |
| for (i = 0; i < nr_threads; i++) |
| wait_thread(fdt32_to_cpu(threads[i])); |
| } |
| |
| static void wait_each_secondary(int fdtnode, u64 regval __unused, void *info) |
| { |
| wait_core(fdtnode); |
| } |
| |
| void stop_all_cpus(void) |
| { |
| while (nr_cpus_online > 1) |
| cpu_relax(); |
| |
| dt_for_each_cpu_node(wait_each_secondary, NULL); |
| mb(); |
| nr_started = 1; |
| } |