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