Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
22362a0e MS |
2 | /* |
3 | * Copyright IBM Corp. 2015 | |
4 | * Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com> | |
5 | */ | |
d5ab7a34 | 6 | |
22362a0e | 7 | #include <linux/kernel.h> |
d5ab7a34 HC |
8 | #include <asm/processor.h> |
9 | #include <asm/lowcore.h> | |
22362a0e MS |
10 | #include <asm/ebcdic.h> |
11 | #include <asm/irq.h> | |
d5ab7a34 HC |
12 | #include "sclp.h" |
13 | #include "sclp_rw.h" | |
22362a0e | 14 | |
401a0b8c VG |
15 | char sclp_early_sccb[PAGE_SIZE] __aligned(PAGE_SIZE) __section(.data); |
16 | int sclp_init_state __section(.data) = sclp_init_state_uninitialized; | |
0b0d1173 CI |
17 | /* |
18 | * Used to keep track of the size of the event masks. Qemu until version 2.11 | |
19 | * only supports 4 and needs a workaround. | |
20 | */ | |
dccccd33 | 21 | bool sclp_mask_compat_mode __section(.data); |
76fdf141 | 22 | |
d5ab7a34 | 23 | void sclp_early_wait_irq(void) |
22362a0e | 24 | { |
d5ab7a34 | 25 | unsigned long psw_mask, addr; |
22362a0e | 26 | psw_t psw_ext_save, psw_wait; |
742dc577 | 27 | union ctlreg0 cr0, cr0_new; |
22362a0e | 28 | |
742dc577 HC |
29 | __ctl_store(cr0.val, 0, 0); |
30 | cr0_new.val = cr0.val & ~CR0_IRQ_SUBCLASS_MASK; | |
31 | cr0_new.lap = 0; | |
32 | cr0_new.sssm = 1; | |
33 | __ctl_load(cr0_new.val, 0, 0); | |
22362a0e MS |
34 | |
35 | psw_ext_save = S390_lowcore.external_new_psw; | |
f07f21b3 | 36 | psw_mask = __extract_psw(); |
22362a0e MS |
37 | S390_lowcore.external_new_psw.mask = psw_mask; |
38 | psw_wait.mask = psw_mask | PSW_MASK_EXT | PSW_MASK_WAIT; | |
39 | S390_lowcore.ext_int_code = 0; | |
40 | ||
41 | do { | |
42 | asm volatile( | |
43 | " larl %[addr],0f\n" | |
44 | " stg %[addr],%[psw_wait_addr]\n" | |
45 | " stg %[addr],%[psw_ext_addr]\n" | |
46 | " lpswe %[psw_wait]\n" | |
47 | "0:\n" | |
48 | : [addr] "=&d" (addr), | |
49 | [psw_wait_addr] "=Q" (psw_wait.addr), | |
50 | [psw_ext_addr] "=Q" (S390_lowcore.external_new_psw.addr) | |
51 | : [psw_wait] "Q" (psw_wait) | |
52 | : "cc", "memory"); | |
53 | } while (S390_lowcore.ext_int_code != EXT_IRQ_SERVICE_SIG); | |
54 | ||
22362a0e | 55 | S390_lowcore.external_new_psw = psw_ext_save; |
742dc577 | 56 | __ctl_load(cr0.val, 0, 0); |
22362a0e MS |
57 | } |
58 | ||
f694bb3a | 59 | int sclp_early_cmd(sclp_cmdw_t cmd, void *sccb) |
22362a0e | 60 | { |
d5ab7a34 HC |
61 | unsigned long flags; |
62 | int rc; | |
22362a0e | 63 | |
d5ab7a34 HC |
64 | raw_local_irq_save(flags); |
65 | rc = sclp_service_call(cmd, sccb); | |
66 | if (rc) | |
67 | goto out; | |
68 | sclp_early_wait_irq(); | |
69 | out: | |
70 | raw_local_irq_restore(flags); | |
71 | return rc; | |
22362a0e MS |
72 | } |
73 | ||
d5ab7a34 HC |
74 | struct write_sccb { |
75 | struct sccb_header header; | |
76 | struct msg_buf msg; | |
77 | } __packed; | |
78 | ||
3f975df6 | 79 | /* Output multi-line text using SCLP Message interface. */ |
d5ab7a34 | 80 | static void sclp_early_print_lm(const char *str, unsigned int len) |
22362a0e | 81 | { |
d5ab7a34 HC |
82 | unsigned char *ptr, *end, ch; |
83 | unsigned int count, offset; | |
84 | struct write_sccb *sccb; | |
85 | struct msg_buf *msg; | |
86 | struct mdb *mdb; | |
87 | struct mto *mto; | |
88 | struct go *go; | |
89 | ||
90 | sccb = (struct write_sccb *) &sclp_early_sccb; | |
91 | end = (unsigned char *) sccb + sizeof(sclp_early_sccb) - 1; | |
92 | memset(sccb, 0, sizeof(*sccb)); | |
93 | ptr = (unsigned char *) &sccb->msg.mdb.mto; | |
94 | offset = 0; | |
22362a0e | 95 | do { |
d5ab7a34 HC |
96 | for (count = sizeof(*mto); offset < len; count++) { |
97 | ch = str[offset++]; | |
98 | if ((ch == 0x0a) || (ptr + count > end)) | |
f0319748 | 99 | break; |
22362a0e MS |
100 | ptr[count] = _ascebc[ch]; |
101 | } | |
d5ab7a34 HC |
102 | mto = (struct mto *) ptr; |
103 | memset(mto, 0, sizeof(*mto)); | |
104 | mto->length = count; | |
105 | mto->type = 4; | |
106 | mto->line_type_flags = LNTPFLGS_ENDTEXT; | |
22362a0e | 107 | ptr += count; |
d5ab7a34 HC |
108 | } while ((offset < len) && (ptr + sizeof(*mto) <= end)); |
109 | len = ptr - (unsigned char *) sccb; | |
110 | sccb->header.length = len - offsetof(struct write_sccb, header); | |
111 | msg = &sccb->msg; | |
112 | msg->header.type = EVTYP_MSG; | |
113 | msg->header.length = len - offsetof(struct write_sccb, msg.header); | |
114 | mdb = &msg->mdb; | |
115 | mdb->header.type = 1; | |
116 | mdb->header.tag = 0xD4C4C240; | |
117 | mdb->header.revision_code = 1; | |
118 | mdb->header.length = len - offsetof(struct write_sccb, msg.mdb.header); | |
119 | go = &mdb->go; | |
120 | go->length = sizeof(*go); | |
121 | go->type = 1; | |
122 | sclp_early_cmd(SCLP_CMDW_WRITE_EVENT_DATA, sccb); | |
22362a0e MS |
123 | } |
124 | ||
d5ab7a34 HC |
125 | struct vt220_sccb { |
126 | struct sccb_header header; | |
127 | struct { | |
128 | struct evbuf_header header; | |
129 | char data[]; | |
130 | } msg; | |
131 | } __packed; | |
132 | ||
02407baa | 133 | /* Output multi-line text using SCLP VT220 interface. */ |
d5ab7a34 HC |
134 | static void sclp_early_print_vt220(const char *str, unsigned int len) |
135 | { | |
136 | struct vt220_sccb *sccb; | |
137 | ||
138 | sccb = (struct vt220_sccb *) &sclp_early_sccb; | |
139 | if (sizeof(*sccb) + len >= sizeof(sclp_early_sccb)) | |
02407baa | 140 | len = sizeof(sclp_early_sccb) - sizeof(*sccb); |
d5ab7a34 HC |
141 | memset(sccb, 0, sizeof(*sccb)); |
142 | memcpy(&sccb->msg.data, str, len); | |
02407baa HC |
143 | sccb->header.length = sizeof(*sccb) + len; |
144 | sccb->msg.header.length = sizeof(sccb->msg) + len; | |
d5ab7a34 HC |
145 | sccb->msg.header.type = EVTYP_VT220MSG; |
146 | sclp_early_cmd(SCLP_CMDW_WRITE_EVENT_DATA, sccb); | |
147 | } | |
148 | ||
149 | int sclp_early_set_event_mask(struct init_sccb *sccb, | |
0ee5f8dc CI |
150 | sccb_mask_t receive_mask, |
151 | sccb_mask_t send_mask) | |
d5ab7a34 | 152 | { |
0b0d1173 | 153 | retry: |
d5ab7a34 HC |
154 | memset(sccb, 0, sizeof(*sccb)); |
155 | sccb->header.length = sizeof(*sccb); | |
0b0d1173 CI |
156 | if (sclp_mask_compat_mode) |
157 | sccb->mask_length = SCLP_MASK_SIZE_COMPAT; | |
158 | else | |
159 | sccb->mask_length = sizeof(sccb_mask_t); | |
b8435635 CI |
160 | sccb_set_recv_mask(sccb, receive_mask); |
161 | sccb_set_send_mask(sccb, send_mask); | |
f694bb3a HC |
162 | if (sclp_early_cmd(SCLP_CMDW_WRITE_EVENT_MASK, sccb)) |
163 | return -EIO; | |
0b0d1173 CI |
164 | if ((sccb->header.response_code == 0x74f0) && !sclp_mask_compat_mode) { |
165 | sclp_mask_compat_mode = true; | |
166 | goto retry; | |
167 | } | |
f694bb3a HC |
168 | if (sccb->header.response_code != 0x20) |
169 | return -EIO; | |
170 | return 0; | |
d5ab7a34 HC |
171 | } |
172 | ||
173 | unsigned int sclp_early_con_check_linemode(struct init_sccb *sccb) | |
22362a0e | 174 | { |
b8435635 | 175 | if (!(sccb_get_sclp_send_mask(sccb) & EVTYP_OPCMD_MASK)) |
d5ab7a34 | 176 | return 0; |
b8435635 | 177 | if (!(sccb_get_sclp_recv_mask(sccb) & (EVTYP_MSG_MASK | EVTYP_PMSGCMD_MASK))) |
d5ab7a34 HC |
178 | return 0; |
179 | return 1; | |
180 | } | |
181 | ||
b8435635 CI |
182 | unsigned int sclp_early_con_check_vt220(struct init_sccb *sccb) |
183 | { | |
184 | if (sccb_get_sclp_send_mask(sccb) & EVTYP_VT220MSG_MASK) | |
185 | return 1; | |
186 | return 0; | |
187 | } | |
188 | ||
d5ab7a34 HC |
189 | static int sclp_early_setup(int disable, int *have_linemode, int *have_vt220) |
190 | { | |
191 | unsigned long receive_mask, send_mask; | |
192 | struct init_sccb *sccb; | |
193 | int rc; | |
194 | ||
b8435635 CI |
195 | BUILD_BUG_ON(sizeof(struct init_sccb) > PAGE_SIZE); |
196 | ||
d5ab7a34 HC |
197 | *have_linemode = *have_vt220 = 0; |
198 | sccb = (struct init_sccb *) &sclp_early_sccb; | |
199 | receive_mask = disable ? 0 : EVTYP_OPCMD_MASK; | |
200 | send_mask = disable ? 0 : EVTYP_VT220MSG_MASK | EVTYP_MSG_MASK; | |
201 | rc = sclp_early_set_event_mask(sccb, receive_mask, send_mask); | |
202 | if (rc) | |
203 | return rc; | |
204 | *have_linemode = sclp_early_con_check_linemode(sccb); | |
b8435635 | 205 | *have_vt220 = !!(sccb_get_send_mask(sccb) & EVTYP_VT220MSG_MASK); |
d5ab7a34 | 206 | return rc; |
3f975df6 SS |
207 | } |
208 | ||
02407baa HC |
209 | /* |
210 | * Output one or more lines of text on the SCLP console (VT220 and / | |
211 | * or line-mode). | |
3f975df6 | 212 | */ |
55a5542a | 213 | void __sclp_early_printk(const char *str, unsigned int len, unsigned int force) |
3f975df6 | 214 | { |
d5ab7a34 HC |
215 | int have_linemode, have_vt220; |
216 | ||
55a5542a | 217 | if (!force && sclp_init_state != sclp_init_state_uninitialized) |
76fdf141 | 218 | return; |
d5ab7a34 | 219 | if (sclp_early_setup(0, &have_linemode, &have_vt220) != 0) |
3f975df6 SS |
220 | return; |
221 | if (have_linemode) | |
d5ab7a34 | 222 | sclp_early_print_lm(str, len); |
3f975df6 | 223 | if (have_vt220) |
d5ab7a34 HC |
224 | sclp_early_print_vt220(str, len); |
225 | sclp_early_setup(1, &have_linemode, &have_vt220); | |
22362a0e | 226 | } |
89175cf7 | 227 | |
d5ab7a34 | 228 | void sclp_early_printk(const char *str) |
89175cf7 | 229 | { |
55a5542a GS |
230 | __sclp_early_printk(str, strlen(str), 0); |
231 | } | |
232 | ||
233 | void sclp_early_printk_force(const char *str) | |
234 | { | |
235 | __sclp_early_printk(str, strlen(str), 1); | |
89175cf7 | 236 | } |