blob: 8cc26cf2c64ac34a791e94c63489497aaaf3882d [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright IBM Corp. 1999, 2023
*/
#include <linux/irqflags.h>
#include <linux/spinlock.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);