summaryrefslogtreecommitdiffstats
path: root/arch/s390/kernel/ctlreg.c
blob: 8cc26cf2c64ac34a791e94c63489497aaaf3882d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// 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);