1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * IBM Real-Time Linux driver |
4 | * |
5 | * Copyright (C) IBM Corporation, 2010 |
6 | * |
7 | * Author: Keith Mannthey <kmannth@us.ibm.com> |
8 | * Vernon Mauery <vernux@us.ibm.com> |
9 | */ |
10 | |
11 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
12 | |
13 | #include <linux/kernel.h> |
14 | #include <linux/delay.h> |
15 | #include <linux/module.h> |
16 | #include <linux/io.h> |
17 | #include <linux/dmi.h> |
18 | #include <linux/efi.h> |
19 | #include <linux/mutex.h> |
20 | #include <asm/bios_ebda.h> |
21 | |
22 | #include <linux/io-64-nonatomic-lo-hi.h> |
23 | |
24 | static bool force; |
25 | module_param(force, bool, 0); |
26 | MODULE_PARM_DESC(force, "Force driver load, ignore DMI data" ); |
27 | |
28 | static bool debug; |
29 | module_param(debug, bool, 0644); |
30 | MODULE_PARM_DESC(debug, "Show debug output" ); |
31 | |
32 | MODULE_LICENSE("GPL" ); |
33 | MODULE_AUTHOR("Keith Mannthey <kmmanth@us.ibm.com>" ); |
34 | MODULE_AUTHOR("Vernon Mauery <vernux@us.ibm.com>" ); |
35 | |
36 | #define RTL_ADDR_TYPE_IO 1 |
37 | #define RTL_ADDR_TYPE_MMIO 2 |
38 | |
39 | #define RTL_CMD_ENTER_PRTM 1 |
40 | #define RTL_CMD_EXIT_PRTM 2 |
41 | |
42 | /* The RTL table as presented by the EBDA: */ |
43 | struct ibm_rtl_table { |
44 | char signature[5]; /* signature should be "_RTL_" */ |
45 | u8 version; |
46 | u8 rt_status; |
47 | u8 command; |
48 | u8 command_status; |
49 | u8 cmd_address_type; |
50 | u8 cmd_granularity; |
51 | u8 cmd_offset; |
52 | u16 reserve1; |
53 | u32 cmd_port_address; /* platform dependent address */ |
54 | u32 cmd_port_value; /* platform dependent value */ |
55 | } __attribute__((packed)); |
56 | |
57 | /* to locate "_RTL_" signature do a masked 5-byte integer compare */ |
58 | #define RTL_SIGNATURE 0x0000005f4c54525fULL |
59 | #define RTL_MASK 0x000000ffffffffffULL |
60 | |
61 | #define RTL_DEBUG(fmt, ...) \ |
62 | do { \ |
63 | if (debug) \ |
64 | pr_info(fmt, ##__VA_ARGS__); \ |
65 | } while (0) |
66 | |
67 | static DEFINE_MUTEX(rtl_lock); |
68 | static struct ibm_rtl_table __iomem *rtl_table; |
69 | static void __iomem *ebda_map; |
70 | static void __iomem *rtl_cmd_addr; |
71 | static u8 rtl_cmd_type; |
72 | static u8 rtl_cmd_width; |
73 | |
74 | static void __iomem *rtl_port_map(phys_addr_t addr, unsigned long len) |
75 | { |
76 | if (rtl_cmd_type == RTL_ADDR_TYPE_MMIO) |
77 | return ioremap(offset: addr, size: len); |
78 | return ioport_map(port: addr, nr: len); |
79 | } |
80 | |
81 | static void rtl_port_unmap(void __iomem *addr) |
82 | { |
83 | if (addr && rtl_cmd_type == RTL_ADDR_TYPE_MMIO) |
84 | iounmap(addr); |
85 | else |
86 | ioport_unmap(p: addr); |
87 | } |
88 | |
89 | static int ibm_rtl_write(u8 value) |
90 | { |
91 | int ret = 0, count = 0; |
92 | u32 cmd_port_val; |
93 | |
94 | RTL_DEBUG("%s(%d)\n" , __func__, value); |
95 | |
96 | value = value == 1 ? RTL_CMD_ENTER_PRTM : RTL_CMD_EXIT_PRTM; |
97 | |
98 | mutex_lock(&rtl_lock); |
99 | |
100 | if (ioread8(&rtl_table->rt_status) != value) { |
101 | iowrite8(value, &rtl_table->command); |
102 | |
103 | switch (rtl_cmd_width) { |
104 | case 8: |
105 | cmd_port_val = ioread8(&rtl_table->cmd_port_value); |
106 | RTL_DEBUG("cmd_port_val = %u\n" , cmd_port_val); |
107 | iowrite8((u8)cmd_port_val, rtl_cmd_addr); |
108 | break; |
109 | case 16: |
110 | cmd_port_val = ioread16(&rtl_table->cmd_port_value); |
111 | RTL_DEBUG("cmd_port_val = %u\n" , cmd_port_val); |
112 | iowrite16((u16)cmd_port_val, rtl_cmd_addr); |
113 | break; |
114 | case 32: |
115 | cmd_port_val = ioread32(&rtl_table->cmd_port_value); |
116 | RTL_DEBUG("cmd_port_val = %u\n" , cmd_port_val); |
117 | iowrite32(cmd_port_val, rtl_cmd_addr); |
118 | break; |
119 | } |
120 | |
121 | while (ioread8(&rtl_table->command)) { |
122 | msleep(msecs: 10); |
123 | if (count++ > 500) { |
124 | pr_err("Hardware not responding to " |
125 | "mode switch request\n" ); |
126 | ret = -EIO; |
127 | break; |
128 | } |
129 | |
130 | } |
131 | |
132 | if (ioread8(&rtl_table->command_status)) { |
133 | RTL_DEBUG("command_status reports failed command\n" ); |
134 | ret = -EIO; |
135 | } |
136 | } |
137 | |
138 | mutex_unlock(lock: &rtl_lock); |
139 | return ret; |
140 | } |
141 | |
142 | static ssize_t rtl_show_version(struct device *dev, |
143 | struct device_attribute *attr, |
144 | char *buf) |
145 | { |
146 | return sprintf(buf, fmt: "%d\n" , (int)ioread8(&rtl_table->version)); |
147 | } |
148 | |
149 | static ssize_t rtl_show_state(struct device *dev, |
150 | struct device_attribute *attr, |
151 | char *buf) |
152 | { |
153 | return sprintf(buf, fmt: "%d\n" , ioread8(&rtl_table->rt_status)); |
154 | } |
155 | |
156 | static ssize_t rtl_set_state(struct device *dev, |
157 | struct device_attribute *attr, |
158 | const char *buf, |
159 | size_t count) |
160 | { |
161 | ssize_t ret; |
162 | |
163 | if (count < 1 || count > 2) |
164 | return -EINVAL; |
165 | |
166 | switch (buf[0]) { |
167 | case '0': |
168 | ret = ibm_rtl_write(value: 0); |
169 | break; |
170 | case '1': |
171 | ret = ibm_rtl_write(value: 1); |
172 | break; |
173 | default: |
174 | ret = -EINVAL; |
175 | } |
176 | if (ret >= 0) |
177 | ret = count; |
178 | |
179 | return ret; |
180 | } |
181 | |
182 | static struct bus_type rtl_subsys = { |
183 | .name = "ibm_rtl" , |
184 | .dev_name = "ibm_rtl" , |
185 | }; |
186 | |
187 | static DEVICE_ATTR(version, S_IRUGO, rtl_show_version, NULL); |
188 | static DEVICE_ATTR(state, 0600, rtl_show_state, rtl_set_state); |
189 | |
190 | static struct device_attribute *rtl_attributes[] = { |
191 | &dev_attr_version, |
192 | &dev_attr_state, |
193 | NULL |
194 | }; |
195 | |
196 | |
197 | static int rtl_setup_sysfs(void) { |
198 | int ret, i; |
199 | |
200 | ret = subsys_system_register(subsys: &rtl_subsys, NULL); |
201 | if (!ret) { |
202 | struct device *dev_root = bus_get_dev_root(bus: &rtl_subsys); |
203 | |
204 | if (dev_root) { |
205 | for (i = 0; rtl_attributes[i]; i ++) |
206 | device_create_file(device: dev_root, entry: rtl_attributes[i]); |
207 | put_device(dev: dev_root); |
208 | } |
209 | } |
210 | return ret; |
211 | } |
212 | |
213 | static void rtl_teardown_sysfs(void) { |
214 | struct device *dev_root = bus_get_dev_root(bus: &rtl_subsys); |
215 | int i; |
216 | |
217 | if (dev_root) { |
218 | for (i = 0; rtl_attributes[i]; i ++) |
219 | device_remove_file(dev: dev_root, attr: rtl_attributes[i]); |
220 | put_device(dev: dev_root); |
221 | } |
222 | bus_unregister(bus: &rtl_subsys); |
223 | } |
224 | |
225 | |
226 | static const struct dmi_system_id ibm_rtl_dmi_table[] __initconst = { |
227 | { \ |
228 | .matches = { \ |
229 | DMI_MATCH(DMI_SYS_VENDOR, "IBM" ), \ |
230 | }, \ |
231 | }, |
232 | { } |
233 | }; |
234 | |
235 | static int __init ibm_rtl_init(void) { |
236 | unsigned long ebda_addr, ebda_size; |
237 | unsigned int ebda_kb; |
238 | int ret = -ENODEV, i; |
239 | |
240 | if (force) |
241 | pr_warn("module loaded by force\n" ); |
242 | /* first ensure that we are running on IBM HW */ |
243 | else if (efi_enabled(EFI_BOOT) || !dmi_check_system(list: ibm_rtl_dmi_table)) |
244 | return -ENODEV; |
245 | |
246 | /* Get the address for the Extended BIOS Data Area */ |
247 | ebda_addr = get_bios_ebda(); |
248 | if (!ebda_addr) { |
249 | RTL_DEBUG("no BIOS EBDA found\n" ); |
250 | return -ENODEV; |
251 | } |
252 | |
253 | ebda_map = ioremap(offset: ebda_addr, size: 4); |
254 | if (!ebda_map) |
255 | return -ENOMEM; |
256 | |
257 | /* First word in the EDBA is the Size in KB */ |
258 | ebda_kb = ioread16(ebda_map); |
259 | RTL_DEBUG("EBDA is %d kB\n" , ebda_kb); |
260 | |
261 | if (ebda_kb == 0) |
262 | goto out; |
263 | |
264 | iounmap(addr: ebda_map); |
265 | ebda_size = ebda_kb*1024; |
266 | |
267 | /* Remap the whole table */ |
268 | ebda_map = ioremap(offset: ebda_addr, size: ebda_size); |
269 | if (!ebda_map) |
270 | return -ENOMEM; |
271 | |
272 | /* search for the _RTL_ signature at the start of the table */ |
273 | for (i = 0 ; i < ebda_size/sizeof(unsigned int); i++) { |
274 | struct ibm_rtl_table __iomem * tmp; |
275 | tmp = (struct ibm_rtl_table __iomem *) (ebda_map+i); |
276 | if ((readq(addr: &tmp->signature) & RTL_MASK) == RTL_SIGNATURE) { |
277 | phys_addr_t addr; |
278 | unsigned int plen; |
279 | RTL_DEBUG("found RTL_SIGNATURE at %p\n" , tmp); |
280 | rtl_table = tmp; |
281 | /* The address, value, width and offset are platform |
282 | * dependent and found in the ibm_rtl_table */ |
283 | rtl_cmd_width = ioread8(&rtl_table->cmd_granularity); |
284 | rtl_cmd_type = ioread8(&rtl_table->cmd_address_type); |
285 | RTL_DEBUG("rtl_cmd_width = %u, rtl_cmd_type = %u\n" , |
286 | rtl_cmd_width, rtl_cmd_type); |
287 | addr = ioread32(&rtl_table->cmd_port_address); |
288 | RTL_DEBUG("addr = %#llx\n" , (unsigned long long)addr); |
289 | plen = rtl_cmd_width/sizeof(char); |
290 | rtl_cmd_addr = rtl_port_map(addr, len: plen); |
291 | RTL_DEBUG("rtl_cmd_addr = %p\n" , rtl_cmd_addr); |
292 | if (!rtl_cmd_addr) { |
293 | ret = -ENOMEM; |
294 | break; |
295 | } |
296 | ret = rtl_setup_sysfs(); |
297 | break; |
298 | } |
299 | } |
300 | |
301 | out: |
302 | if (ret) { |
303 | iounmap(addr: ebda_map); |
304 | rtl_port_unmap(addr: rtl_cmd_addr); |
305 | } |
306 | |
307 | return ret; |
308 | } |
309 | |
310 | static void __exit ibm_rtl_exit(void) |
311 | { |
312 | if (rtl_table) { |
313 | RTL_DEBUG("cleaning up" ); |
314 | /* do not leave the machine in SMI-free mode */ |
315 | ibm_rtl_write(value: 0); |
316 | /* unmap, unlink and remove all traces */ |
317 | rtl_teardown_sysfs(); |
318 | iounmap(addr: ebda_map); |
319 | rtl_port_unmap(addr: rtl_cmd_addr); |
320 | } |
321 | } |
322 | |
323 | module_init(ibm_rtl_init); |
324 | module_exit(ibm_rtl_exit); |
325 | |