xref: /illumos-gate/usr/src/cmd/fwflash/plugins/transport/common/ufm.c (revision 6f1fa39e3cf1b335f342bbca41590e9d76ab29b7)
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 2020 Oxide Computer Company
14  */
15 
16 /*
17  * fwflash(1M) backend for UFMs.
18  */
19 
20 #include <libdevinfo.h>
21 #include <strings.h>
22 #include <libintl.h>
23 #include <pcidb.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <fcntl.h>
27 #include <unistd.h>
28 #include <libnvpair.h>
29 #include <sys/ddi_ufm.h>
30 #include <sys/sysmacros.h>
31 #include <fwflash/fwflash.h>
32 
33 /*
34  * We pick a fixed size unit to work on.
35  */
36 #define	UFM_READ_BUFLEN	(16 * 1024 * 1024)
37 
38 /*
39  * These are indexes into the addresses array that we use.
40  */
41 #define	UFM_ADDR_PATH	0
42 #define	UFM_ADDR_SUB	1
43 #define	UFM_ADDR_CAP	2
44 
45 typedef struct ufmfw_ident_arg {
46 	uint_t uia_nfound;
47 	int uia_index;
48 	int uia_err;
49 } ufmfw_ident_arg_t;
50 
51 /*
52  * fwflash requires we declare our driver name as data with this name.
53  */
54 const char drivername[] = "ufm";
55 const int plugin_version = FWPLUGIN_VERSION_2;
56 
57 /*
58  * External data from fwflash.
59  */
60 extern di_node_t rootnode;
61 extern struct fw_plugin *self;
62 
63 /*
64  * Global, shared data.
65  */
66 static int ufmfw_ufm_fd = -1;
67 static pcidb_hdl_t *ufmfw_pcidb;
68 static boolean_t ufmfw_ready = B_FALSE;
69 
70 /*
71  * Read image zero and slot zero that we find.
72  */
73 int
74 fw_readfw(struct devicelist *flashdev, const char *filename)
75 {
76 	nvlist_t **images, **slots;
77 	uint_t nimages, nslots, caps;
78 	uint64_t imgsize, offset;
79 	void *buf;
80 	int fd;
81 	nvlist_t *nvl = flashdev->ident->encap_ident;
82 
83 	caps = (uintptr_t)flashdev->addresses[UFM_ADDR_CAP];
84 	if ((caps & DDI_UFM_CAP_READIMG) == 0) {
85 		logmsg(MSG_ERROR, "%s: device %s does not support reading "
86 		    "images\n", flashdev->drvname, flashdev->access_devname);
87 		return (FWFLASH_FAILURE);
88 	}
89 
90 	if (nvlist_lookup_nvlist_array(nvl, DDI_UFM_NV_IMAGES, &images,
91 	    &nimages) != 0) {
92 		logmsg(MSG_ERROR, gettext("%s: %s missing UFM image data\n"),
93 		    flashdev->drvname, flashdev->access_devname);
94 		return (FWFLASH_FAILURE);
95 	}
96 
97 	if (nimages == 0) {
98 		logmsg(MSG_ERROR, gettext("%s: %s has no UFM images\n"),
99 		    flashdev->drvname, flashdev->access_devname);
100 		return (FWFLASH_FAILURE);
101 	}
102 
103 	if (nvlist_lookup_nvlist_array(images[0], DDI_UFM_NV_IMAGE_SLOTS,
104 	    &slots, &nslots) != 0) {
105 		logmsg(MSG_ERROR, gettext("%s: image zero of %s has no "
106 		    "slots\n"), flashdev->drvname, flashdev->access_devname);
107 		return (FWFLASH_FAILURE);
108 	}
109 
110 	if (nvlist_lookup_uint64(slots[0], DDI_UFM_NV_SLOT_IMGSIZE,
111 	    &imgsize) != 0) {
112 		logmsg(MSG_ERROR, gettext("%s: device %s doesn't have an image "
113 		    "size\n"), flashdev->drvname, flashdev->access_devname);
114 		return (FWFLASH_FAILURE);
115 	}
116 
117 	logmsg(MSG_INFO, gettext("%s: Need to read %" PRIu64 " bytes\n"),
118 	    flashdev->drvname, imgsize);
119 
120 	if ((buf = malloc(UFM_READ_BUFLEN)) == NULL) {
121 		logmsg(MSG_ERROR, gettext("%s: Failed to allocate data "
122 		    "buffer\n"), flashdev->drvname);
123 		return (FWFLASH_FAILURE);
124 	}
125 
126 	if ((fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0644)) < 0) {
127 		logmsg(MSG_ERROR, gettext("%s: failed to open file %s: %s\n"),
128 		    flashdev->drvname, filename, strerror(errno));
129 		free(buf);
130 		return (FWFLASH_FAILURE);
131 	}
132 
133 	offset = 0;
134 	while (imgsize > 0) {
135 		ufm_ioc_readimg_t rimg;
136 		uint64_t toread = MIN(imgsize, UFM_READ_BUFLEN);
137 		size_t woff;
138 
139 		bzero(&rimg, sizeof (rimg));
140 		rimg.ufri_version = DDI_UFM_CURRENT_VERSION;
141 		rimg.ufri_imageno = 0;
142 		rimg.ufri_slotno = 0;
143 		rimg.ufri_offset = offset;
144 		rimg.ufri_len = toread;
145 		rimg.ufri_buf = buf;
146 		(void) strlcpy(rimg.ufri_devpath,
147 		    flashdev->addresses[UFM_ADDR_PATH],
148 		    sizeof (rimg.ufri_devpath));
149 		logmsg(MSG_INFO, gettext("%s: want to read %" PRIu64 " bytes "
150 		    "at offset %" PRIu64 "\n"), flashdev->drvname,
151 		    rimg.ufri_len, rimg.ufri_offset);
152 
153 		if (ioctl(ufmfw_ufm_fd, UFM_IOC_READIMG, &rimg) != 0) {
154 			logmsg(MSG_ERROR, gettext("%s: failed to read image: "
155 			    "%s\n"), flashdev->drvname, strerror(errno));
156 			free(buf);
157 			(void) close(fd);
158 			return (FWFLASH_FAILURE);
159 		}
160 
161 		logmsg(MSG_INFO, gettext("%s: read %" PRIu64 " bytes at offset "
162 		    "%" PRIu64 "\n"), flashdev->drvname, rimg.ufri_nread,
163 		    offset);
164 		offset += rimg.ufri_nread;
165 		imgsize -= rimg.ufri_nread;
166 
167 		woff = 0;
168 		while (rimg.ufri_nread > 0) {
169 			size_t towrite = MIN(rimg.ufri_nread, UFM_READ_BUFLEN);
170 			ssize_t ret = write(fd, buf + woff, towrite);
171 			if (ret == -1) {
172 				logmsg(MSG_ERROR, gettext("%s: failed to write "
173 				    "to %s: %s\n"), flashdev->drvname, filename,
174 				    strerror(errno));
175 				free(buf);
176 				(void) close(fd);
177 				return (FWFLASH_FAILURE);
178 			}
179 
180 			rimg.ufri_nread -= ret;
181 			woff += ret;
182 		}
183 	}
184 
185 	free(buf);
186 	if (close(fd) != 0) {
187 		logmsg(MSG_ERROR, gettext("%s: failed to finish writing to %s: "
188 		    "%s\n"), flashdev->drvname, filename, strerror(errno));
189 		return (FWFLASH_FAILURE);
190 	}
191 	logmsg(MSG_INFO, gettext("%s: successfully wrote image to %s\n"),
192 	    flashdev->drvname, filename);
193 	return (FWFLASH_SUCCESS);
194 }
195 
196 
197 int
198 fw_writefw(struct devicelist *flashdev)
199 {
200 	return (FWFLASH_SUCCESS);
201 }
202 
203 static void
204 ufmfw_flashdev_free(struct devicelist *flashdev)
205 {
206 	if (flashdev == NULL)
207 		return;
208 	if (flashdev->ident != NULL) {
209 		free(flashdev->ident->vid);
210 		free(flashdev->ident->pid);
211 		nvlist_free(flashdev->ident->encap_ident);
212 	}
213 	free(flashdev->ident);
214 	free(flashdev->drvname);
215 	free(flashdev->classname);
216 	free(flashdev->access_devname);
217 	di_devfs_path_free(flashdev->addresses[UFM_ADDR_PATH]);
218 	free(flashdev->addresses[UFM_ADDR_SUB]);
219 	free(flashdev);
220 }
221 
222 /*
223  * Check if a node is a PCI device. This is so we can deal with VPD information.
224  * Hopefully we'll have a generalized devinfo or fmtopo VPD section which we can
225  * then use for this instead.
226  */
227 static boolean_t
228 ufmfw_node_pci(di_node_t node)
229 {
230 	while (node != DI_NODE_NIL) {
231 		char *strs;
232 		int ret = di_prop_lookup_strings(DDI_DEV_T_ANY, node,
233 		    "device_type", &strs);
234 
235 		if (ret > 0) {
236 			if (strcmp(strs, "pci") == 0 ||
237 			    strcmp(strs, "pciex") == 0) {
238 				return (B_TRUE);
239 			}
240 		}
241 
242 		node = di_parent_node(node);
243 	}
244 	return (B_FALSE);
245 }
246 
247 /*
248  * Cons up VPD information based on the PCI ID. Hopefully in time we'll use the
249  * actual PCI VPD information and more generally allow a device to specify its
250  * vpd automatically.
251  */
252 static boolean_t
253 ufmfw_fill_vpd(struct devicelist *flashdev, di_node_t node)
254 {
255 	int *vid, *did, *svid, *sdid;
256 	pcidb_vendor_t *vend = NULL;
257 	pcidb_device_t *dev = NULL;
258 	pcidb_subvd_t *subdv = NULL;
259 	char *vstr, *dstr;
260 
261 	if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "vendor-id", &vid) != 1) {
262 		logmsg(MSG_ERROR, gettext("%s: %s missing 'vendor-id' "
263 		    "property\n"), flashdev->drvname, flashdev->access_devname);
264 		return (B_FALSE);
265 	}
266 
267 	if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "device-id", &did) != 1) {
268 		logmsg(MSG_ERROR, gettext("%s: %s missing 'device-id' "
269 		    "property\n"), flashdev->drvname, flashdev->access_devname);
270 		return (B_FALSE);
271 	}
272 
273 	if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "subsystem-vendor-id",
274 	    &svid) != 1 || di_prop_lookup_ints(DDI_DEV_T_ANY, node,
275 	    "subsystem-device-id", &sdid) != 1) {
276 		svid = NULL;
277 		sdid = NULL;
278 	}
279 
280 	vend = pcidb_lookup_vendor(ufmfw_pcidb, vid[0]);
281 	if (vend != NULL) {
282 		dev = pcidb_lookup_device_by_vendor(vend, did[0]);
283 	}
284 
285 	if (dev != NULL && svid != NULL && sdid != NULL) {
286 		subdv = pcidb_lookup_subvd_by_device(dev, svid[0], sdid[0]);
287 	}
288 
289 	if (vend != NULL) {
290 		vstr = strdup(pcidb_vendor_name(vend));
291 	} else {
292 		(void) asprintf(&vstr, "pci:%x", vid[0]);
293 	}
294 
295 	if (vstr == NULL) {
296 		logmsg(MSG_ERROR, gettext("%s: failed to allocate vid "
297 		    "string\n"), flashdev->drvname);
298 		return (B_FALSE);
299 	}
300 	flashdev->ident->vid = vstr;
301 
302 	if (dstr != NULL) {
303 		dstr = strdup(pcidb_device_name(dev));
304 	} else {
305 		(void) asprintf(&dstr, "pci:%x", did[0]);
306 	}
307 
308 	if (dstr == NULL) {
309 		logmsg(MSG_ERROR, gettext("%s: failed to allocate pid "
310 		    "string\n"), flashdev->drvname);
311 		return (B_FALSE);
312 	}
313 	flashdev->ident->pid = dstr;
314 
315 	if (subdv != NULL) {
316 		/*
317 		 * Because this is optional, don't fail if we fail to duplicate
318 		 * this.
319 		 */
320 		flashdev->addresses[UFM_ADDR_SUB] =
321 		    strdup(pcidb_subvd_name(subdv));
322 		if (flashdev->addresses[UFM_ADDR_SUB] == NULL) {
323 			logmsg(MSG_WARN, gettext("%s: failed to allocate vpd "
324 			    "subsystem name\n"), flashdev->drvname);
325 		}
326 	}
327 
328 	return (B_TRUE);
329 }
330 
331 static int
332 ufmfw_di_walk_cb(di_node_t node, void *arg)
333 {
334 	int ret;
335 	boolean_t found = B_FALSE;
336 	di_prop_t prop = DI_PROP_NIL;
337 	ufmfw_ident_arg_t *uia = arg;
338 	struct devicelist *flashdev = NULL;
339 	ufm_ioc_getcaps_t caps;
340 	ufm_ioc_bufsz_t bufsz;
341 	ufm_ioc_report_t rep;
342 	char *devfs, *packnvl;
343 	nvlist_t *nvl = NULL;
344 
345 	while ((prop = di_prop_next(node, prop)) != DI_PROP_NIL) {
346 		const char *pname = di_prop_name(prop);
347 		if (strcmp(pname, "ddi-ufm-capable") == 0) {
348 			found = B_TRUE;
349 			break;
350 		}
351 	}
352 
353 	if (!found) {
354 		return (DI_WALK_CONTINUE);
355 	}
356 
357 	if (!ufmfw_node_pci(node)) {
358 		return (DI_WALK_CONTINUE);
359 	}
360 
361 	if ((devfs = di_devfs_path(node)) == NULL) {
362 		logmsg(MSG_ERROR, gettext("%s: failed to get device node "
363 		    "path\n"), drivername);
364 		goto err;
365 	}
366 
367 	bzero(&caps, sizeof (caps));
368 	caps.ufmg_version = DDI_UFM_CURRENT_VERSION;
369 	(void) strlcpy(caps.ufmg_devpath, devfs, sizeof (caps.ufmg_devpath));
370 	if (ioctl(ufmfw_ufm_fd, UFM_IOC_GETCAPS, &caps) != 0) {
371 		logmsg(MSG_ERROR, gettext("%s: failed to get UFM caps for "
372 		    "UFM compatible device %s: %s\n"), drivername, devfs,
373 		    strerror(errno));
374 		di_devfs_path_free(devfs);
375 		return (DI_WALK_CONTINUE);
376 	}
377 
378 	/*
379 	 * If nothing is supported just leave it be.
380 	 */
381 	if (caps.ufmg_caps == 0) {
382 		di_devfs_path_free(devfs);
383 		return (DI_WALK_CONTINUE);
384 	}
385 
386 	bzero(&bufsz, sizeof (bufsz));
387 	bufsz.ufbz_version = DDI_UFM_CURRENT_VERSION;
388 	(void) strlcpy(bufsz.ufbz_devpath, devfs, sizeof (bufsz.ufbz_devpath));
389 	if (ioctl(ufmfw_ufm_fd, UFM_IOC_REPORTSZ, &bufsz) != 0) {
390 		logmsg(MSG_ERROR, gettext("%s: failed to get UFM report size "
391 		    "for device %s: %s\n"), drivername, devfs,
392 		    strerror(errno));
393 		di_devfs_path_free(devfs);
394 		return (DI_WALK_CONTINUE);
395 	}
396 
397 	if ((packnvl = malloc(bufsz.ufbz_size)) == NULL) {
398 		logmsg(MSG_ERROR, gettext("%s: failed to allocate %zu bytes "
399 		    "for report buffer\n"), drivername, bufsz.ufbz_size);
400 		di_devfs_path_free(devfs);
401 		goto err;
402 	}
403 	bzero(&rep, sizeof (rep));
404 	rep.ufmr_version = DDI_UFM_CURRENT_VERSION;
405 	rep.ufmr_bufsz = bufsz.ufbz_size;
406 	rep.ufmr_buf = packnvl;
407 	(void) strlcpy(rep.ufmr_devpath, devfs, sizeof (rep.ufmr_devpath));
408 	if (ioctl(ufmfw_ufm_fd, UFM_IOC_REPORT, &rep) != 0) {
409 		logmsg(MSG_ERROR, gettext("%s: failed to get UFM report "
410 		    "for device %s: %s\n"), drivername, devfs,
411 		    strerror(errno));
412 		free(packnvl);
413 		di_devfs_path_free(devfs);
414 		return (DI_WALK_CONTINUE);
415 	}
416 
417 	if ((ret = nvlist_unpack(packnvl, rep.ufmr_bufsz, &nvl, 0)) != 0) {
418 		logmsg(MSG_ERROR, gettext("%s: failed to unpack UFM report "
419 		    "for device %s: %s\n"), drivername, devfs, strerror(ret));
420 		free(packnvl);
421 		di_devfs_path_free(devfs);
422 		return (DI_WALK_CONTINUE);
423 
424 	}
425 	free(packnvl);
426 
427 	if ((flashdev = calloc(1, sizeof (*flashdev))) == NULL) {
428 		logmsg(MSG_ERROR, gettext("%s: failed to allocate new "
429 		    "device entry for node %s\n"), drivername, devfs);
430 		di_devfs_path_free(devfs);
431 		goto err;
432 	}
433 
434 	flashdev->addresses[UFM_ADDR_PATH] = devfs;
435 
436 	if (asprintf(&flashdev->access_devname, "/devices%s",
437 	    flashdev->addresses[UFM_ADDR_PATH]) == -1) {
438 		logmsg(MSG_ERROR, gettext("%s: failed to construct device "
439 		    "path\n"), drivername);
440 		goto err;
441 	}
442 	if ((flashdev->drvname = strdup(drivername)) == NULL) {
443 		logmsg(MSG_ERROR, gettext("%s: failed to construct driver "
444 		    "name\n"), drivername);
445 		goto err;
446 	}
447 	if ((flashdev->classname = strdup(drivername)) == NULL) {
448 		logmsg(MSG_ERROR, gettext("%s: failed to allocate vpd "
449 		    "data\n"), drivername);
450 		goto err;
451 	}
452 
453 	if ((flashdev->ident = calloc(1, sizeof (struct vpr))) == NULL) {
454 		logmsg(MSG_ERROR, gettext("%s: failed to construct class "
455 		    "name\n"), drivername);
456 		goto err;
457 	}
458 	if (!ufmfw_fill_vpd(flashdev, node)) {
459 		goto err;
460 	}
461 
462 	flashdev->ident->encap_ident = nvl;
463 
464 	flashdev->index = uia->uia_index;
465 	uia->uia_index++;
466 	flashdev->addresses[UFM_ADDR_CAP] = (void *)(uintptr_t)caps.ufmg_caps;
467 	flashdev->plugin = self;
468 	uia->uia_nfound++;
469 
470 	TAILQ_INSERT_TAIL(fw_devices, flashdev, nextdev);
471 
472 	return (DI_WALK_CONTINUE);
473 
474 err:
475 	nvlist_free(nvl);
476 	uia->uia_err = FWFLASH_FAILURE;
477 	ufmfw_flashdev_free(flashdev);
478 	return (DI_WALK_TERMINATE);
479 }
480 
481 int
482 fw_identify(int start)
483 {
484 	ufmfw_ident_arg_t uia;
485 
486 	if (!ufmfw_ready) {
487 		return (FWFLASH_FAILURE);
488 	}
489 
490 	uia.uia_nfound = 0;
491 	uia.uia_index = start;
492 	uia.uia_err = FWFLASH_SUCCESS;
493 	(void) di_walk_node(rootnode, DI_WALK_CLDFIRST, &uia,
494 	    ufmfw_di_walk_cb);
495 	if (uia.uia_nfound == 0) {
496 		return (FWFLASH_FAILURE);
497 	}
498 
499 	return (uia.uia_err);
500 }
501 
502 int
503 fw_devinfo(struct devicelist *flashdev)
504 {
505 	nvlist_t *nvl, **images;
506 	uint_t nimages, img, caps;
507 
508 	(void) printf(gettext("Device[%d] %s\n"), flashdev->index,
509 	    flashdev->access_devname);
510 	(void) printf(gettext("Class [%s]\n"), flashdev->classname);
511 	(void) printf(gettext("\tVendor: %s\n\tDevice: %s\n"),
512 	    flashdev->ident->vid, flashdev->ident->pid);
513 	if (flashdev->addresses[UFM_ADDR_SUB] != NULL) {
514 		(void) printf(gettext("\tSubsystem: %s\n"),
515 		    flashdev->addresses[UFM_ADDR_SUB]);
516 	}
517 
518 	caps = (uintptr_t)flashdev->addresses[UFM_ADDR_CAP];
519 	if (caps != 0) {
520 		boolean_t first = B_TRUE;
521 		(void) printf(gettext("\tCapabilities: "));
522 		if (caps & DDI_UFM_CAP_REPORT) {
523 			(void) printf(gettext("Report"));
524 			first = B_FALSE;
525 		}
526 
527 		if (caps & DDI_UFM_CAP_READIMG) {
528 			(void) printf(gettext("%sRead Image"),
529 			    first ? "" : ", ");
530 		}
531 		(void) printf("\n");
532 	}
533 
534 	nvl = flashdev->ident->encap_ident;
535 	if (nvlist_lookup_nvlist_array(nvl, DDI_UFM_NV_IMAGES, &images,
536 	    &nimages) != 0) {
537 		goto done;
538 	}
539 
540 	for (img = 0; img < nimages; img++) {
541 		nvlist_t **slots;
542 		uint_t nslots, s;
543 		char *desc;
544 
545 		if (nvlist_lookup_nvlist_array(images[img],
546 		    DDI_UFM_NV_IMAGE_SLOTS, &slots, &nslots) != 0) {
547 			goto done;
548 		}
549 
550 		if (nvlist_lookup_string(images[img], DDI_UFM_NV_IMAGE_DESC,
551 		    &desc) != 0) {
552 			desc = NULL;
553 		}
554 
555 		if (desc != NULL) {
556 			(void) printf(gettext("\tImage %d: %s\n"), img, desc);
557 		} else {
558 			(void) printf(gettext("\tImage %d:\n"), img);
559 		}
560 
561 		for (s = 0; s < nslots; s++) {
562 			uint32_t attr;
563 			char *version;
564 
565 			if (nvlist_lookup_uint32(slots[s], DDI_UFM_NV_SLOT_ATTR,
566 			    &attr) != 0) {
567 				attr = 0;
568 			}
569 
570 			if (nvlist_lookup_string(slots[s],
571 			    DDI_UFM_NV_SLOT_VERSION, &version) != 0) {
572 				version = "<unknown>";
573 			}
574 
575 			printf(gettext("\t    Slot %d (%c|%c|%c): %s\n"), s,
576 			    attr & DDI_UFM_ATTR_READABLE ? 'r' : '-',
577 			    attr & DDI_UFM_ATTR_WRITEABLE ? 'w' : '-',
578 			    attr & DDI_UFM_ATTR_ACTIVE ? 'a' : '-',
579 			    attr & DDI_UFM_ATTR_EMPTY ? "<empty>" : version);
580 
581 		}
582 	}
583 
584 done:
585 	(void) printf("\n\n");
586 	return (FWFLASH_SUCCESS);
587 }
588 
589 void
590 fw_cleanup(struct devicelist *flashdev)
591 {
592 	ufmfw_flashdev_free(flashdev);
593 }
594 
595 #pragma init(ufmfw_init)
596 static void
597 ufmfw_init(void)
598 {
599 	ufmfw_ufm_fd = open("/dev/ufm", O_RDONLY);
600 	if (ufmfw_ufm_fd < 0) {
601 		logmsg(MSG_ERROR, gettext("%s: failed to open /dev/ufm: %s\n"),
602 		    drivername, strerror(errno));
603 		return;
604 	}
605 
606 	ufmfw_pcidb = pcidb_open(PCIDB_VERSION);
607 	if (ufmfw_pcidb == NULL) {
608 		logmsg(MSG_ERROR, gettext("%s: failed to open libpcidb: %s\n"),
609 		    drivername, strerror(errno));
610 		return;
611 	}
612 	ufmfw_ready = B_TRUE;
613 }
614 
615 #pragma fini(ufmfw_fini)
616 static void
617 ufmfw_fini(void)
618 {
619 	pcidb_close(ufmfw_pcidb);
620 	if (ufmfw_ufm_fd >= 0) {
621 		(void) close(ufmfw_ufm_fd);
622 	}
623 	ufmfw_ready = B_FALSE;
624 }
625