1 /*
2 * virpcivpd.c: helper APIs for working with the PCI/PCIe VPD capability
3 *
4 * Copyright (C) 2021 Canonical Ltd.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library. If not, see
18 * <http://www.gnu.org/licenses/>.
19 */
20
21 #include <config.h>
22
23 #ifdef __linux__
24 # include <unistd.h>
25 #endif
26
27 #define LIBVIRT_VIRPCIVPDPRIV_H_ALLOW
28
29 #include "virthread.h"
30 #include "virpcivpdpriv.h"
31 #include "virlog.h"
32 #include "virerror.h"
33 #include "virfile.h"
34
35 #define VIR_FROM_THIS VIR_FROM_NONE
36
37 VIR_LOG_INIT("util.pcivpd");
38
39 static bool
virPCIVPDResourceIsUpperOrNumber(const char c)40 virPCIVPDResourceIsUpperOrNumber(const char c)
41 {
42 return g_ascii_isupper(c) || g_ascii_isdigit(c);
43 }
44
45 static bool
virPCIVPDResourceIsVendorKeyword(const char * keyword)46 virPCIVPDResourceIsVendorKeyword(const char *keyword)
47 {
48 return g_str_has_prefix(keyword, "V") && virPCIVPDResourceIsUpperOrNumber(keyword[1]);
49 }
50
51 static bool
virPCIVPDResourceIsSystemKeyword(const char * keyword)52 virPCIVPDResourceIsSystemKeyword(const char *keyword)
53 {
54 /* Special-case the system-specific keywords since they share the "Y" prefix with "YA". */
55 return (g_str_has_prefix(keyword, "Y") && virPCIVPDResourceIsUpperOrNumber(keyword[1]) &&
56 STRNEQ(keyword, "YA"));
57 }
58
59 static char *
virPCIVPDResourceGetKeywordPrefix(const char * keyword)60 virPCIVPDResourceGetKeywordPrefix(const char *keyword)
61 {
62 g_autofree char *key = NULL;
63
64 /* Keywords must have a length of 2 bytes. */
65 if (strlen(keyword) != 2) {
66 virReportError(VIR_ERR_INTERNAL_ERROR, _("The keyword length is not 2 bytes: %s"), keyword);
67 return NULL;
68 } else if (!(virPCIVPDResourceIsUpperOrNumber(keyword[0]) &&
69 virPCIVPDResourceIsUpperOrNumber(keyword[1]))) {
70 virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
71 _("The keyword is not comprised only of uppercase ASCII letters or digits"));
72 return NULL;
73 }
74 /* Special-case the system-specific keywords since they share the "Y" prefix with "YA". */
75 if (virPCIVPDResourceIsSystemKeyword(keyword) || virPCIVPDResourceIsVendorKeyword(keyword))
76 key = g_strndup(keyword, 1);
77 else
78 key = g_strndup(keyword, 2);
79
80 return g_steal_pointer(&key);
81 }
82
83 static GHashTable *fieldValueFormats;
84
85 static int
virPCIVPDResourceOnceInit(void)86 virPCIVPDResourceOnceInit(void)
87 {
88 /* Initialize a hash table once with static format metadata coming from the PCI(e) specs.
89 * The VPD format does not embed format metadata into the resource records so it is not
90 * possible to do format discovery without static information. Legacy PICMIG keywords
91 * are not included. NOTE: string literals are copied as g_hash_table_insert
92 * requires pointers to non-const data. */
93 fieldValueFormats = g_hash_table_new(g_str_hash, g_str_equal);
94 /* Extended capability. Contains binary data per PCI(e) specs. */
95 g_hash_table_insert(fieldValueFormats, g_strdup("CP"),
96 GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_BINARY));
97 /* Engineering Change Level of an Add-in Card. */
98 g_hash_table_insert(fieldValueFormats, g_strdup("EC"),
99 GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
100 /* Manufacture ID */
101 g_hash_table_insert(fieldValueFormats, g_strdup("MN"),
102 GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
103 /* Add-in Card Part Number */
104 g_hash_table_insert(fieldValueFormats, g_strdup("PN"),
105 GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
106 /* Checksum and Reserved */
107 g_hash_table_insert(fieldValueFormats, g_strdup("RV"),
108 GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RESVD));
109 /* Remaining Read/Write Area */
110 g_hash_table_insert(fieldValueFormats, g_strdup("RW"),
111 GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RDWR));
112 /* Serial Number */
113 g_hash_table_insert(fieldValueFormats, g_strdup("SN"),
114 GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
115 /* Asset Tag Identifier */
116 g_hash_table_insert(fieldValueFormats, g_strdup("YA"),
117 GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
118 /* This is a vendor specific item and the characters are alphanumeric. The second
119 * character (x) of the keyword can be 0 through Z so only the first one is stored. */
120 g_hash_table_insert(fieldValueFormats, g_strdup("V"),
121 GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
122 /* This is a system specific item and the characters are alphanumeric.
123 * The second character (x) of the keyword can be 0 through 9 and B through Z. */
124 g_hash_table_insert(fieldValueFormats, g_strdup("Y"),
125 GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
126
127 return 0;
128 }
129
130 VIR_ONCE_GLOBAL_INIT(virPCIVPDResource);
131
132 /**
133 * virPCIVPDResourceGetFieldValueFormat:
134 * @keyword: A keyword for which to get a value type
135 *
136 * Returns: a virPCIVPDResourceFieldValueFormat value which specifies the field value type for
137 * a provided keyword based on the static information from PCI(e) specs.
138 */
139 virPCIVPDResourceFieldValueFormat
virPCIVPDResourceGetFieldValueFormat(const char * keyword)140 virPCIVPDResourceGetFieldValueFormat(const char *keyword)
141 {
142 g_autofree char *key = NULL;
143 gpointer keyVal = NULL;
144 virPCIVPDResourceFieldValueFormat format = VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST;
145
146 /* Keywords are expected to be 2 bytes in length which is defined in the specs. */
147 if (strlen(keyword) != 2)
148 return VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST;
149
150 if (virPCIVPDResourceInitialize() < 0)
151 return VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST;
152
153 /* The system and vendor-specific keywords have a variable part - lookup
154 * the prefix significant for determining the value format. */
155 key = virPCIVPDResourceGetKeywordPrefix(keyword);
156 if (key) {
157 keyVal = g_hash_table_lookup(fieldValueFormats, key);
158 if (keyVal)
159 format = GPOINTER_TO_INT(keyVal);
160 }
161 return format;
162 }
163
164 /**
165 * virPCIVPDResourceIsValidTextValue:
166 * @value: A NULL-terminated string to assess.
167 *
168 * Returns: a boolean indicating whether this value is a valid string resource
169 * value or text field value. The expectations are based on the keywords specified
170 * in relevant sections of PCI(e) specifications
171 * ("I.3. VPD Definitions" in PCI specs, "6.28.1 VPD Format" PCIe 4.0).
172 */
173 bool
virPCIVPDResourceIsValidTextValue(const char * value)174 virPCIVPDResourceIsValidTextValue(const char *value)
175 {
176 size_t i = 0;
177 /*
178 * The PCI(e) specs mention alphanumeric characters when talking about text fields
179 * and the string resource but also include spaces and dashes in the provided example.
180 * Dots, commas, equal signs have also been observed in values used by major device vendors.
181 * The specs do not specify a full set of allowed code points and for Libvirt it is important
182 * to keep values in the ranges allowed within XML elements (mainly excluding less-than,
183 * greater-than and ampersand).
184 */
185
186 if (value == NULL)
187 return false;
188
189 /* An empty string is a valid value. */
190 if (STREQ(value, ""))
191 return true;
192
193 while (i < strlen(value)) {
194 if (!g_ascii_isprint(value[i])) {
195 VIR_DEBUG("The provided value contains non-ASCII printable characters: %s", value);
196 return false;
197 }
198 ++i;
199 }
200 return true;
201 }
202
203 void
virPCIVPDResourceFree(virPCIVPDResource * res)204 virPCIVPDResourceFree(virPCIVPDResource *res)
205 {
206 if (!res)
207 return;
208
209 g_free(res->name);
210 virPCIVPDResourceROFree(res->ro);
211 virPCIVPDResourceRWFree(res->rw);
212 g_free(res);
213 }
214
215 virPCIVPDResourceRO *
virPCIVPDResourceRONew(void)216 virPCIVPDResourceRONew(void)
217 {
218 g_autoptr(virPCIVPDResourceRO) ro = g_new0(virPCIVPDResourceRO, 1);
219 ro->vendor_specific = g_ptr_array_new_full(0, (GDestroyNotify)virPCIVPDResourceCustomFree);
220 return g_steal_pointer(&ro);
221 }
222
223 void
virPCIVPDResourceROFree(virPCIVPDResourceRO * ro)224 virPCIVPDResourceROFree(virPCIVPDResourceRO *ro)
225 {
226 if (!ro)
227 return;
228
229 g_free(ro->change_level);
230 g_free(ro->manufacture_id);
231 g_free(ro->part_number);
232 g_free(ro->serial_number);
233 g_ptr_array_unref(ro->vendor_specific);
234 g_free(ro);
235 }
236
237 virPCIVPDResourceRW *
virPCIVPDResourceRWNew(void)238 virPCIVPDResourceRWNew(void)
239 {
240 g_autoptr(virPCIVPDResourceRW) rw = g_new0(virPCIVPDResourceRW, 1);
241 rw->vendor_specific = g_ptr_array_new_full(0, (GDestroyNotify)virPCIVPDResourceCustomFree);
242 rw->system_specific = g_ptr_array_new_full(0, (GDestroyNotify)virPCIVPDResourceCustomFree);
243 return g_steal_pointer(&rw);
244 }
245
246 void
virPCIVPDResourceRWFree(virPCIVPDResourceRW * rw)247 virPCIVPDResourceRWFree(virPCIVPDResourceRW *rw)
248 {
249 if (!rw)
250 return;
251
252 g_free(rw->asset_tag);
253 g_ptr_array_unref(rw->vendor_specific);
254 g_ptr_array_unref(rw->system_specific);
255 g_free(rw);
256 }
257
258 void
virPCIVPDResourceCustomFree(virPCIVPDResourceCustom * custom)259 virPCIVPDResourceCustomFree(virPCIVPDResourceCustom *custom)
260 {
261 g_free(custom->value);
262 g_free(custom);
263 }
264
265 gboolean
virPCIVPDResourceCustomCompareIndex(virPCIVPDResourceCustom * a,virPCIVPDResourceCustom * b)266 virPCIVPDResourceCustomCompareIndex(virPCIVPDResourceCustom *a, virPCIVPDResourceCustom *b)
267 {
268 if (a == b)
269 return TRUE;
270 else if (a == NULL || b == NULL)
271 return FALSE;
272 else
273 return a->idx == b->idx ? TRUE : FALSE;
274 }
275
276 /**
277 * virPCIVPDResourceCustomUpsertValue:
278 * @arr: A GPtrArray with virPCIVPDResourceCustom entries to update.
279 * @index: An index character for the keyword.
280 * @value: A pointer to the value to be inserted at a given index.
281 *
282 * Returns: true if a value has been updated successfully, false otherwise.
283 */
284 bool
virPCIVPDResourceCustomUpsertValue(GPtrArray * arr,char index,const char * const value)285 virPCIVPDResourceCustomUpsertValue(GPtrArray *arr, char index, const char *const value)
286 {
287 g_autoptr(virPCIVPDResourceCustom) custom = NULL;
288 virPCIVPDResourceCustom *existing = NULL;
289 guint pos = 0;
290 bool found = false;
291
292 if (arr == NULL || value == NULL)
293 return false;
294
295 custom = g_new0(virPCIVPDResourceCustom, 1);
296 custom->idx = index;
297 custom->value = g_strdup(value);
298 found = g_ptr_array_find_with_equal_func(arr, custom,
299 (GEqualFunc)virPCIVPDResourceCustomCompareIndex,
300 &pos);
301 if (found) {
302 existing = g_ptr_array_index(arr, pos);
303 g_free(existing->value);
304 existing->value = g_steal_pointer(&custom->value);
305 } else {
306 g_ptr_array_add(arr, g_steal_pointer(&custom));
307 }
308 return true;
309 }
310
311 /**
312 * virPCIVPDResourceUpdateKeyword:
313 * @res: A non-NULL pointer to a virPCIVPDResource where a keyword will be updated.
314 * @readOnly: A bool specifying which section to update (in-memory): read-only or read-write.
315 * @keyword: A non-NULL pointer to a name of the keyword that will be updated.
316 * @value: A pointer to the keyword value or NULL. The value is copied on successful update.
317 *
318 * The caller is responsible for initializing the relevant RO or RW sections of the resource,
319 * otherwise, false will be returned.
320 *
321 * Keyword names are either 2-byte keywords from the spec or their human-readable alternatives
322 * used in XML elements. For vendor-specific and system-specific keywords only V%s and Y%s
323 * (except "YA" which is an asset tag) formatted values are accepted.
324 *
325 * Returns: true if a keyword has been updated successfully, false otherwise.
326 */
327 bool
virPCIVPDResourceUpdateKeyword(virPCIVPDResource * res,const bool readOnly,const char * const keyword,const char * const value)328 virPCIVPDResourceUpdateKeyword(virPCIVPDResource *res, const bool readOnly,
329 const char *const keyword, const char *const value)
330 {
331 if (!res) {
332 virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
333 _("Cannot update the resource: a NULL resource pointer has been provided."));
334 return false;
335 } else if (!keyword) {
336 virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
337 _("Cannot update the resource: a NULL keyword pointer has been provided."));
338 return false;
339 }
340
341 if (readOnly) {
342 if (!res->ro) {
343 virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
344 _("Cannot update the read-only keyword: RO section not initialized."));
345 return false;
346 }
347
348 if (STREQ("EC", keyword) || STREQ("change_level", keyword)) {
349 g_free(res->ro->change_level);
350 res->ro->change_level = g_strdup(value);
351 return true;
352 } else if (STREQ("MN", keyword) || STREQ("manufacture_id", keyword)) {
353 g_free(res->ro->manufacture_id);
354 res->ro->manufacture_id = g_strdup(value);
355 return true;
356 } else if (STREQ("PN", keyword) || STREQ("part_number", keyword)) {
357 g_free(res->ro->part_number);
358 res->ro->part_number = g_strdup(value);
359 return true;
360 } else if (STREQ("SN", keyword) || STREQ("serial_number", keyword)) {
361 g_free(res->ro->serial_number);
362 res->ro->serial_number = g_strdup(value);
363 return true;
364 } else if (virPCIVPDResourceIsVendorKeyword(keyword)) {
365 if (!virPCIVPDResourceCustomUpsertValue(res->ro->vendor_specific, keyword[1], value)) {
366 return false;
367 }
368 return true;
369 } else if (STREQ("FG", keyword) || STREQ("LC", keyword) || STREQ("PG", keyword)) {
370 /* Legacy PICMIG keywords are skipped on purpose. */
371 return true;
372 } else if (STREQ("CP", keyword)) {
373 /* The CP keyword is currently not supported and is skipped. */
374 return true;
375 }
376
377 } else {
378 if (!res->rw) {
379 virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
380 _
381 ("Cannot update the read-write keyword: read-write section not initialized."));
382 return false;
383 }
384
385 if (STREQ("YA", keyword) || STREQ("asset_tag", keyword)) {
386 g_free(res->rw->asset_tag);
387 res->rw->asset_tag = g_strdup(value);
388 return true;
389 } else if (virPCIVPDResourceIsVendorKeyword(keyword)) {
390 if (!virPCIVPDResourceCustomUpsertValue(res->rw->vendor_specific, keyword[1], value)) {
391 return false;
392 }
393 return true;
394 } else if (virPCIVPDResourceIsSystemKeyword(keyword)) {
395 if (!virPCIVPDResourceCustomUpsertValue(res->rw->system_specific, keyword[1], value)) {
396 return false;
397 }
398 return true;
399 }
400 }
401 VIR_WARN("Tried to update an unsupported keyword %s: skipping.", keyword);
402 return true;
403 }
404
405 #ifdef __linux__
406
407 /**
408 * virPCIVPDReadVPDBytes:
409 * @vpdFileFd: A file descriptor associated with a file containing PCI device VPD.
410 * @buf: An allocated buffer to use for storing VPD bytes read.
411 * @count: The number of bytes to read from the VPD file descriptor.
412 * @offset: The offset at which bytes need to be read.
413 * @csum: A pointer to a byte containing the current checksum value. Mutated by this function.
414 *
415 * Returns: the number of VPD bytes read from the specified file descriptor. The csum value is
416 * also modified as bytes are read. If an error occurs while reading data from the VPD file
417 * descriptor, it is reported and -1 is returned to the caller. If EOF is occurred, 0 is returned
418 * to the caller.
419 */
420 size_t
virPCIVPDReadVPDBytes(int vpdFileFd,uint8_t * buf,size_t count,off_t offset,uint8_t * csum)421 virPCIVPDReadVPDBytes(int vpdFileFd, uint8_t *buf, size_t count, off_t offset, uint8_t *csum)
422 {
423 ssize_t numRead = pread(vpdFileFd, buf, count, offset);
424
425 if (numRead == -1) {
426 VIR_DEBUG("Unable to read %zu bytes at offset %zd from fd: %d",
427 count, (ssize_t)offset, vpdFileFd);
428 } else if (numRead) {
429 /*
430 * Update the checksum for every byte read. Per the PCI(e) specs
431 * the checksum is correct if the sum of all bytes in VPD from
432 * VPD address 0 up to and including the VPD-R RV field's first
433 * data byte is zero.
434 */
435 while (count--) {
436 *csum += *buf;
437 buf++;
438 }
439 }
440 return numRead;
441 }
442
443 /**
444 * virPCIVPDParseVPDLargeResourceFields:
445 * @vpdFileFd: A file descriptor associated with a file containing PCI device VPD.
446 * @resPos: A position where the resource data bytes begin in a file descriptor.
447 * @resDataLen: A length of the data portion of a resource.
448 * @readOnly: A boolean showing whether the resource type is VPD-R or VPD-W.
449 * @csum: A pointer to a 1-byte checksum.
450 * @res: A pointer to virPCIVPDResource.
451 *
452 * Returns: a pointer to a VPDResource which needs to be freed by the caller or
453 * NULL if getting it failed for some reason.
454 */
455 bool
virPCIVPDParseVPDLargeResourceFields(int vpdFileFd,uint16_t resPos,uint16_t resDataLen,bool readOnly,uint8_t * csum,virPCIVPDResource * res)456 virPCIVPDParseVPDLargeResourceFields(int vpdFileFd, uint16_t resPos, uint16_t resDataLen,
457 bool readOnly, uint8_t *csum, virPCIVPDResource *res)
458 {
459 /* A buffer of up to one resource record field size (plus a zero byte) is needed. */
460 g_autofree uint8_t *buf = g_malloc0(PCI_VPD_MAX_FIELD_SIZE + 1);
461 uint16_t fieldDataLen = 0, bytesToRead = 0;
462 uint16_t fieldPos = resPos;
463
464 bool hasChecksum = false;
465 bool hasRW = false;
466 bool endReached = false;
467
468 /* Note the equal sign - fields may have a zero length in which case they will
469 * just occupy 3 header bytes. In the in case of the RW field this may mean that
470 * no more space is left in the section. */
471 while (fieldPos + 3 <= resPos + resDataLen) {
472 virPCIVPDResourceFieldValueFormat fieldFormat = VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST;
473 g_autofree char *fieldKeyword = NULL;
474 g_autofree char *fieldValue = NULL;
475
476 /* Keyword resources consist of keywords (2 ASCII bytes per the spec) and 1-byte length. */
477 if (virPCIVPDReadVPDBytes(vpdFileFd, buf, 3, fieldPos, csum) != 3) {
478 /* Invalid field encountered which means the resource itself is invalid too. Report
479 * That VPD has invalid format and bail. */
480 virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
481 _("Could not read a resource field header - VPD has invalid format"));
482 return false;
483 }
484 fieldDataLen = buf[2];
485 /* Change the position to the field's data portion skipping the keyword and length bytes. */
486 fieldPos += 3;
487 fieldKeyword = g_strndup((char *)buf, 2);
488 fieldFormat = virPCIVPDResourceGetFieldValueFormat(fieldKeyword);
489
490 /* Handle special cases first */
491 if (!readOnly && fieldFormat == VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RESVD) {
492 virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
493 _("Unexpected RV keyword in the read-write section."));
494 return false;
495 } else if (readOnly && fieldFormat == VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RDWR) {
496 virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
497 _("Unexpected RW keyword in the read-only section."));
498 return false;
499 }
500
501 /* Determine how many bytes to read per field value type. */
502 switch (fieldFormat) {
503 case VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT:
504 case VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RDWR:
505 case VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_BINARY:
506 bytesToRead = fieldDataLen;
507 break;
508 case VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RESVD:
509 /* Only need one byte to be read and accounted towards
510 * the checksum calculation. */
511 bytesToRead = 1;
512 break;
513 case VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST:
514 /* The VPD format could be extended in future versions with new
515 * keywords - attempt to skip them by reading past them since
516 * their data length would still be specified. */
517 VIR_DEBUG("Could not determine a field value format for keyword: %s", fieldKeyword);
518 bytesToRead = fieldDataLen;
519 break;
520 default:
521 virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
522 _("Unexpected field value format encountered."));
523 return false;
524 }
525
526 if (resPos + resDataLen < fieldPos + fieldDataLen) {
527 /* In this case the field cannot simply be skipped since the position of the
528 * next field is determined based on the length of a previous field. */
529 virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
530 _("A field data length violates the resource length boundary."));
531 return false;
532 }
533 if (virPCIVPDReadVPDBytes(vpdFileFd, buf, bytesToRead, fieldPos, csum) != bytesToRead) {
534 virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
535 _("Could not parse a resource field data - VPD has invalid format"));
536 return false;
537 }
538 /* Advance the position to the first byte of the next field. */
539 fieldPos += fieldDataLen;
540
541 if (fieldFormat == VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT) {
542 /* Trim whitespace around a retrieved value and set it to be a field's value. Cases
543 * where unnecessary whitespace was present around a field value have been encountered
544 * in the wild.
545 */
546 fieldValue = g_strstrip(g_strndup((char *)buf, fieldDataLen));
547 if (!virPCIVPDResourceIsValidTextValue(fieldValue)) {
548 /* Skip fields with invalid values - this is safe assuming field length is
549 * correctly specified. */
550 VIR_DEBUG("A value for field %s contains invalid characters", fieldKeyword);
551 continue;
552 }
553 } else if (fieldFormat == VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RESVD) {
554 if (*csum) {
555 /* All bytes up to and including the checksum byte should add up to 0. */
556 virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Checksum validation has failed"));
557 return false;
558 }
559 hasChecksum = true;
560 break;
561 } else if (fieldFormat == VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RDWR) {
562 /* Skip the read-write space since it is used for indication only. */
563 hasRW = true;
564 break;
565 } else if (fieldFormat == VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST) {
566 /* Skip unknown fields */
567 continue;
568 } else {
569 fieldValue = g_malloc(fieldDataLen);
570 memcpy(fieldValue, buf, fieldDataLen);
571 }
572
573 if (readOnly) {
574 if (!res->ro)
575 res->ro = virPCIVPDResourceRONew();
576 } else {
577 if (!res->rw)
578 res->rw = virPCIVPDResourceRWNew();
579 }
580 /* The field format, keyword and value are determined. Attempt to update the resource. */
581 if (!virPCIVPDResourceUpdateKeyword(res, readOnly, fieldKeyword, fieldValue)) {
582 virReportError(VIR_ERR_INTERNAL_ERROR,
583 _("Could not update the VPD resource keyword: %s"), fieldKeyword);
584 return false;
585 }
586 }
587
588 /* May have exited the loop prematurely in case RV or RW were encountered and
589 * they were not the last fields in the section. */
590 endReached = (fieldPos >= resPos + resDataLen);
591 if (readOnly && !(hasChecksum && endReached)) {
592 VIR_DEBUG("VPD-R does not contain the mandatory RV field as the last field");
593 return false;
594 } else if (!readOnly && !endReached) {
595 /* The lack of RW is allowed on purpose in the read-write section since some vendors
596 * violate the PCI/PCIe specs and do not include it, however, this does not prevent parsing
597 * of valid data. If the RW is present, however, we make sure it is the last field in
598 * the read-write section. */
599 if (hasRW) {
600 VIR_DEBUG("VPD-W section parsing ended prematurely (RW is not the last field).");
601 return false;
602 } else {
603 VIR_DEBUG("VPD-W section parsing ended prematurely.");
604 return false;
605 }
606 }
607
608 return true;
609 }
610
611 /**
612 * virPCIVPDParseVPDLargeResourceString:
613 * @vpdFileFd: A file descriptor associated with a file containing PCI device VPD.
614 * @resPos: A position where the resource data bytes begin in a file descriptor.
615 * @resDataLen: A length of the data portion of a resource.
616 * @csum: A pointer to a 1-byte checksum.
617 *
618 * Returns: a pointer to a VPDResource which needs to be freed by the caller or
619 * NULL if getting it failed for some reason.
620 */
621 bool
virPCIVPDParseVPDLargeResourceString(int vpdFileFd,uint16_t resPos,uint16_t resDataLen,uint8_t * csum,virPCIVPDResource * res)622 virPCIVPDParseVPDLargeResourceString(int vpdFileFd, uint16_t resPos,
623 uint16_t resDataLen, uint8_t *csum, virPCIVPDResource *res)
624 {
625 g_autofree char *resValue = NULL;
626
627 /* The resource value is not NULL-terminated so add one more byte. */
628 g_autofree char *buf = g_malloc0(resDataLen + 1);
629
630 if (virPCIVPDReadVPDBytes(vpdFileFd, (uint8_t *)buf, resDataLen, resPos, csum) != resDataLen) {
631 virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
632 _("Could not read a part of a resource - VPD has invalid format"));
633 return false;
634 }
635 resValue = g_strdup(g_strstrip(buf));
636 if (!virPCIVPDResourceIsValidTextValue(resValue)) {
637 virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
638 _("The string resource has invalid characters in its value"));
639 return false;
640 }
641 res->name = g_steal_pointer(&resValue);
642 return true;
643 }
644
645 /**
646 * virPCIVPDParse:
647 * @vpdFileFd: a file descriptor associated with a file containing PCI device VPD.
648 *
649 * Parse a PCI device's Vital Product Data (VPD) contained in a file descriptor.
650 *
651 * Returns: a pointer to a GList of VPDResource types which needs to be freed by the caller or
652 * NULL if getting it failed for some reason.
653 */
654 virPCIVPDResource *
virPCIVPDParse(int vpdFileFd)655 virPCIVPDParse(int vpdFileFd)
656 {
657 /* A checksum which is calculated as a sum of all bytes from VPD byte 0 up to
658 * the checksum byte in the RV field's value. The RV field is only present in the
659 * VPD-R resource and the checksum byte there is the first byte of the field's value.
660 * The checksum byte in RV field is actually a two's complement of the sum of all bytes
661 * of VPD that come before it so adding the two together must produce 0 if data
662 * was not corrupted and VPD storage is intact.
663 */
664 uint8_t csum = 0;
665 uint8_t headerBuf[2];
666
667 bool isWellFormed = false;
668 uint16_t resPos = 0, resDataLen;
669 uint8_t tag = 0;
670 bool endResReached = false, hasReadOnly = false;
671
672 g_autoptr(virPCIVPDResource) res = g_new0(virPCIVPDResource, 1);
673
674 while (resPos <= PCI_VPD_ADDR_MASK) {
675 /* Read the resource data type tag. */
676 if (virPCIVPDReadVPDBytes(vpdFileFd, &tag, 1, resPos, &csum) != 1)
677 break;
678
679 /* 0x80 == 0b10000000 - the large resource data type flag. */
680 if (tag & PCI_VPD_LARGE_RESOURCE_FLAG) {
681 if (resPos > PCI_VPD_ADDR_MASK + 1 - 3) {
682 /* Bail if the large resource starts at the position
683 * where the end tag should be. */
684 break;
685 }
686 /* Read the two length bytes of the large resource record. */
687 if (virPCIVPDReadVPDBytes(vpdFileFd, headerBuf, 2, resPos + 1, &csum) != 2)
688 break;
689
690 resDataLen = headerBuf[0] + (headerBuf[1] << 8);
691 /* Change the position to the byte following the tag and length bytes. */
692 resPos += 3;
693 } else {
694 /* Handle a small resource record.
695 * 0xxxxyyy & 00000111, where xxxx - resource data type bits, yyy - length bits. */
696 resDataLen = tag & 7;
697 /* 0xxxxyyy >> 3 == 0000xxxx */
698 tag >>= 3;
699 /* Change the position to the byte past the byte containing tag and length bits. */
700 resPos += 1;
701 }
702 if (tag == PCI_VPD_RESOURCE_END_TAG) {
703 /* Stop VPD traversal since the end tag was encountered. */
704 endResReached = true;
705 break;
706 }
707 if (resDataLen > PCI_VPD_ADDR_MASK + 1 - resPos) {
708 /* Bail if the resource is too long to fit into the VPD address space. */
709 break;
710 }
711
712 switch (tag) {
713 /* Large resource type which is also a string: 0x80 | 0x02 = 0x82 */
714 case PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_STRING_RESOURCE_FLAG:
715 isWellFormed = virPCIVPDParseVPDLargeResourceString(vpdFileFd, resPos, resDataLen,
716 &csum, res);
717 break;
718 /* Large resource type which is also a VPD-R: 0x80 | 0x10 == 0x90 */
719 case PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG:
720 isWellFormed = virPCIVPDParseVPDLargeResourceFields(vpdFileFd, resPos,
721 resDataLen, true, &csum, res);
722 /* Encountered the VPD-R tag. The resource record parsing also validates
723 * the presence of the required checksum in the RV field. */
724 hasReadOnly = true;
725 break;
726 /* Large resource type which is also a VPD-W: 0x80 | 0x11 == 0x91 */
727 case PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_WRITE_LARGE_RESOURCE_FLAG:
728 isWellFormed = virPCIVPDParseVPDLargeResourceFields(vpdFileFd, resPos, resDataLen,
729 false, &csum, res);
730 break;
731 default:
732 /* While we cannot parse unknown resource types, they can still be skipped
733 * based on the header and data length. */
734 VIR_DEBUG("Encountered an unexpected VPD resource tag: %#x", tag);
735 resPos += resDataLen;
736 continue;
737 }
738
739 if (!isWellFormed) {
740 VIR_DEBUG("Encountered an invalid VPD");
741 return NULL;
742 }
743
744 /* Continue processing other resource records. */
745 resPos += resDataLen;
746 }
747 if (!hasReadOnly) {
748 VIR_DEBUG("Encountered an invalid VPD: does not have a VPD-R record");
749 return NULL;
750 } else if (!endResReached) {
751 /* Does not have an end tag. */
752 VIR_DEBUG("Encountered an invalid VPD");
753 return NULL;
754 }
755 return g_steal_pointer(&res);
756 }
757
758 #else /* ! __linux__ */
759
760 size_t
virPCIVPDReadVPDBytes(int vpdFileFd G_GNUC_UNUSED,uint8_t * buf G_GNUC_UNUSED,size_t count G_GNUC_UNUSED,off_t offset G_GNUC_UNUSED,uint8_t * csum G_GNUC_UNUSED)761 virPCIVPDReadVPDBytes(int vpdFileFd G_GNUC_UNUSED,
762 uint8_t *buf G_GNUC_UNUSED,
763 size_t count G_GNUC_UNUSED,
764 off_t offset G_GNUC_UNUSED,
765 uint8_t *csum G_GNUC_UNUSED)
766 {
767 virReportError(VIR_ERR_NO_SUPPORT, "%s",
768 _("PCI VPD reporting not available on this platform"));
769 return 0;
770 }
771
772 bool
virPCIVPDParseVPDLargeResourceString(int vpdFileFd G_GNUC_UNUSED,uint16_t resPos G_GNUC_UNUSED,uint16_t resDataLen G_GNUC_UNUSED,uint8_t * csum G_GNUC_UNUSED,virPCIVPDResource * res G_GNUC_UNUSED)773 virPCIVPDParseVPDLargeResourceString(int vpdFileFd G_GNUC_UNUSED,
774 uint16_t resPos G_GNUC_UNUSED,
775 uint16_t resDataLen G_GNUC_UNUSED,
776 uint8_t *csum G_GNUC_UNUSED,
777 virPCIVPDResource *res G_GNUC_UNUSED)
778 {
779 virReportError(VIR_ERR_NO_SUPPORT, "%s",
780 _("PCI VPD reporting not available on this platform"));
781 return false;
782 }
783
784 bool
virPCIVPDParseVPDLargeResourceFields(int vpdFileFd G_GNUC_UNUSED,uint16_t resPos G_GNUC_UNUSED,uint16_t resDataLen G_GNUC_UNUSED,bool readOnly G_GNUC_UNUSED,uint8_t * csum G_GNUC_UNUSED,virPCIVPDResource * res G_GNUC_UNUSED)785 virPCIVPDParseVPDLargeResourceFields(int vpdFileFd G_GNUC_UNUSED,
786 uint16_t resPos G_GNUC_UNUSED,
787 uint16_t resDataLen G_GNUC_UNUSED,
788 bool readOnly G_GNUC_UNUSED,
789 uint8_t *csum G_GNUC_UNUSED,
790 virPCIVPDResource *res G_GNUC_UNUSED)
791 {
792 virReportError(VIR_ERR_NO_SUPPORT, "%s",
793 _("PCI VPD reporting not available on this platform"));
794 return false;
795 }
796
797 virPCIVPDResource *
virPCIVPDParse(int vpdFileFd G_GNUC_UNUSED)798 virPCIVPDParse(int vpdFileFd G_GNUC_UNUSED)
799 {
800 virReportError(VIR_ERR_NO_SUPPORT, "%s",
801 _("PCI VPD reporting not available on this platform"));
802 return NULL;
803 }
804
805 #endif /* ! __linux__ */
806