1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright IBM Corp. 1999, 2023
4 */
5
6#include <linux/irqflags.h>
7#include <linux/spinlock.h>
8#include <linux/kernel.h>
9#include <linux/init.h>
10#include <linux/smp.h>
11#include <linux/cache.h>
12#include <asm/abs_lowcore.h>
13#include <asm/ctlreg.h>
14
15/*
16 * ctl_lock guards access to global control register contents which
17 * are kept in the control register save area within absolute lowcore
18 * at physical address zero.
19 */
20static DEFINE_SPINLOCK(system_ctl_lock);
21
22void system_ctlreg_lock(void)
23 __acquires(&system_ctl_lock)
24{
25 spin_lock(lock: &system_ctl_lock);
26}
27
28void system_ctlreg_unlock(void)
29 __releases(&system_ctl_lock)
30{
31 spin_unlock(lock: &system_ctl_lock);
32}
33
34static bool system_ctlreg_area_init __ro_after_init;
35
36void __init system_ctlreg_init_save_area(struct lowcore *lc)
37{
38 struct lowcore *abs_lc;
39
40 abs_lc = get_abs_lowcore();
41 __local_ctl_store(0, 15, lc->cregs_save_area);
42 __local_ctl_store(0, 15, abs_lc->cregs_save_area);
43 put_abs_lowcore(abs_lc);
44 system_ctlreg_area_init = true;
45}
46
47struct ctlreg_parms {
48 unsigned long andval;
49 unsigned long orval;
50 unsigned long val;
51 int request;
52 int cr;
53};
54
55static void ctlreg_callback(void *info)
56{
57 struct ctlreg_parms *pp = info;
58 struct ctlreg regs[16];
59
60 __local_ctl_store(0, 15, regs);
61 if (pp->request == CTLREG_LOAD) {
62 regs[pp->cr].val = pp->val;
63 } else {
64 regs[pp->cr].val &= pp->andval;
65 regs[pp->cr].val |= pp->orval;
66 }
67 __local_ctl_load(0, 15, regs);
68}
69
70static void system_ctlreg_update(void *info)
71{
72 unsigned long flags;
73
74 if (system_state == SYSTEM_BOOTING) {
75 /*
76 * For very early calls do not call on_each_cpu()
77 * since not everything might be setup.
78 */
79 local_irq_save(flags);
80 ctlreg_callback(info);
81 local_irq_restore(flags);
82 } else {
83 on_each_cpu(func: ctlreg_callback, info, wait: 1);
84 }
85}
86
87void system_ctlreg_modify(unsigned int cr, unsigned long data, int request)
88{
89 struct ctlreg_parms pp = { .cr = cr, .request = request, };
90 struct lowcore *abs_lc;
91
92 switch (request) {
93 case CTLREG_SET_BIT:
94 pp.orval = 1UL << data;
95 pp.andval = -1UL;
96 break;
97 case CTLREG_CLEAR_BIT:
98 pp.orval = 0;
99 pp.andval = ~(1UL << data);
100 break;
101 case CTLREG_LOAD:
102 pp.val = data;
103 break;
104 }
105 if (system_ctlreg_area_init) {
106 system_ctlreg_lock();
107 abs_lc = get_abs_lowcore();
108 if (request == CTLREG_LOAD) {
109 abs_lc->cregs_save_area[cr].val = pp.val;
110 } else {
111 abs_lc->cregs_save_area[cr].val &= pp.andval;
112 abs_lc->cregs_save_area[cr].val |= pp.orval;
113 }
114 put_abs_lowcore(abs_lc);
115 system_ctlreg_update(info: &pp);
116 system_ctlreg_unlock();
117 } else {
118 system_ctlreg_update(info: &pp);
119 }
120}
121EXPORT_SYMBOL(system_ctlreg_modify);
122

source code of linux/arch/s390/kernel/ctlreg.c