1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * apple-properties.c - EFI device properties on Macs |
4 | * Copyright (C) 2016 Lukas Wunner <lukas@wunner.de> |
5 | * |
6 | * Properties are stored either as: |
7 | * u8 arrays which can be retrieved with device_property_read_u8_array() or |
8 | * booleans which can be queried with device_property_present(). |
9 | */ |
10 | |
11 | #define pr_fmt(fmt) "apple-properties: " fmt |
12 | |
13 | #include <linux/memblock.h> |
14 | #include <linux/efi.h> |
15 | #include <linux/io.h> |
16 | #include <linux/platform_data/x86/apple.h> |
17 | #include <linux/property.h> |
18 | #include <linux/slab.h> |
19 | #include <linux/ucs2_string.h> |
20 | #include <asm/setup.h> |
21 | |
22 | static bool dump_properties __initdata; |
23 | |
24 | static int __init dump_properties_enable(char *arg) |
25 | { |
26 | dump_properties = true; |
27 | return 1; |
28 | } |
29 | |
30 | __setup("dump_apple_properties" , dump_properties_enable); |
31 | |
32 | struct { |
33 | u32 ; |
34 | u32 ; |
35 | struct efi_dev_path []; |
36 | /* |
37 | * followed by key/value pairs, each key and value preceded by u32 len, |
38 | * len includes itself, value may be empty (in which case its len is 4) |
39 | */ |
40 | }; |
41 | |
42 | struct { |
43 | u32 ; |
44 | u32 ; |
45 | u32 ; |
46 | struct dev_header []; |
47 | }; |
48 | |
49 | static void __init unmarshal_key_value_pairs(struct dev_header *, |
50 | struct device *dev, const void *ptr, |
51 | struct property_entry entry[]) |
52 | { |
53 | int i; |
54 | |
55 | for (i = 0; i < dev_header->prop_count; i++) { |
56 | int remaining = dev_header->len - (ptr - (void *)dev_header); |
57 | u32 key_len, val_len, entry_len; |
58 | const u8 *entry_data; |
59 | char *key; |
60 | |
61 | if (sizeof(key_len) > remaining) |
62 | break; |
63 | |
64 | key_len = *(typeof(key_len) *)ptr; |
65 | if (key_len + sizeof(val_len) > remaining || |
66 | key_len < sizeof(key_len) + sizeof(efi_char16_t) || |
67 | *(efi_char16_t *)(ptr + sizeof(key_len)) == 0) { |
68 | dev_err(dev, "invalid property name len at %#zx\n" , |
69 | ptr - (void *)dev_header); |
70 | break; |
71 | } |
72 | |
73 | val_len = *(typeof(val_len) *)(ptr + key_len); |
74 | if (key_len + val_len > remaining || |
75 | val_len < sizeof(val_len)) { |
76 | dev_err(dev, "invalid property val len at %#zx\n" , |
77 | ptr - (void *)dev_header + key_len); |
78 | break; |
79 | } |
80 | |
81 | /* 4 bytes to accommodate UTF-8 code points + null byte */ |
82 | key = kzalloc(size: (key_len - sizeof(key_len)) * 4 + 1, GFP_KERNEL); |
83 | if (!key) { |
84 | dev_err(dev, "cannot allocate property name\n" ); |
85 | break; |
86 | } |
87 | ucs2_as_utf8(dest: key, src: ptr + sizeof(key_len), |
88 | maxlength: key_len - sizeof(key_len)); |
89 | |
90 | entry_data = ptr + key_len + sizeof(val_len); |
91 | entry_len = val_len - sizeof(val_len); |
92 | if (entry_len) |
93 | entry[i] = PROPERTY_ENTRY_U8_ARRAY_LEN(key, entry_data, |
94 | entry_len); |
95 | else |
96 | entry[i] = PROPERTY_ENTRY_BOOL(key); |
97 | |
98 | if (dump_properties) { |
99 | dev_info(dev, "property: %s\n" , key); |
100 | print_hex_dump(KERN_INFO, pr_fmt(), prefix_type: DUMP_PREFIX_OFFSET, |
101 | rowsize: 16, groupsize: 1, buf: entry_data, len: entry_len, ascii: true); |
102 | } |
103 | |
104 | ptr += key_len + val_len; |
105 | } |
106 | |
107 | if (i != dev_header->prop_count) { |
108 | dev_err(dev, "got %d device properties, expected %u\n" , i, |
109 | dev_header->prop_count); |
110 | print_hex_dump(KERN_ERR, pr_fmt(), prefix_type: DUMP_PREFIX_OFFSET, |
111 | rowsize: 16, groupsize: 1, buf: dev_header, len: dev_header->len, ascii: true); |
112 | return; |
113 | } |
114 | |
115 | dev_info(dev, "assigning %d device properties\n" , i); |
116 | } |
117 | |
118 | static int __init unmarshal_devices(struct properties_header *properties) |
119 | { |
120 | size_t offset = offsetof(struct properties_header, dev_header[0]); |
121 | |
122 | while (offset + sizeof(struct dev_header) < properties->len) { |
123 | struct dev_header * = (void *)properties + offset; |
124 | struct property_entry *entry = NULL; |
125 | const struct efi_dev_path *ptr; |
126 | struct device *dev; |
127 | size_t len; |
128 | int ret, i; |
129 | |
130 | if (offset + dev_header->len > properties->len || |
131 | dev_header->len <= sizeof(*dev_header)) { |
132 | pr_err("invalid len in dev_header at %#zx\n" , offset); |
133 | return -EINVAL; |
134 | } |
135 | |
136 | ptr = dev_header->path; |
137 | len = dev_header->len - sizeof(*dev_header); |
138 | |
139 | dev = efi_get_device_by_path(node: &ptr, len: &len); |
140 | if (IS_ERR(ptr: dev)) { |
141 | pr_err("device path parse error %ld at %#zx:\n" , |
142 | PTR_ERR(dev), (void *)ptr - (void *)dev_header); |
143 | print_hex_dump(KERN_ERR, pr_fmt(), prefix_type: DUMP_PREFIX_OFFSET, |
144 | rowsize: 16, groupsize: 1, buf: dev_header, len: dev_header->len, ascii: true); |
145 | dev = NULL; |
146 | goto skip_device; |
147 | } |
148 | |
149 | entry = kcalloc(n: dev_header->prop_count + 1, size: sizeof(*entry), |
150 | GFP_KERNEL); |
151 | if (!entry) { |
152 | dev_err(dev, "cannot allocate properties\n" ); |
153 | goto skip_device; |
154 | } |
155 | |
156 | unmarshal_key_value_pairs(dev_header, dev, ptr, entry); |
157 | if (!entry[0].name) |
158 | goto skip_device; |
159 | |
160 | ret = device_create_managed_software_node(dev, properties: entry, NULL); |
161 | if (ret) |
162 | dev_err(dev, "error %d assigning properties\n" , ret); |
163 | |
164 | for (i = 0; entry[i].name; i++) |
165 | kfree(objp: entry[i].name); |
166 | |
167 | skip_device: |
168 | kfree(objp: entry); |
169 | put_device(dev); |
170 | offset += dev_header->len; |
171 | } |
172 | |
173 | return 0; |
174 | } |
175 | |
176 | static int __init map_properties(void) |
177 | { |
178 | struct properties_header *properties; |
179 | struct setup_data *data; |
180 | u32 data_len; |
181 | u64 pa_data; |
182 | int ret; |
183 | |
184 | if (!x86_apple_machine) |
185 | return 0; |
186 | |
187 | pa_data = boot_params.hdr.setup_data; |
188 | while (pa_data) { |
189 | data = memremap(offset: pa_data, size: sizeof(*data), flags: MEMREMAP_WB); |
190 | if (!data) { |
191 | pr_err("cannot map setup_data header\n" ); |
192 | return -ENOMEM; |
193 | } |
194 | |
195 | if (data->type != SETUP_APPLE_PROPERTIES) { |
196 | pa_data = data->next; |
197 | memunmap(addr: data); |
198 | continue; |
199 | } |
200 | |
201 | data_len = data->len; |
202 | memunmap(addr: data); |
203 | |
204 | data = memremap(offset: pa_data, size: sizeof(*data) + data_len, flags: MEMREMAP_WB); |
205 | if (!data) { |
206 | pr_err("cannot map setup_data payload\n" ); |
207 | return -ENOMEM; |
208 | } |
209 | |
210 | properties = (struct properties_header *)data->data; |
211 | if (properties->version != 1) { |
212 | pr_err("unsupported version:\n" ); |
213 | print_hex_dump(KERN_ERR, pr_fmt(), prefix_type: DUMP_PREFIX_OFFSET, |
214 | rowsize: 16, groupsize: 1, buf: properties, len: data_len, ascii: true); |
215 | ret = -ENOTSUPP; |
216 | } else if (properties->len != data_len) { |
217 | pr_err("length mismatch, expected %u\n" , data_len); |
218 | print_hex_dump(KERN_ERR, pr_fmt(), prefix_type: DUMP_PREFIX_OFFSET, |
219 | rowsize: 16, groupsize: 1, buf: properties, len: data_len, ascii: true); |
220 | ret = -EINVAL; |
221 | } else |
222 | ret = unmarshal_devices(properties); |
223 | |
224 | /* |
225 | * Can only free the setup_data payload but not its header |
226 | * to avoid breaking the chain of ->next pointers. |
227 | */ |
228 | data->len = 0; |
229 | memunmap(addr: data); |
230 | memblock_free_late(base: pa_data + sizeof(*data), size: data_len); |
231 | |
232 | return ret; |
233 | } |
234 | return 0; |
235 | } |
236 | |
237 | fs_initcall(map_properties); |
238 | |