Commit | Line | Data |
---|---|---|
58c5475a LW |
1 | /* |
2 | * apple-properties.c - EFI device properties on Macs | |
3 | * Copyright (C) 2016 Lukas Wunner <lukas@wunner.de> | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License (version 2) as | |
7 | * published by the Free Software Foundation. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | * GNU General Public License for more details. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License | |
15 | * along with this program; if not, see <http://www.gnu.org/licenses/>. | |
63dcc709 AS |
16 | * |
17 | * Note, all properties are considered as u8 arrays. | |
18 | * To get a value of any of them the caller must use device_property_read_u8_array(). | |
58c5475a LW |
19 | */ |
20 | ||
21 | #define pr_fmt(fmt) "apple-properties: " fmt | |
22 | ||
23 | #include <linux/bootmem.h> | |
58c5475a | 24 | #include <linux/efi.h> |
44612d7e | 25 | #include <linux/io.h> |
630b3aff | 26 | #include <linux/platform_data/x86/apple.h> |
58c5475a LW |
27 | #include <linux/property.h> |
28 | #include <linux/slab.h> | |
29 | #include <linux/ucs2_string.h> | |
30 | #include <asm/setup.h> | |
31 | ||
32 | static bool dump_properties __initdata; | |
33 | ||
34 | static int __init dump_properties_enable(char *arg) | |
35 | { | |
36 | dump_properties = true; | |
37 | return 0; | |
38 | } | |
39 | ||
40 | __setup("dump_apple_properties", dump_properties_enable); | |
41 | ||
42 | struct dev_header { | |
43 | u32 len; | |
44 | u32 prop_count; | |
45 | struct efi_dev_path path[0]; | |
46 | /* | |
47 | * followed by key/value pairs, each key and value preceded by u32 len, | |
48 | * len includes itself, value may be empty (in which case its len is 4) | |
49 | */ | |
50 | }; | |
51 | ||
52 | struct properties_header { | |
53 | u32 len; | |
54 | u32 version; | |
55 | u32 dev_count; | |
56 | struct dev_header dev_header[0]; | |
57 | }; | |
58 | ||
58c5475a LW |
59 | static void __init unmarshal_key_value_pairs(struct dev_header *dev_header, |
60 | struct device *dev, void *ptr, | |
61 | struct property_entry entry[]) | |
62 | { | |
63 | int i; | |
64 | ||
65 | for (i = 0; i < dev_header->prop_count; i++) { | |
66 | int remaining = dev_header->len - (ptr - (void *)dev_header); | |
67 | u32 key_len, val_len; | |
68 | char *key; | |
69 | ||
70 | if (sizeof(key_len) > remaining) | |
71 | break; | |
72 | ||
73 | key_len = *(typeof(key_len) *)ptr; | |
74 | if (key_len + sizeof(val_len) > remaining || | |
75 | key_len < sizeof(key_len) + sizeof(efi_char16_t) || | |
76 | *(efi_char16_t *)(ptr + sizeof(key_len)) == 0) { | |
77 | dev_err(dev, "invalid property name len at %#zx\n", | |
78 | ptr - (void *)dev_header); | |
79 | break; | |
80 | } | |
81 | ||
82 | val_len = *(typeof(val_len) *)(ptr + key_len); | |
83 | if (key_len + val_len > remaining || | |
84 | val_len < sizeof(val_len)) { | |
85 | dev_err(dev, "invalid property val len at %#zx\n", | |
86 | ptr - (void *)dev_header + key_len); | |
87 | break; | |
88 | } | |
89 | ||
90 | /* 4 bytes to accommodate UTF-8 code points + null byte */ | |
91 | key = kzalloc((key_len - sizeof(key_len)) * 4 + 1, GFP_KERNEL); | |
92 | if (!key) { | |
93 | dev_err(dev, "cannot allocate property name\n"); | |
94 | break; | |
95 | } | |
96 | ucs2_as_utf8(key, ptr + sizeof(key_len), | |
97 | key_len - sizeof(key_len)); | |
98 | ||
99 | entry[i].name = key; | |
58c5475a | 100 | entry[i].length = val_len - sizeof(val_len); |
6e98503d | 101 | entry[i].is_array = !!entry[i].length; |
63dcc709 AS |
102 | entry[i].type = DEV_PROP_U8; |
103 | entry[i].pointer.u8_data = ptr + key_len + sizeof(val_len); | |
58c5475a LW |
104 | |
105 | if (dump_properties) { | |
106 | dev_info(dev, "property: %s\n", entry[i].name); | |
107 | print_hex_dump(KERN_INFO, pr_fmt(), DUMP_PREFIX_OFFSET, | |
63dcc709 | 108 | 16, 1, entry[i].pointer.u8_data, |
58c5475a LW |
109 | entry[i].length, true); |
110 | } | |
111 | ||
112 | ptr += key_len + val_len; | |
113 | } | |
114 | ||
115 | if (i != dev_header->prop_count) { | |
116 | dev_err(dev, "got %d device properties, expected %u\n", i, | |
117 | dev_header->prop_count); | |
118 | print_hex_dump(KERN_ERR, pr_fmt(), DUMP_PREFIX_OFFSET, | |
119 | 16, 1, dev_header, dev_header->len, true); | |
120 | return; | |
121 | } | |
122 | ||
123 | dev_info(dev, "assigning %d device properties\n", i); | |
124 | } | |
125 | ||
126 | static int __init unmarshal_devices(struct properties_header *properties) | |
127 | { | |
128 | size_t offset = offsetof(struct properties_header, dev_header[0]); | |
129 | ||
130 | while (offset + sizeof(struct dev_header) < properties->len) { | |
131 | struct dev_header *dev_header = (void *)properties + offset; | |
132 | struct property_entry *entry = NULL; | |
133 | struct device *dev; | |
134 | size_t len; | |
135 | int ret, i; | |
136 | void *ptr; | |
137 | ||
138 | if (offset + dev_header->len > properties->len || | |
139 | dev_header->len <= sizeof(*dev_header)) { | |
140 | pr_err("invalid len in dev_header at %#zx\n", offset); | |
141 | return -EINVAL; | |
142 | } | |
143 | ||
144 | ptr = dev_header->path; | |
145 | len = dev_header->len - sizeof(*dev_header); | |
146 | ||
147 | dev = efi_get_device_by_path((struct efi_dev_path **)&ptr, &len); | |
148 | if (IS_ERR(dev)) { | |
149 | pr_err("device path parse error %ld at %#zx:\n", | |
150 | PTR_ERR(dev), ptr - (void *)dev_header); | |
151 | print_hex_dump(KERN_ERR, pr_fmt(), DUMP_PREFIX_OFFSET, | |
152 | 16, 1, dev_header, dev_header->len, true); | |
153 | dev = NULL; | |
154 | goto skip_device; | |
155 | } | |
156 | ||
157 | entry = kcalloc(dev_header->prop_count + 1, sizeof(*entry), | |
158 | GFP_KERNEL); | |
159 | if (!entry) { | |
160 | dev_err(dev, "cannot allocate properties\n"); | |
161 | goto skip_device; | |
162 | } | |
163 | ||
164 | unmarshal_key_value_pairs(dev_header, dev, ptr, entry); | |
165 | if (!entry[0].name) | |
166 | goto skip_device; | |
167 | ||
168 | ret = device_add_properties(dev, entry); /* makes deep copy */ | |
169 | if (ret) | |
170 | dev_err(dev, "error %d assigning properties\n", ret); | |
171 | ||
172 | for (i = 0; entry[i].name; i++) | |
173 | kfree(entry[i].name); | |
174 | ||
175 | skip_device: | |
176 | kfree(entry); | |
177 | put_device(dev); | |
178 | offset += dev_header->len; | |
179 | } | |
180 | ||
181 | return 0; | |
182 | } | |
183 | ||
184 | static int __init map_properties(void) | |
185 | { | |
186 | struct properties_header *properties; | |
187 | struct setup_data *data; | |
188 | u32 data_len; | |
189 | u64 pa_data; | |
190 | int ret; | |
191 | ||
630b3aff | 192 | if (!x86_apple_machine) |
58c5475a LW |
193 | return 0; |
194 | ||
195 | pa_data = boot_params.hdr.setup_data; | |
196 | while (pa_data) { | |
44612d7e | 197 | data = memremap(pa_data, sizeof(*data), MEMREMAP_WB); |
58c5475a LW |
198 | if (!data) { |
199 | pr_err("cannot map setup_data header\n"); | |
200 | return -ENOMEM; | |
201 | } | |
202 | ||
203 | if (data->type != SETUP_APPLE_PROPERTIES) { | |
204 | pa_data = data->next; | |
44612d7e | 205 | memunmap(data); |
58c5475a LW |
206 | continue; |
207 | } | |
208 | ||
209 | data_len = data->len; | |
44612d7e | 210 | memunmap(data); |
58c5475a | 211 | |
44612d7e | 212 | data = memremap(pa_data, sizeof(*data) + data_len, MEMREMAP_WB); |
58c5475a LW |
213 | if (!data) { |
214 | pr_err("cannot map setup_data payload\n"); | |
215 | return -ENOMEM; | |
216 | } | |
217 | ||
218 | properties = (struct properties_header *)data->data; | |
219 | if (properties->version != 1) { | |
220 | pr_err("unsupported version:\n"); | |
221 | print_hex_dump(KERN_ERR, pr_fmt(), DUMP_PREFIX_OFFSET, | |
222 | 16, 1, properties, data_len, true); | |
223 | ret = -ENOTSUPP; | |
224 | } else if (properties->len != data_len) { | |
225 | pr_err("length mismatch, expected %u\n", data_len); | |
226 | print_hex_dump(KERN_ERR, pr_fmt(), DUMP_PREFIX_OFFSET, | |
227 | 16, 1, properties, data_len, true); | |
228 | ret = -EINVAL; | |
229 | } else | |
230 | ret = unmarshal_devices(properties); | |
231 | ||
232 | /* | |
233 | * Can only free the setup_data payload but not its header | |
234 | * to avoid breaking the chain of ->next pointers. | |
235 | */ | |
236 | data->len = 0; | |
44612d7e | 237 | memunmap(data); |
53ab85eb | 238 | memblock_free_late(pa_data + sizeof(*data), data_len); |
58c5475a LW |
239 | |
240 | return ret; | |
241 | } | |
242 | return 0; | |
243 | } | |
244 | ||
245 | fs_initcall(map_properties); |