| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright IBM Corp. 1999, 2023 | |
| */ | |
| #include <linux/irqflags.h> | |
| #include <linux/spinlock.h> | |
| #include <linux/export.h> | |
| #include <linux/kernel.h> | |
| #include <linux/init.h> | |
| #include <linux/smp.h> | |
| #include <linux/cache.h> | |
| #include <asm/abs_lowcore.h> | |
| #include <asm/ctlreg.h> | |
| /* | |
| * ctl_lock guards access to global control register contents which | |
| * are kept in the control register save area within absolute lowcore | |
| * at physical address zero. | |
| */ | |
| static DEFINE_SPINLOCK(system_ctl_lock); | |
| void system_ctlreg_lock(void) | |
| __acquires(&system_ctl_lock) | |
| { | |
| spin_lock(&system_ctl_lock); | |
| } | |
| void system_ctlreg_unlock(void) | |
| __releases(&system_ctl_lock) | |
| { | |
| spin_unlock(&system_ctl_lock); | |
| } | |
| static bool system_ctlreg_area_init __ro_after_init; | |
| void __init system_ctlreg_init_save_area(struct lowcore *lc) | |
| { | |
| struct lowcore *abs_lc; | |
| abs_lc = get_abs_lowcore(); | |
| __local_ctl_store(0, 15, lc->cregs_save_area); | |
| __local_ctl_store(0, 15, abs_lc->cregs_save_area); | |
| put_abs_lowcore(abs_lc); | |
| system_ctlreg_area_init = true; | |
| } | |
| struct ctlreg_parms { | |
| unsigned long andval; | |
| unsigned long orval; | |
| unsigned long val; | |
| int request; | |
| int cr; | |
| }; | |
| static void ctlreg_callback(void *info) | |
| { | |
| struct ctlreg_parms *pp = info; | |
| struct ctlreg regs[16]; | |
| __local_ctl_store(0, 15, regs); | |
| if (pp->request == CTLREG_LOAD) { | |
| regs[pp->cr].val = pp->val; | |
| } else { | |
| regs[pp->cr].val &= pp->andval; | |
| regs[pp->cr].val |= pp->orval; | |
| } | |
| __local_ctl_load(0, 15, regs); | |
| } | |
| static void system_ctlreg_update(void *info) | |
| { | |
| unsigned long flags; | |
| if (system_state == SYSTEM_BOOTING) { | |
| /* | |
| * For very early calls do not call on_each_cpu() | |
| * since not everything might be setup. | |
| */ | |
| local_irq_save(flags); | |
| ctlreg_callback(info); | |
| local_irq_restore(flags); | |
| } else { | |
| on_each_cpu(ctlreg_callback, info, 1); | |
| } | |
| } | |
| void system_ctlreg_modify(unsigned int cr, unsigned long data, int request) | |
| { | |
| struct ctlreg_parms pp = { .cr = cr, .request = request, }; | |
| struct lowcore *abs_lc; | |
| switch (request) { | |
| case CTLREG_SET_BIT: | |
| pp.orval = 1UL << data; | |
| pp.andval = -1UL; | |
| break; | |
| case CTLREG_CLEAR_BIT: | |
| pp.orval = 0; | |
| pp.andval = ~(1UL << data); | |
| break; | |
| case CTLREG_LOAD: | |
| pp.val = data; | |
| break; | |
| } | |
| if (system_ctlreg_area_init) { | |
| system_ctlreg_lock(); | |
| abs_lc = get_abs_lowcore(); | |
| if (request == CTLREG_LOAD) { | |
| abs_lc->cregs_save_area[cr].val = pp.val; | |
| } else { | |
| abs_lc->cregs_save_area[cr].val &= pp.andval; | |
| abs_lc->cregs_save_area[cr].val |= pp.orval; | |
| } | |
| put_abs_lowcore(abs_lc); | |
| system_ctlreg_update(&pp); | |
| system_ctlreg_unlock(); | |
| } else { | |
| system_ctlreg_update(&pp); | |
| } | |
| } | |
| EXPORT_SYMBOL(system_ctlreg_modify); |