root/arch/s390/kernel/ctlreg.c
// 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);