xref: /illumos-gate/usr/src/cmd/pcieadm/pcieadm_bar.c (revision 415435b44529c2d47836fd3e301c66134ae17524)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2026 Oxide Computer Company
14  */
15 
16 /*
17  * Implement logic related to listing, reading, and writing BARs.
18  */
19 
20 #include <err.h>
21 #include <stdio.h>
22 #include <sys/pci.h>
23 #include <ofmt.h>
24 #include <stdbool.h>
25 #include <string.h>
26 #include <stdlib.h>
27 
28 #include "pcieadm.h"
29 
30 static void
pcieadm_bar_list_usage(FILE * f)31 pcieadm_bar_list_usage(FILE *f)
32 {
33 	(void) fprintf(f, "\tbar list\t[-H] [-o field,... [-p]] -d device "
34 	    "[filter...]\n");
35 }
36 
37 static void
pcieadm_bar_list_help(const char * fmt,...)38 pcieadm_bar_list_help(const char *fmt, ...)
39 {
40 	if (fmt != NULL) {
41 		va_list ap;
42 
43 		va_start(ap, fmt);
44 		vwarnx(fmt, ap);
45 		va_end(ap);
46 		(void) fprintf(stderr, "\n");
47 	}
48 
49 	(void) fprintf(stderr, "Usage:  %s bar list [-H] [-o field,... [-p]] "
50 	    "-d device [filter,...]\n", pcieadm_progname);
51 	(void) fprintf(stderr, "List BARs specific to a single device.\n\n"
52 	    "\t-d device\tlist BARs from the specified device (driver instance,"
53 	    "\n\t\t\t/devices path, or b/d/f)\n"
54 	    "\t-H\t\tomit the column header\n"
55 	    "\t-o field\toutput fields to print (required for -p)\n"
56 	    "\t-p\t\tparsable output (requires -o)\n\n");
57 	(void) fprintf(stderr, "The following fields are supported:\n"
58 	    "\taddress\t\tthe address programmed in the BAR\n"
59 	    "\tbar\t\tthe bar's numeric identifier\n"
60 	    "\tdesc\t\ta human description of the BAR\n"
61 	    "\tmtype\t\tthe memory type of the BAR\n"
62 	    "\tprefetech\tindicates whether or not the BAR is pre-fetchable\n"
63 	    "\traw\t\tthe raw contents of the hardware BAR register\n"
64 	    "\tsize\t\tthe size of the bar\n"
65 	    "\tspace\t\tthe type of space the BAR represents\n"
66 	    "\twidth\t\tindicates the width of the BAR in bytes\n");
67 	(void) fprintf(stderr, "The following filters are supported:\n"
68 	    "\t<index>\t\tthe BAR matches the specified index\n"
69 	    "\tio\t\tthe BAR is an I/O BAR\n"
70 	    "\tmem\t\tthe BAR is a memory BAR\n"
71 	    "\tmem32\t\tthe BAR is a 32-bit memory BAR\n"
72 	    "\tmem64\t\tthe BAR is a 64-bit memory BAR\n"
73 	    "\tprefetch\tthe BAR is prefetchable\n");
74 }
75 
76 typedef enum pcieadm_bar_list_otype {
77 	PCIEADM_BAR_LIST_BAR,
78 	PCIEADM_BAR_LIST_ADDRESS,
79 	PCIEADM_BAR_LIST_DESC,
80 	PCIEADM_BAR_LIST_MTYPE,
81 	PCIEADM_BAR_LIST_SIZE,
82 	PCIEADM_BAR_LIST_SPACE,
83 	PCIEADM_BAR_LIST_PREFETCH,
84 	PCIEADM_BAR_LIST_WIDTH,
85 	PCIEADM_BAR_LIST_RAW
86 } pcieadm_bar_list_otpye_t;
87 
88 typedef struct pcieadm_bar_list_ofmt {
89 	uint8_t pblo_idx;
90 	uint8_t pblo_width;
91 	bool pblo_mem;
92 	bool pblo_prefetch;
93 	uint64_t pblo_addr;
94 	uint64_t pblo_size;
95 	uint64_t pblo_raw;
96 	const char *pblo_mtype;
97 } pcieadm_bar_list_ofmt_t;
98 
99 static boolean_t
pcieadm_bar_list_ofmt_cb(ofmt_arg_t * ofarg,char * buf,uint_t buflen)100 pcieadm_bar_list_ofmt_cb(ofmt_arg_t *ofarg, char *buf, uint_t buflen)
101 {
102 	size_t ret;
103 	const pcieadm_bar_list_ofmt_t *pblo = ofarg->ofmt_cbarg;
104 
105 	switch (ofarg->ofmt_id) {
106 	case PCIEADM_BAR_LIST_BAR:
107 		ret = snprintf(buf, buflen, "%u", pblo->pblo_idx);
108 		break;
109 	case PCIEADM_BAR_LIST_ADDRESS:
110 		ret = snprintf(buf, buflen, "0x%" PRIx64, pblo->pblo_addr);
111 		break;
112 	case PCIEADM_BAR_LIST_DESC:
113 		if (pblo->pblo_mem) {
114 			ret = snprintf(buf, buflen, "%s%s Memory",
115 			    pblo->pblo_mtype, pblo->pblo_prefetch ?
116 			    " Prefetchable" : "");
117 		} else {
118 			ret = strlcat(buf, "I/O", buflen);
119 		}
120 		break;
121 	case PCIEADM_BAR_LIST_MTYPE:
122 		ret = strlcat(buf, pblo->pblo_mtype, buflen);
123 		break;
124 	case PCIEADM_BAR_LIST_SIZE:
125 		ret = snprintf(buf, buflen, "0x%" PRIx64, pblo->pblo_size);
126 		break;
127 	case PCIEADM_BAR_LIST_SPACE:
128 		ret = strlcat(buf, pblo->pblo_mem ? "Memory" : "I/O", buflen);
129 		break;
130 	case PCIEADM_BAR_LIST_PREFETCH:
131 		ret = strlcat(buf, pblo->pblo_prefetch ? "yes" : "no", buflen);
132 		break;
133 	case PCIEADM_BAR_LIST_WIDTH:
134 		ret = snprintf(buf, buflen, "%u", pblo->pblo_width);
135 		break;
136 	case PCIEADM_BAR_LIST_RAW:
137 		ret = snprintf(buf, buflen, "0x%" PRIx64, pblo->pblo_raw);
138 		break;
139 	default:
140 		return (B_FALSE);
141 	}
142 
143 	return (buflen > ret);
144 }
145 
146 static const char *pcieadm_bar_list_fields = "bar,size,address,desc";
147 static const ofmt_field_t pcieadm_bar_list_ofmt[] = {
148 	{ "BAR", 8, PCIEADM_BAR_LIST_BAR, pcieadm_bar_list_ofmt_cb },
149 	{ "ADDRESS", 16, PCIEADM_BAR_LIST_ADDRESS, pcieadm_bar_list_ofmt_cb },
150 	{ "DESC", 32, PCIEADM_BAR_LIST_DESC, pcieadm_bar_list_ofmt_cb },
151 	{ "MTYPE", 8, PCIEADM_BAR_LIST_MTYPE, pcieadm_bar_list_ofmt_cb },
152 	{ "SIZE", 12, PCIEADM_BAR_LIST_SIZE, pcieadm_bar_list_ofmt_cb },
153 	{ "SPACE", 10, PCIEADM_BAR_LIST_SPACE, pcieadm_bar_list_ofmt_cb },
154 	{ "PREFETCH", 4, PCIEADM_BAR_LIST_PREFETCH, pcieadm_bar_list_ofmt_cb },
155 	{ "WIDTH", 6, PCIEADM_BAR_LIST_WIDTH, pcieadm_bar_list_ofmt_cb },
156 	{ "RAW", 16, PCIEADM_BAR_LIST_RAW, pcieadm_bar_list_ofmt_cb },
157 };
158 
159 static bool
pcieadm_show_bar_match(const pcieadm_bar_list_ofmt_t * pblo,int nfilts,char ** filts,bool * used)160 pcieadm_show_bar_match(const pcieadm_bar_list_ofmt_t *pblo, int nfilts,
161     char **filts, bool *used)
162 {
163 	bool match = false;
164 
165 	if (nfilts <= 0) {
166 		return (true);
167 	}
168 
169 	for (int i = 0; i < nfilts; i++) {
170 		if (strcmp(filts[i], "io") == 0 && !pblo->pblo_mem) {
171 			used[i] = true;
172 			match = true;
173 			continue;
174 		}
175 
176 		if (strcmp(filts[i], "mem32") == 0 &&
177 		    strcmp(pblo->pblo_mtype, "32-bit") == 0) {
178 			used[i] = true;
179 			match = true;
180 			continue;
181 		}
182 
183 		if (strcmp(filts[i], "mem64") == 0 &&
184 		    strcmp(pblo->pblo_mtype, "64-bit") == 0) {
185 			used[i] = true;
186 			match = true;
187 			continue;
188 		}
189 
190 		if (strcmp(filts[i], "mem") == 0 && pblo->pblo_mem) {
191 			used[i] = true;
192 			match = true;
193 			continue;
194 		}
195 
196 		if (strcmp(filts[i], "prefetch") == 0 && pblo->pblo_prefetch) {
197 			used[i] = true;
198 			match = true;
199 			continue;
200 		}
201 
202 		/*
203 		 * Attempt to parse anything left as an integer indicating a BAR
204 		 * index.
205 		 */
206 		const char *errstr;
207 		long long l = strtonumx(filts[i], 0, UINT32_MAX, &errstr, 0);
208 		if (errstr == NULL && l == pblo->pblo_idx) {
209 			used[i] = true;
210 			match = true;
211 			continue;
212 		}
213 	}
214 
215 	return (match);
216 }
217 
218 /*
219  * Read information about BARs. There are basically two different sources that
220  * we want to combine information from: reg[] and the device itself. We prefer
221  * reg[] over assigned-addresses[] because the latter may not exist if we have
222  * some kind of resource error.
223  *
224  * We prefer to walk the device itself and then augment it with size information
225  * from reg[] as reg[] will skip unimplemented BARs and we want to be able to
226  * accurately indicate the type and other information of a BAR with zero size.
227  */
228 static int
pcieadm_bar_list(pcieadm_t * pcip,int argc,char * argv[])229 pcieadm_bar_list(pcieadm_t *pcip, int argc, char *argv[])
230 {
231 	int c, ret = EXIT_SUCCESS;
232 	const char *device = NULL;
233 	const pcieadm_ops_t *ops;
234 	void *readarg;
235 	uint8_t hdr, nbar;
236 	uint_t flags = 0;
237 	bool parse = false, *filts = NULL, found = false;
238 	const char *fields = NULL;
239 	ofmt_status_t oferr;
240 	ofmt_handle_t ofmt;
241 
242 	while ((c = getopt(argc, argv, ":d:Ho:p")) != -1) {
243 		switch (c) {
244 		case 'd':
245 			device = optarg;
246 			break;
247 		case 'H':
248 			flags |= OFMT_NOHEADER;
249 			break;
250 		case 'o':
251 			fields = optarg;
252 			break;
253 		case 'p':
254 			parse = true;
255 			flags |= OFMT_PARSABLE;
256 			break;
257 		case ':':
258 			pcieadm_bar_list_help("Option -%c requires an "
259 			    "argument", optopt);
260 			exit(EXIT_USAGE);
261 		case '?':
262 		default:
263 			pcieadm_bar_list_help("unknown option: -%c",
264 			    optopt);
265 			exit(EXIT_USAGE);
266 		}
267 	}
268 
269 	if (device == NULL) {
270 		pcieadm_bar_list_help("missing required device argument (-d)");
271 		exit(EXIT_USAGE);
272 	}
273 
274 	if (parse && fields == NULL) {
275 		errx(EXIT_USAGE, "-p requires fields specified with -o");
276 	}
277 
278 	if (fields == NULL) {
279 		fields = pcieadm_bar_list_fields;
280 	}
281 
282 	argc -= optind;
283 	argv += optind;
284 
285 	if (argc > 0) {
286 		filts = calloc(argc, sizeof (bool));
287 		if (filts == NULL) {
288 			err(EXIT_FAILURE, "failed to allocate filter tracking "
289 			    "memory");
290 		}
291 	}
292 
293 	oferr = ofmt_open(fields, pcieadm_bar_list_ofmt, flags, 0, &ofmt);
294 	ofmt_check(oferr, parse, ofmt, pcieadm_ofmt_errx, warnx);
295 
296 	/*
297 	 * We will need full privileges to get information from the device.
298 	 */
299 	priv_fillset(pcip->pia_priv_eff);
300 
301 	pcieadm_find_dip(pcip, device);
302 	int *reg;
303 	int nreg = di_prop_lookup_ints(DDI_DEV_T_ANY, pcip->pia_devi,
304 	    "reg", &reg);
305 	if (nreg < 0 && errno != ENXIO) {
306 		err(EXIT_FAILURE, "failed to look up reg[] property");
307 	} else if (nreg < 0) {
308 		nreg = 0;
309 	} else if (nreg % 5 != 0) {
310 		errx(EXIT_FAILURE, "reg[] property has wrong shape, found %d "
311 		    "integers but expected a multiple of 5", nreg);
312 	}
313 	nreg /= 5;
314 
315 	pcieadm_init_ops_kernel(pcip, &ops, &readarg);
316 	if (!ops->pop_cfg(PCI_CONF_HEADER, sizeof (hdr), &hdr, readarg)) {
317 		errx(EXIT_FAILURE, "failed to read offset %u from device "
318 		    "configuration space", PCI_CONF_HEADER);
319 	}
320 
321 	switch (hdr & PCI_HEADER_TYPE_M) {
322 	case PCI_HEADER_ZERO:
323 		nbar = PCI_BASE_NUM;
324 		break;
325 	case PCI_HEADER_ONE:
326 		nbar = PCI_BCNF_BASE_NUM;
327 		break;
328 	case PCI_HEADER_TWO:
329 	default:
330 		errx(EXIT_FAILURE, "unsupported PCI header type: 0x%x",
331 		    hdr & PCI_HEADER_TYPE_M);
332 	}
333 
334 	/*
335 	 * First do a pass where we read all of the raw BAR registers from the
336 	 * device.
337 	 */
338 	uint32_t raw[PCI_BASE_NUM];
339 	for (uint8_t i = 0; i < nbar; i++) {
340 		if (!ops->pop_cfg(PCI_CONF_BASE0 + i * 4, sizeof (uint32_t),
341 		    &raw[i], readarg)) {
342 			errx(EXIT_FAILURE, "failed to read BAR data form "
343 			    "device at offset 0x%x", PCI_CONF_BASE0 + i * 4);
344 		}
345 	}
346 
347 	/*
348 	 * Go through and process each BAR entry. Determine where we have a
349 	 * 64-bit BAR and therefore need to account for two entries here. This
350 	 * is also where we try to marry things up to assigned-addresses to
351 	 * determine the size.
352 	 */
353 	for (uint8_t i = 0; i < nbar; i++) {
354 		pcieadm_bar_list_ofmt_t arg;
355 
356 		(void) memset(&arg, 0, sizeof (arg));
357 		arg.pblo_idx = i;
358 		arg.pblo_width = PCI_BAR_SZ_32;
359 		arg.pblo_raw = raw[i];
360 		arg.pblo_prefetch = false;
361 
362 		if ((raw[i] & PCI_BASE_SPACE_M) == PCI_BASE_SPACE_IO) {
363 			arg.pblo_mem = false;
364 			arg.pblo_addr = raw[i] & PCI_BASE_IO_ADDR_M;
365 			arg.pblo_mtype = "--";
366 		} else {
367 			arg.pblo_mem = true;
368 			arg.pblo_addr = raw[i] & PCI_BASE_M_ADDR_M;
369 
370 			if ((raw[i] & PCI_BASE_TYPE_M) == PCI_BASE_TYPE_ALL) {
371 				i++;
372 				if (i == nbar) {
373 					warnx("device %s has corrupt BAR %u: "
374 					    "no additional BARs exist for "
375 					    "upper 32-bit data", device,
376 					    arg.pblo_idx);
377 					ret = EXIT_FAILURE;
378 					continue;
379 				}
380 				arg.pblo_width = PCI_BAR_SZ_64;
381 				arg.pblo_raw |= (uint64_t)raw[i] << 32;
382 				arg.pblo_addr |= (uint64_t)raw[i] << 32;
383 			}
384 
385 			if ((arg.pblo_raw & PCI_BASE_PREF_M) != 0) {
386 				arg.pblo_prefetch = true;
387 			}
388 
389 			switch (arg.pblo_raw & PCI_BASE_TYPE_M) {
390 			case PCI_BASE_TYPE_MEM:
391 				arg.pblo_mtype = "32-bit";
392 				break;
393 			case PCI_BASE_TYPE_LOW:
394 				arg.pblo_mtype = "1M";
395 				break;
396 			case PCI_BASE_TYPE_ALL:
397 				arg.pblo_mtype = "64-bit";
398 				break;
399 			case PCI_BASE_TYPE_RES:
400 				arg.pblo_mtype = "reserved";
401 				break;
402 			}
403 		}
404 
405 		/*
406 		 * Finally, before we call back, determine what the size of this
407 		 * BAR is. We walk reg[] to try to find a matching register
408 		 * index, which is considered the offset into configuration
409 		 * space of the start of this BAR.
410 		 *
411 		 * If we can't find a match, then it likely is because the BAR
412 		 * has no size. Using reg[] versus assigned-addresses[] gives us
413 		 * a reasonable confidence here, though this doesn't account for
414 		 * resizable BAR support.
415 		 */
416 		arg.pblo_size = 0;
417 		uint32_t targ = PCI_CONF_BASE0 + arg.pblo_idx *
418 		    sizeof (uint32_t);
419 		const pci_regspec_t *rsp = (pci_regspec_t *)reg;
420 		for (int ridx = 0; ridx < nreg; ridx++, rsp++) {
421 			uint32_t check = PCI_REG_REG_G(rsp->pci_phys_hi);
422 
423 			if (targ == check) {
424 				arg.pblo_size = (uint64_t)rsp->pci_size_hi <<
425 				    32;
426 				arg.pblo_size |= rsp->pci_size_low;
427 				break;
428 			}
429 		}
430 
431 		if (pcieadm_show_bar_match(&arg, argc, argv, filts)) {
432 			found = true;
433 			ofmt_print(ofmt, &arg);
434 		}
435 	}
436 
437 	for (int i = 0; i < argc; i++) {
438 		if (!filts[i]) {
439 			warnx("filter '%s' did not match any BARs", argv[i]);
440 			ret = EXIT_FAILURE;
441 		}
442 	}
443 
444 	if (!found) {
445 		ret = EXIT_FAILURE;
446 	}
447 
448 	free(filts);
449 	pcieadm_fini_ops_kernel(readarg);
450 	ofmt_close(ofmt);
451 
452 	return (ret);
453 }
454 
455 static void
pcieadm_bar_read_usage(FILE * f)456 pcieadm_bar_read_usage(FILE *f)
457 {
458 	(void) fprintf(f, "\tbar read\t[-l length] -d device -b bar reg\n");
459 }
460 
461 static void
pcieadm_bar_read_help(const char * fmt,...)462 pcieadm_bar_read_help(const char *fmt, ...)
463 {
464 	if (fmt != NULL) {
465 		va_list ap;
466 
467 		va_start(ap, fmt);
468 		vwarnx(fmt, ap);
469 		va_end(ap);
470 		(void) fprintf(stderr, "\n");
471 	}
472 
473 	(void) fprintf(stderr, "Usage:  %s bar read [-l length] -d device "
474 	    "-b bar reg\n", pcieadm_progname);
475 	(void) fprintf(stderr, "Read data at offset <reg> from a device "
476 	    "BAR.\n\n"
477 	    "\t-b bar\t\tthe index of the BAR to read from\n"
478 	    "\t-d device\tread BAR from the specified device (driver instance,"
479 	    "\n\t\t\t/devices path, or b/d/f)\n"
480 	    "\t-l length\tspecify the number of bytes to read: 1, 2, 4 "
481 	    "(default),\n\t\t\tor 8\n");
482 }
483 
484 /*
485  * We can't use strtonumx here as its maximum values are all based in signed
486  * integers.
487  */
488 static uint64_t
pcieadm_bar_parse_u64(const char * reg,const char * desc)489 pcieadm_bar_parse_u64(const char *reg, const char *desc)
490 {
491 	char *eptr;
492 	unsigned long long ull;
493 
494 	errno = 0;
495 	ull = strtoull(reg, &eptr, 0);
496 	if (errno != 0 || *eptr != '\0') {
497 		errx(EXIT_FAILURE, "failed to parse %s %s", desc, reg);
498 	}
499 
500 	return ((uint64_t)ull);
501 }
502 
503 static uint8_t
pcieadm_bar_parse_len(const char * len)504 pcieadm_bar_parse_len(const char *len)
505 {
506 	char *eptr;
507 	unsigned long ul;
508 
509 	errno = 0;
510 	ul = strtoul(len, &eptr, 0);
511 	if (errno != 0 || *eptr != '\0') {
512 		errx(EXIT_FAILURE, "failed to parse length %s", len);
513 	}
514 
515 	if (ul != 1 && ul != 2 && ul != 4 && ul != 8) {
516 		errx(EXIT_FAILURE, "invalid byte length 0x%lx: only 1, 2, 4, "
517 		    "and 8 byte I/Os are supported", ul);
518 	}
519 
520 	return ((uint8_t)ul);
521 }
522 
523 static uint8_t
pcieadm_bar_parse_bar(const char * bar)524 pcieadm_bar_parse_bar(const char *bar)
525 {
526 	uint8_t val;
527 	const char *errstr;
528 	val = (uint8_t)strtonumx(bar, 0, PCI_BASE_NUM - 1, &errstr, 0);
529 	if (errstr != NULL) {
530 		errx(EXIT_FAILURE, "failed to parse BAR %s: value is %s", bar,
531 		    errstr);
532 	}
533 
534 	return (val);
535 }
536 
537 static int
pcieadm_bar_read(pcieadm_t * pcip,int argc,char * argv[])538 pcieadm_bar_read(pcieadm_t *pcip, int argc, char *argv[])
539 {
540 	int c, ret = EXIT_SUCCESS;
541 	const char *device = NULL, *barstr = NULL, *lenstr = NULL;
542 	const pcieadm_ops_t *ops;
543 	void *karg;
544 
545 	while ((c = getopt(argc, argv, ":b:d:l:")) != -1) {
546 		switch (c) {
547 		case 'b':
548 			barstr = optarg;
549 			break;
550 		case 'd':
551 			device = optarg;
552 			break;
553 		case 'l':
554 			lenstr = optarg;
555 			break;
556 		case ':':
557 			pcieadm_bar_read_help("Option -%c requires an "
558 			    "argument", optopt);
559 			exit(EXIT_USAGE);
560 		case '?':
561 		default:
562 			pcieadm_bar_read_help("unknown option: -%c",
563 			    optopt);
564 			exit(EXIT_USAGE);
565 		}
566 	}
567 
568 	if (device == NULL) {
569 		pcieadm_bar_read_help("missing required device argument (-d)");
570 		exit(EXIT_USAGE);
571 	}
572 
573 	if (barstr == NULL) {
574 		pcieadm_bar_read_help("missing required bar argument (-b)");
575 		exit(EXIT_USAGE);
576 	}
577 
578 	argc -= optind;
579 	argv += optind;
580 
581 	if (argc == 0) {
582 		errx(EXIT_FAILURE, "missing required register");
583 	} else if (argc > 1) {
584 		errx(EXIT_FAILURE, "only a single register may be read");
585 	}
586 
587 	uint8_t bar = pcieadm_bar_parse_bar(barstr);
588 	uint8_t len = 4;
589 	if (lenstr != NULL) {
590 		len = pcieadm_bar_parse_len(lenstr);
591 	}
592 	uint64_t reg = pcieadm_bar_parse_u64(argv[0], "register");
593 	void *buf = calloc(1, len);
594 	if (buf == NULL) {
595 		err(EXIT_FAILURE, "failed to allocate memory for read request");
596 	}
597 
598 	/*
599 	 * We will need full privileges to read a BAR.
600 	 */
601 	priv_fillset(pcip->pia_priv_eff);
602 
603 	pcieadm_find_dip(pcip, device);
604 	pcieadm_init_ops_kernel(pcip, &ops, &karg);
605 
606 	if (!ops->pop_bar(bar, len, reg, buf, karg, B_FALSE)) {
607 		errx(EXIT_FAILURE, "failed to read %u bytes at 0x%" PRIx64
608 		    " from BAR %u", len, reg, bar);
609 	}
610 
611 	switch (len) {
612 	case 1:
613 		(void) printf("0x%x\n", *(uint8_t *)buf);
614 		break;
615 	case 2:
616 		(void) printf("0x%x\n", *(uint16_t *)buf);
617 		break;
618 	case 4:
619 		(void) printf("0x%x\n", *(uint32_t *)buf);
620 		break;
621 	case 8:
622 		(void) printf("0x%x\n", *(uint64_t *)buf);
623 		break;
624 	default:
625 		abort();
626 	}
627 
628 	free(buf);
629 	pcieadm_fini_ops_kernel(karg);
630 
631 	return (ret);
632 }
633 
634 static void
pcieadm_bar_write_usage(FILE * f)635 pcieadm_bar_write_usage(FILE *f)
636 {
637 	(void) fprintf(f, "\tbar write\t[-l length] -d device -b bar "
638 	    "reg=value\n");
639 }
640 
641 static void
pcieadm_bar_write_help(const char * fmt,...)642 pcieadm_bar_write_help(const char *fmt, ...)
643 {
644 	if (fmt != NULL) {
645 		va_list ap;
646 
647 		va_start(ap, fmt);
648 		vwarnx(fmt, ap);
649 		va_end(ap);
650 		(void) fprintf(stderr, "\n");
651 	}
652 
653 	(void) fprintf(stderr, "Usage:  %s bar write [-l length] -d device "
654 	    "-b bar reg=value", pcieadm_progname);
655 	(void) fprintf(stderr, "Usage:  %s bar write [-l length] -d device "
656 	    "-b bar reg=value\n", pcieadm_progname);
657 	(void) fprintf(stderr, "Write data to a device BAR at offset <reg>.\n\n"
658 	    "\t-b bar\t\tthe index of the BAR to write to\n"
659 	    "\t-d device\tread BAR from the specified device (driver instance,"
660 	    "\n\t\t\t/devices path, or b/d/f)\n"
661 	    "\t-l length\tspecify the number of bytes to read: 1, 2, 4 "
662 	    "(default),\n\t\t\tor 8\n");
663 
664 }
665 
666 static int
pcieadm_bar_write(pcieadm_t * pcip,int argc,char * argv[])667 pcieadm_bar_write(pcieadm_t *pcip, int argc, char *argv[])
668 {
669 	int c, ret = EXIT_SUCCESS;
670 	const char *device = NULL, *barstr = NULL, *lenstr = NULL;
671 	const pcieadm_ops_t *ops;
672 	void *karg;
673 
674 	while ((c = getopt(argc, argv, ":b:d:l:")) != -1) {
675 		switch (c) {
676 		case 'b':
677 			barstr = optarg;
678 			break;
679 		case 'd':
680 			device = optarg;
681 			break;
682 		case 'l':
683 			lenstr = optarg;
684 			break;
685 		case ':':
686 			pcieadm_bar_write_help("Option -%c requires an "
687 			    "argument", optopt);
688 			exit(EXIT_USAGE);
689 		case '?':
690 		default:
691 			pcieadm_bar_write_help("unknown option: -%c",
692 			    optopt);
693 			exit(EXIT_USAGE);
694 		}
695 	}
696 
697 	if (device == NULL) {
698 		pcieadm_bar_write_help("missing required device argument (-d)");
699 		exit(EXIT_USAGE);
700 	}
701 
702 	if (barstr == NULL) {
703 		pcieadm_bar_write_help("missing required bar argument (-b)");
704 		exit(EXIT_USAGE);
705 	}
706 
707 	argc -= optind;
708 	argv += optind;
709 
710 	if (argc == 0) {
711 		errx(EXIT_FAILURE, "missing required register");
712 	} else if (argc > 1) {
713 		errx(EXIT_FAILURE, "only a single register may be read");
714 	}
715 	char *eq = strchr(argv[0], '=');
716 	if (eq == NULL) {
717 		errx(EXIT_FAILURE, "failed to parse value string %s: missing "
718 		    "equals ('=') separator", argv[0]);
719 	} else if (eq[1] == '\0') {
720 		errx(EXIT_FAILURE, "missing a value after the equals ('=') "
721 		    "separator");
722 	}
723 	*eq = '\0';
724 
725 	uint8_t bar = pcieadm_bar_parse_bar(barstr);
726 	uint8_t len = 4;
727 	if (lenstr != NULL) {
728 		len = pcieadm_bar_parse_len(lenstr);
729 	}
730 	void *buf = malloc(len);
731 	if (buf == NULL) {
732 		err(EXIT_FAILURE, "failed to allocate memory for read request");
733 	}
734 	uint64_t reg = pcieadm_bar_parse_u64(argv[0], "register");
735 	uint64_t value = pcieadm_bar_parse_u64(eq + 1, "value");
736 
737 	uint8_t u8;
738 	uint16_t u16;
739 	uint32_t u32;
740 	switch (len) {
741 	case 1:
742 		if (value > UINT8_MAX) {
743 			errx(EXIT_FAILURE, "value %s is too large for a "
744 			    "1 byte write", eq + 1);
745 		}
746 		u8 = (uint8_t)value;
747 		(void) memcpy(buf, &u8, sizeof (uint8_t));
748 		break;
749 	case 2:
750 		if (value > UINT16_MAX) {
751 			errx(EXIT_FAILURE, "value %s is too large for a "
752 			    "2 byte write", eq + 1);
753 		}
754 		u16 = (uint8_t)value;
755 		(void) memcpy(buf, &u16, sizeof (uint16_t));
756 		break;
757 	case 4:
758 		if (value > UINT32_MAX) {
759 			errx(EXIT_FAILURE, "value %s is too large for a "
760 			    "4 byte write", eq + 1);
761 		}
762 		u32 = (uint32_t)value;
763 		(void) memcpy(buf, &u32, sizeof (uint32_t));
764 		break;
765 	case 8:
766 		(void) memcpy(buf, &value, sizeof (uint64_t));
767 		break;
768 	default:
769 		abort();
770 	}
771 
772 
773 	/*
774 	 * We will need full privileges to read a BAR.
775 	 */
776 	priv_fillset(pcip->pia_priv_eff);
777 
778 	pcieadm_find_dip(pcip, device);
779 	pcieadm_init_ops_kernel(pcip, &ops, &karg);
780 
781 	if (!ops->pop_bar(bar, len, reg, buf, karg, B_TRUE)) {
782 		errx(EXIT_FAILURE, "failed to write %u bytes at 0x%" PRIx64
783 		    " from BAR %u", len, reg, bar);
784 	}
785 
786 	pcieadm_fini_ops_kernel(karg);
787 
788 	return (ret);
789 }
790 
791 
792 static const pcieadm_cmdtab_t pcieadm_cmds_dev[] = {
793 	{ "list", pcieadm_bar_list, pcieadm_bar_list_usage },
794 	{ "read", pcieadm_bar_read, pcieadm_bar_read_usage },
795 	{ "write", pcieadm_bar_write, pcieadm_bar_write_usage },
796 	{ NULL }
797 };
798 
799 int
pcieadm_bar(pcieadm_t * pcip,int argc,char * argv[])800 pcieadm_bar(pcieadm_t *pcip, int argc, char *argv[])
801 {
802 	return (pcieadm_walk_tab(pcip, pcieadm_cmds_dev, argc, argv));
803 }
804 
805 void
pcieadm_bar_usage(FILE * f)806 pcieadm_bar_usage(FILE *f)
807 {
808 	pcieadm_walk_usage(pcieadm_cmds_dev, f);
809 }
810