1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2022 Beckhoff Automation GmbH & Co. KG 5 * Author: Corvin Köhne <c.koehne@beckhoff.com> 6 */ 7 8 #include <sys/types.h> 9 #include <sys/param.h> 10 #include <sys/endian.h> 11 #include <sys/queue.h> 12 13 #include <machine/vmm.h> 14 15 #include <err.h> 16 #include <errno.h> 17 #include <stdio.h> 18 #include <stdlib.h> 19 #include <string.h> 20 #include <vmmapi.h> 21 22 #include "qemu_fwcfg.h" 23 #include "qemu_loader.h" 24 25 #ifndef __FreeBSD__ 26 /* 27 * This is better than the number of ifdef blocks that would be otherwise 28 * required throughout this code. Hopefully upstream will clear up the 29 * char* /uint8_t * confusion. 30 */ 31 #define strlen(x) strlen((char *)(x)) 32 #define strncpy(p, q, n) strncpy((char *)(p), (char *)(q), (n)) 33 #endif 34 35 struct qemu_loader_entry { 36 uint32_t cmd_le; 37 union { 38 struct { 39 uint8_t name[QEMU_FWCFG_MAX_NAME]; 40 uint32_t alignment_le; 41 uint8_t zone; 42 } alloc; 43 struct { 44 uint8_t dest_name[QEMU_FWCFG_MAX_NAME]; 45 uint8_t src_name[QEMU_FWCFG_MAX_NAME]; 46 uint32_t off_le; 47 uint8_t size; 48 } add_pointer; 49 struct { 50 uint8_t name[QEMU_FWCFG_MAX_NAME]; 51 uint32_t off_le; 52 uint32_t start_le; 53 uint32_t len_le; 54 } add_checksum; 55 struct { 56 uint8_t dest_name[QEMU_FWCFG_MAX_NAME]; 57 uint8_t src_name[QEMU_FWCFG_MAX_NAME]; 58 uint32_t dest_off_le; 59 uint32_t src_off_le; 60 uint8_t size; 61 } write_pointer; 62 63 /* padding */ 64 uint8_t pad[124]; 65 }; 66 } __packed; 67 68 enum qemu_loader_command { 69 QEMU_LOADER_CMD_ALLOC = 0x1, 70 QEMU_LOADER_CMD_ADD_POINTER = 0x2, 71 QEMU_LOADER_CMD_ADD_CHECKSUM = 0x3, 72 QEMU_LOADER_CMD_WRITE_POINTER = 0x4, 73 }; 74 75 struct qemu_loader_element { 76 STAILQ_ENTRY(qemu_loader_element) chain; 77 struct qemu_loader_entry entry; 78 }; 79 80 struct qemu_loader { 81 uint8_t fwcfg_name[QEMU_FWCFG_MAX_NAME]; 82 STAILQ_HEAD(qemu_loader_list, qemu_loader_element) list; 83 }; 84 85 int 86 qemu_loader_alloc(struct qemu_loader *const loader, const uint8_t *name, 87 const uint32_t alignment, const enum qemu_loader_zone zone) 88 { 89 struct qemu_loader_element *element; 90 91 if (strlen(name) >= QEMU_FWCFG_MAX_NAME) 92 return (EINVAL); 93 94 element = calloc(1, sizeof(struct qemu_loader_element)); 95 if (element == NULL) { 96 warnx("%s: failed to allocate command", __func__); 97 return (ENOMEM); 98 } 99 100 element->entry.cmd_le = htole32(QEMU_LOADER_CMD_ALLOC); 101 strncpy(element->entry.alloc.name, name, QEMU_FWCFG_MAX_NAME); 102 element->entry.alloc.alignment_le = htole32(alignment); 103 element->entry.alloc.zone = zone; 104 105 /* 106 * The guest always works on copies of the fwcfg item, which where 107 * loaded into guest memory. Loading a fwcfg item is caused by ALLOC. 108 * For that reason, ALLOC should be scheduled in front of any other 109 * commands. 110 */ 111 STAILQ_INSERT_HEAD(&loader->list, element, chain); 112 113 return (0); 114 } 115 116 int 117 qemu_loader_add_checksum(struct qemu_loader *const loader, const uint8_t *name, 118 const uint32_t off, const uint32_t start, const uint32_t len) 119 { 120 struct qemu_loader_element *element; 121 122 if (strlen(name) >= QEMU_FWCFG_MAX_NAME) 123 return (EINVAL); 124 125 element = calloc(1, sizeof(struct qemu_loader_element)); 126 if (element == NULL) { 127 warnx("%s: failed to allocate command", __func__); 128 return (ENOMEM); 129 } 130 131 element->entry.cmd_le = htole32(QEMU_LOADER_CMD_ADD_CHECKSUM); 132 strncpy(element->entry.add_checksum.name, name, QEMU_FWCFG_MAX_NAME); 133 element->entry.add_checksum.off_le = htole32(off); 134 element->entry.add_checksum.start_le = htole32(start); 135 element->entry.add_checksum.len_le = htole32(len); 136 137 STAILQ_INSERT_TAIL(&loader->list, element, chain); 138 139 return (0); 140 } 141 142 int 143 qemu_loader_add_pointer(struct qemu_loader *const loader, 144 const uint8_t *dest_name, const uint8_t *src_name, const uint32_t off, 145 const uint8_t size) 146 { 147 struct qemu_loader_element *element; 148 149 if (strlen(dest_name) >= QEMU_FWCFG_MAX_NAME || 150 strlen(src_name) >= QEMU_FWCFG_MAX_NAME) 151 return (EINVAL); 152 153 element = calloc(1, sizeof(struct qemu_loader_element)); 154 if (element == NULL) { 155 warnx("%s: failed to allocate command", __func__); 156 return (ENOMEM); 157 } 158 159 element->entry.cmd_le = htole32(QEMU_LOADER_CMD_ADD_POINTER); 160 strncpy(element->entry.add_pointer.dest_name, dest_name, 161 QEMU_FWCFG_MAX_NAME); 162 strncpy(element->entry.add_pointer.src_name, src_name, 163 QEMU_FWCFG_MAX_NAME); 164 element->entry.add_pointer.off_le = htole32(off); 165 element->entry.add_pointer.size = size; 166 167 STAILQ_INSERT_TAIL(&loader->list, element, chain); 168 169 return (0); 170 } 171 172 int 173 qemu_loader_create(struct qemu_loader **const new_loader, 174 const uint8_t *fwcfg_name) 175 { 176 struct qemu_loader *loader; 177 178 if (new_loader == NULL || strlen(fwcfg_name) >= QEMU_FWCFG_MAX_NAME) { 179 return (EINVAL); 180 } 181 182 loader = calloc(1, sizeof(struct qemu_loader)); 183 if (loader == NULL) { 184 warnx("%s: failed to allocate loader", __func__); 185 return (ENOMEM); 186 } 187 188 strncpy(loader->fwcfg_name, fwcfg_name, QEMU_FWCFG_MAX_NAME); 189 STAILQ_INIT(&loader->list); 190 191 *new_loader = loader; 192 193 return (0); 194 } 195 196 static const uint8_t * 197 qemu_loader_get_zone_name(const enum qemu_loader_zone zone) 198 { 199 #ifdef __FreeBSD__ 200 switch (zone) { 201 case QEMU_LOADER_ALLOC_HIGH: 202 return ("HIGH"); 203 case QEMU_LOADER_ALLOC_FSEG: 204 return ("FSEG"); 205 default: 206 return ("Unknown"); 207 } 208 #else 209 switch (zone) { 210 case QEMU_LOADER_ALLOC_HIGH: 211 return ((uint8_t *)"HIGH"); 212 case QEMU_LOADER_ALLOC_FSEG: 213 return ((uint8_t *)"FSEG"); 214 default: 215 return ((uint8_t *)"Unknown"); 216 } 217 #endif 218 } 219 220 static void __unused 221 qemu_loader_dump_entry(const struct qemu_loader_entry *const entry) 222 { 223 switch (le32toh(entry->cmd_le)) { 224 case QEMU_LOADER_CMD_ALLOC: 225 printf("CMD_ALLOC\n\r"); 226 printf(" name : %s\n\r", entry->alloc.name); 227 printf(" alignment: %8x\n\r", 228 le32toh(entry->alloc.alignment_le)); 229 printf(" zone : %s\n\r", 230 qemu_loader_get_zone_name(entry->alloc.zone)); 231 break; 232 case QEMU_LOADER_CMD_ADD_POINTER: 233 printf("CMD_ADD_POINTER\n\r"); 234 printf(" dest_name: %s\n\r", entry->add_pointer.dest_name); 235 printf(" src_name : %s\n\r", entry->add_pointer.src_name); 236 printf(" off : %8x\n\r", 237 le32toh(entry->add_pointer.off_le)); 238 printf(" size : %8x\n\r", entry->add_pointer.size); 239 break; 240 case QEMU_LOADER_CMD_ADD_CHECKSUM: 241 printf("CMD_ADD_CHECKSUM\n\r"); 242 printf(" name : %s\n\r", entry->add_checksum.name); 243 printf(" off : %8x\n\r", 244 le32toh(entry->add_checksum.off_le)); 245 printf(" start : %8x\n\r", 246 le32toh(entry->add_checksum.start_le)); 247 printf(" length : %8x\n\r", 248 le32toh(entry->add_checksum.len_le)); 249 break; 250 case QEMU_LOADER_CMD_WRITE_POINTER: 251 printf("CMD_WRITE_POINTER\n\r"); 252 printf(" dest_name: %s\n\r", entry->write_pointer.dest_name); 253 printf(" src_name : %s\n\r", entry->write_pointer.src_name); 254 printf(" dest_off : %8x\n\r", 255 le32toh(entry->write_pointer.dest_off_le)); 256 printf(" src_off : %8x\n\r", 257 le32toh(entry->write_pointer.src_off_le)); 258 printf(" size : %8x\n\r", entry->write_pointer.size); 259 break; 260 default: 261 printf("UNKNOWN\n\r"); 262 break; 263 } 264 } 265 266 int 267 qemu_loader_finish(struct qemu_loader *const loader) 268 { 269 struct qemu_loader_element *element; 270 struct qemu_loader_entry *data; 271 size_t len = 0; 272 273 STAILQ_FOREACH(element, &loader->list, chain) { 274 len += sizeof(struct qemu_loader_entry); 275 } 276 if (len == 0) { 277 warnx("%s: bios loader empty", __func__); 278 return (EFAULT); 279 } 280 281 data = calloc(1, len); 282 if (data == NULL) { 283 warnx("%s: failed to allocate fwcfg data", __func__); 284 return (ENOMEM); 285 } 286 287 int i = 0; 288 STAILQ_FOREACH(element, &loader->list, chain) { 289 memcpy(&data[i], &element->entry, 290 sizeof(struct qemu_loader_entry)); 291 ++i; 292 } 293 294 #ifdef __FreeBSD__ 295 return (qemu_fwcfg_add_file(loader->fwcfg_name, len, data)); 296 #else 297 return (qemu_fwcfg_add_file((const char *)loader->fwcfg_name, 298 len, data)); 299 #endif 300 } 301