xref: /illumos-gate/usr/src/cmd/devfsadm/usb_link.c (revision abb88ab1b9516b1ca12094db7f2cfb5d91e0a135)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  *
21  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
22  * Use is subject to license terms.
23  */
24 /*
25  * Copyright 2019, Joyent, Inc.
26  */
27 
28 #include <devfsadm.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <limits.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <strings.h>
37 
38 extern char *devfsadm_get_devices_dir();
39 static int usb_process(di_minor_t minor, di_node_t node);
40 
41 static void ugen_create_link(char *p_path, char *node_name,
42     di_node_t node, di_minor_t minor);
43 static void ccid_create_link(char *p_path, char *node_name,
44     di_node_t node, di_minor_t minor);
45 
46 
47 /* Rules for creating links */
48 static devfsadm_create_t usb_cbt[] = {
49 	{ "usb", NULL, "usb_ac",	DRV_EXACT,
50 						ILEVEL_0, usb_process },
51 	{ "usb", NULL, "usb_as",	DRV_EXACT,
52 						ILEVEL_0, usb_process },
53 	{ "usb", NULL, "ddivs_usbc",	DRV_EXACT,
54 						ILEVEL_0, usb_process },
55 	{ "usb", NULL, "usbvc",		DRV_EXACT,
56 						ILEVEL_0, usb_process },
57 	{ "usb", NULL, "hid",		DRV_EXACT,
58 						ILEVEL_0, usb_process },
59 	{ "usb", NULL, "hwarc",	DRV_EXACT,
60 						ILEVEL_0, usb_process },
61 	{ "usb", NULL, "wusb_ca",	DRV_EXACT,
62 						ILEVEL_0, usb_process },
63 	{ "usb", DDI_NT_NEXUS, "hubd",	DRV_EXACT|TYPE_EXACT,
64 						ILEVEL_0, usb_process },
65 	{ "usb", DDI_NT_NEXUS, "ohci",	DRV_EXACT|TYPE_EXACT,
66 						ILEVEL_0, usb_process },
67 	{ "usb", DDI_NT_NEXUS, "ehci",	DRV_EXACT|TYPE_EXACT,
68 						ILEVEL_0, usb_process },
69 	{ "usb", DDI_NT_NEXUS, "xhci",	DRV_EXACT|TYPE_EXACT,
70 						ILEVEL_0, usb_process },
71 	{ "usb", DDI_NT_SCSI_NEXUS, "scsa2usb",	DRV_EXACT|TYPE_EXACT,
72 						ILEVEL_0, usb_process },
73 	{ "usb", DDI_NT_UGEN, "scsa2usb",	DRV_EXACT|TYPE_EXACT,
74 						ILEVEL_0, usb_process },
75 	{ "usb", DDI_NT_NEXUS, "uhci",	DRV_EXACT|TYPE_EXACT,
76 						ILEVEL_0, usb_process },
77 	{ "usb", DDI_NT_UGEN, "ugen",	DRV_EXACT|TYPE_EXACT,
78 						ILEVEL_0, usb_process },
79 	{ "usb", DDI_NT_NEXUS, "usb_mid", DRV_EXACT|TYPE_EXACT,
80 						ILEVEL_0, usb_process },
81 	{ "usb", DDI_NT_UGEN, "usb_mid", DRV_EXACT|TYPE_EXACT,
82 						ILEVEL_0, usb_process },
83 	{ "usb", DDI_NT_PRINTER, "usbprn", DRV_EXACT|TYPE_EXACT,
84 						ILEVEL_0, usb_process },
85 	{ "usb", DDI_NT_UGEN, "usbprn", DRV_EXACT|TYPE_EXACT,
86 						ILEVEL_0, usb_process },
87 	{ "usb", DDI_NT_NEXUS, "hwahc", DRV_EXACT|TYPE_EXACT,
88 						ILEVEL_0, usb_process },
89 	{ "usb", DDI_NT_CCID_ATTACHMENT_POINT, "ccid", DRV_EXACT|TYPE_EXACT,
90 						ILEVEL_0, usb_process },
91 };
92 
93 /* For debug printing (-V filter) */
94 static char *debug_mid = "usb_mid";
95 
96 DEVFSADM_CREATE_INIT_V0(usb_cbt);
97 
98 /* USB device links */
99 #define	USB_LINK_RE_AUDIO	"^usb/audio[0-9]+$"
100 #define	USB_LINK_RE_AUDIOMUX	"^usb/audio-mux[0-9]+$"
101 #define	USB_LINK_RE_AUDIOCTL	"^usb/audio-control[0-9]+$"
102 #define	USB_LINK_RE_AUDIOSTREAM	"^usb/audio-stream[0-9]+$"
103 #define	USB_LINK_RE_DDIVS_USBC	"^usb/ddivs_usbc[0-9]+$"
104 #define	USB_LINK_RE_VIDEO	"^usb/video[0-9]+$"
105 #define	USB_LINK_RE_VIDEO2	"^video[0-9]+$"
106 #define	USB_LINK_RE_DEVICE	"^usb/device[0-9]+$"
107 #define	USB_LINK_RE_HID		"^usb/hid[0-9]+$"
108 #define	USB_LINK_RE_HUB		"^usb/hub[0-9]+$"
109 #define	USB_LINK_RE_MASS_STORE	"^usb/mass-storage[0-9]+$"
110 #define	USB_LINK_RE_UGEN	"^usb/[0-9,a-f]+\\.[0-9,a-f]+/[0-9]+/.+$"
111 #define	USB_LINK_RE_USBPRN	"^usb/printer[0-9]+$"
112 #define	USB_LINK_RE_WHOST	"^usb/whost[0-9]+$"
113 #define	USB_LINK_RE_HWARC	"^usb/hwarc[0-9]+$"
114 #define	USB_LINK_RE_WUSB_CA	"^usb/wusb_ca[0-9]+$"
115 #define	USB_LINK_RE_CCID	"^ccid/ccid[0-9]+/slot[0-9]+$"
116 
117 /* Rules for removing links */
118 static devfsadm_remove_t usb_remove_cbt[] = {
119 	{ "usb", USB_LINK_RE_AUDIO, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
120 			devfsadm_rm_all },
121 	{ "usb", USB_LINK_RE_AUDIOMUX, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
122 			devfsadm_rm_all },
123 	{ "usb", USB_LINK_RE_AUDIOCTL, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
124 			devfsadm_rm_all },
125 	{ "usb", USB_LINK_RE_AUDIOSTREAM, RM_POST | RM_HOT | RM_ALWAYS,
126 			ILEVEL_0, devfsadm_rm_all },
127 	{ "usb", USB_LINK_RE_DDIVS_USBC, RM_POST | RM_HOT | RM_ALWAYS,
128 			ILEVEL_0, devfsadm_rm_all },
129 	{ "usb", USB_LINK_RE_VIDEO2, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
130 			devfsadm_rm_all },
131 	{ "usb", USB_LINK_RE_VIDEO, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
132 			devfsadm_rm_all },
133 	{ "usb", USB_LINK_RE_DEVICE, RM_POST | RM_HOT, ILEVEL_0,
134 			devfsadm_rm_all },
135 	{ "usb", USB_LINK_RE_HID, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
136 			devfsadm_rm_all },
137 	{ "usb", USB_LINK_RE_HUB, RM_POST | RM_HOT, ILEVEL_0, devfsadm_rm_all },
138 	{ "usb", USB_LINK_RE_MASS_STORE, RM_POST | RM_HOT | RM_ALWAYS,
139 			ILEVEL_0, devfsadm_rm_all },
140 	{ "usb", USB_LINK_RE_UGEN, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
141 			devfsadm_rm_all },
142 	{ "usb", USB_LINK_RE_USBPRN, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
143 			devfsadm_rm_link },
144 	{ "usb", USB_LINK_RE_WHOST, RM_POST | RM_HOT, ILEVEL_0,
145 			devfsadm_rm_all },
146 	{ "usb", USB_LINK_RE_HWARC, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
147 			devfsadm_rm_all },
148 	{ "usb", USB_LINK_RE_WUSB_CA, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
149 			devfsadm_rm_all },
150 	{ "usb", USB_LINK_RE_CCID, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
151 		devfsadm_rm_all }
152 };
153 
154 /*
155  * Rules for different USB devices except ugen which is dynamically
156  * created
157  */
158 static devfsadm_enumerate_t audio_rules[1] =
159 	{"^usb$/^audio([0-9]+)$", 1, MATCH_ALL};
160 static devfsadm_enumerate_t audio_mux_rules[1] =
161 	{"^usb$/^audio-mux([0-9]+)$", 1, MATCH_ALL};
162 static devfsadm_enumerate_t audio_control_rules[1] =
163 	{"^usb$/^audio-control([0-9]+)$", 1, MATCH_ALL};
164 static devfsadm_enumerate_t audio_stream_rules[1] =
165 	{"^usb$/^audio-stream([0-9]+)$", 1, MATCH_ALL};
166 static devfsadm_enumerate_t ddivs_usbc_rules[1] =
167 	{"^usb$/^ddivs_usbc([0-9]+)$", 1, MATCH_ALL};
168 static devfsadm_enumerate_t video_rules[1] =
169 	{"^usb$/^video([0-9]+)$", 1, MATCH_ALL};
170 static devfsadm_enumerate_t device_rules[1] =
171 	{"^usb$/^device([0-9]+)$", 1, MATCH_ALL};
172 static devfsadm_enumerate_t hid_rules[1] =
173 	{"^usb$/^hid([0-9]+)$", 1, MATCH_ALL};
174 static devfsadm_enumerate_t hub_rules[1] =
175 	{"^usb$/^hub([0-9]+)$", 1, MATCH_ALL};
176 static devfsadm_enumerate_t mass_storage_rules[1] =
177 	{"^usb$/^mass-storage([0-9]+)$", 1, MATCH_ALL};
178 static devfsadm_enumerate_t usbprn_rules[1] =
179 	{"^usb$/^printer([0-9]+)$", 1, MATCH_ALL};
180 static devfsadm_enumerate_t whost_rules[1] =
181 	{"^usb$/^whost([0-9]+)$", 1, MATCH_ALL};
182 static devfsadm_enumerate_t hwarc_rules[1] =
183 	{"^usb$/^hwarc([0-9]+)$", 1, MATCH_ALL};
184 static devfsadm_enumerate_t wusb_ca_rules[1] =
185 	{"^usb$/^wusb_ca([0-9]+)$", 1, MATCH_ALL};
186 
187 DEVFSADM_REMOVE_INIT_V0(usb_remove_cbt);
188 
189 int
190 minor_init(void)
191 {
192 	devfsadm_print(debug_mid, "usb_link: minor_init\n");
193 	return (DEVFSADM_SUCCESS);
194 }
195 
196 int
197 minor_fini(void)
198 {
199 	devfsadm_print(debug_mid, "usb_link: minor_fini\n");
200 	return (DEVFSADM_SUCCESS);
201 }
202 
203 typedef enum {
204 	DRIVER_HUBD,
205 	DRIVER_OHCI,
206 	DRIVER_EHCI,
207 	DRIVER_UHCI,
208 	DRIVER_XHCI,
209 	DRIVER_USB_AC,
210 	DRIVER_USB_AS,
211 	DRIVER_HID,
212 	DRIVER_USB_MID,
213 	DRIVER_DDIVS_USBC,
214 	DRIVER_SCSA2USB,
215 	DRIVER_USBPRN,
216 	DRIVER_UGEN,
217 	DRIVER_VIDEO,
218 	DRIVER_HWAHC,
219 	DRIVER_HWARC,
220 	DRIVER_WUSB_CA,
221 	DRIVER_UNKNOWN
222 } driver_defs_t;
223 
224 typedef struct {
225 	char	*driver_name;
226 	driver_defs_t	index;
227 } driver_name_table_entry_t;
228 
229 driver_name_table_entry_t driver_name_table[] = {
230 	{ "hubd",	DRIVER_HUBD },
231 	{ "ohci",	DRIVER_OHCI },
232 	{ "ehci",	DRIVER_EHCI },
233 	{ "uhci",	DRIVER_UHCI },
234 	{ "xhci",	DRIVER_XHCI },
235 	{ "usb_ac",	DRIVER_USB_AC },
236 	{ "usb_as",	DRIVER_USB_AS },
237 	{ "hid",	DRIVER_HID },
238 	{ "usb_mid",	DRIVER_USB_MID },
239 	{ "ddivs_usbc",	DRIVER_DDIVS_USBC },
240 	{ "scsa2usb",	DRIVER_SCSA2USB },
241 	{ "usbprn",	DRIVER_USBPRN },
242 	{ "ugen",	DRIVER_UGEN },
243 	{ "usbvc",	DRIVER_VIDEO },
244 	{ "hwahc",	DRIVER_HWAHC },
245 	{ "hwarc",	DRIVER_HWARC },
246 	{ "wusb_ca",	DRIVER_WUSB_CA },
247 	{ NULL,		DRIVER_UNKNOWN }
248 };
249 
250 /*
251  * This function is called for every usb minor node.
252  * Calls enumerate to assign a logical usb id, and then
253  * devfsadm_mklink to make the link.
254  */
255 static int
256 usb_process(di_minor_t minor, di_node_t node)
257 {
258 	devfsadm_enumerate_t rules[1];
259 	char *l_path, *p_path, *buf, *devfspath;
260 	char *minor_nm, *drvr_nm, *name = (char *)NULL;
261 	int i;
262 	driver_defs_t index;
263 	int flags = 0;
264 	int create_secondary_link = 0;
265 
266 	minor_nm = di_minor_name(minor);
267 	drvr_nm = di_driver_name(node);
268 	if ((minor_nm == NULL) || (drvr_nm == NULL)) {
269 		return (DEVFSADM_CONTINUE);
270 	}
271 
272 	devfsadm_print(debug_mid, "usb_process: minor=%s node=%s type=%s\n",
273 	    minor_nm, di_node_name(node), di_minor_nodetype(minor));
274 
275 	devfspath = di_devfs_path(node);
276 	if (devfspath == NULL) {
277 		devfsadm_print(debug_mid,
278 		    "USB_process: devfspath is	NULL\n");
279 		return (DEVFSADM_CONTINUE);
280 	}
281 
282 	l_path = (char *)malloc(PATH_MAX);
283 	if (l_path == NULL) {
284 		di_devfs_path_free(devfspath);
285 		devfsadm_print(debug_mid, "usb_process: malloc() failed\n");
286 		return (DEVFSADM_CONTINUE);
287 	}
288 
289 	p_path = (char *)malloc(PATH_MAX);
290 	if (p_path == NULL) {
291 		devfsadm_print(debug_mid, "usb_process: malloc() failed\n");
292 		di_devfs_path_free(devfspath);
293 		free(l_path);
294 		return (DEVFSADM_CONTINUE);
295 	}
296 
297 	(void) strcpy(p_path, devfspath);
298 	(void) strcat(p_path, ":");
299 	(void) strcat(p_path, minor_nm);
300 	di_devfs_path_free(devfspath);
301 
302 	devfsadm_print(debug_mid, "usb_process: path %s\n", p_path);
303 
304 	for (i = 0; ; i++) {
305 		if ((driver_name_table[i].driver_name == NULL) ||
306 		    (strcmp(drvr_nm, driver_name_table[i].driver_name) == 0)) {
307 			index = driver_name_table[i].index;
308 			break;
309 		}
310 	}
311 
312 	if (strcmp(di_minor_nodetype(minor), DDI_NT_UGEN) == 0) {
313 		ugen_create_link(p_path, minor_nm, node, minor);
314 		free(l_path);
315 		free(p_path);
316 		return (DEVFSADM_CONTINUE);
317 	}
318 
319 	if (strcmp(di_minor_nodetype(minor), DDI_NT_CCID_ATTACHMENT_POINT) ==
320 	    0) {
321 		ccid_create_link(p_path, minor_nm, node, minor);
322 		free(l_path);
323 		free(p_path);
324 		return (DEVFSADM_CONTINUE);
325 	}
326 
327 	/* Figure out which rules to apply */
328 	switch (index) {
329 	case DRIVER_HUBD:
330 	case DRIVER_OHCI:
331 	case DRIVER_EHCI:
332 	case DRIVER_UHCI:
333 	case DRIVER_XHCI:
334 		rules[0] = hub_rules[0];	/* For HUBs */
335 		name = "hub";
336 
337 		break;
338 	case DRIVER_USB_AC:
339 		if (strcmp(minor_nm, "sound,audio") == 0) {
340 			rules[0] = audio_rules[0];
341 			name = "audio";		/* For audio */
342 			create_secondary_link = 1;
343 		} else if (strcmp(minor_nm, "sound,audioctl") == 0) {
344 			rules[0] = audio_control_rules[0];
345 			name = "audio-control";		/* For audio */
346 			create_secondary_link = 1;
347 		} else if (strcmp(minor_nm, "mux") == 0) {
348 			rules[0] = audio_mux_rules[0];
349 			name = "audio-mux";		/* For audio */
350 		} else {
351 			free(l_path);
352 			free(p_path);
353 			return (DEVFSADM_CONTINUE);
354 		}
355 		break;
356 	case DRIVER_USB_AS:
357 		rules[0] = audio_stream_rules[0];
358 		name = "audio-stream";		/* For audio */
359 		break;
360 	case DRIVER_VIDEO:
361 		rules[0] = video_rules[0];
362 		name = "video";			/* For video */
363 		create_secondary_link = 1;
364 		break;
365 	case DRIVER_HID:
366 		rules[0] = hid_rules[0];
367 		name = "hid";			/* For HIDs */
368 		break;
369 	case DRIVER_USB_MID:
370 		rules[0] = device_rules[0];
371 		name = "device";		/* For other USB devices */
372 		break;
373 	case DRIVER_DDIVS_USBC:
374 		rules[0] = ddivs_usbc_rules[0];
375 		name = "device";		/* For other USB devices */
376 		break;
377 	case DRIVER_SCSA2USB:
378 		rules[0] = mass_storage_rules[0];
379 		name = "mass-storage";		/* For mass-storage devices */
380 		break;
381 	case DRIVER_USBPRN:
382 		rules[0] = usbprn_rules[0];
383 		name = "printer";
384 		break;
385 	case DRIVER_HWAHC:
386 		if (strcmp(minor_nm, "hwahc") == 0) {
387 			rules[0] = whost_rules[0];
388 			name = "whost";		/* For HWA HC */
389 		} else if (strcmp(minor_nm, "hubd") == 0) {
390 			rules[0] = hub_rules[0];
391 			name = "hub";		/* For HWA HC */
392 		} else {
393 			free(l_path);
394 			free(p_path);
395 			return (DEVFSADM_CONTINUE);
396 		}
397 		break;
398 	case DRIVER_HWARC:
399 		rules[0] = hwarc_rules[0];
400 		name = "hwarc";		/* For UWB HWA Radio Controllers */
401 		break;
402 	case DRIVER_WUSB_CA:
403 		rules[0] = wusb_ca_rules[0];
404 		name = "wusb_ca";	/* for wusb cable association */
405 		break;
406 	default:
407 		devfsadm_print(debug_mid, "usb_process: unknown driver=%s\n",
408 		    drvr_nm);
409 		free(l_path);
410 		free(p_path);
411 		return (DEVFSADM_CONTINUE);
412 	}
413 
414 	/*
415 	 *  build the physical path from the components.
416 	 *  find the logical usb id, and stuff it in buf
417 	 */
418 	if (devfsadm_enumerate_int(p_path, 0, &buf, rules, 1)) {
419 		devfsadm_print(debug_mid, "usb_process: exit/continue\n");
420 		free(l_path);
421 		free(p_path);
422 		return (DEVFSADM_CONTINUE);
423 	}
424 
425 	(void) snprintf(l_path, PATH_MAX, "usb/%s%s", name, buf);
426 
427 	devfsadm_print(debug_mid, "usb_process: p_path=%s buf=%s\n",
428 	    p_path, buf);
429 
430 	free(buf);
431 
432 	devfsadm_print(debug_mid, "mklink %s -> %s\n", l_path, p_path);
433 
434 	(void) devfsadm_mklink(l_path, node, minor, flags);
435 
436 	if (create_secondary_link) {
437 		/*
438 		 * Create secondary links to make newly hotplugged
439 		 * usb audio device the primary device.
440 		 */
441 		if (strcmp(name, "audio") == 0) {
442 			(void) devfsadm_secondary_link("audio", l_path, 0);
443 		} else if (strcmp(name, "audio-control") == 0) {
444 			(void) devfsadm_secondary_link("audioctl", l_path, 0);
445 		} else if (strcmp(name, "video") == 0) {
446 			(void) devfsadm_secondary_link(l_path + 4, l_path, 0);
447 		}
448 	}
449 
450 	free(p_path);
451 	free(l_path);
452 
453 	return (DEVFSADM_CONTINUE);
454 }
455 
456 static void
457 ugen_create_link(char *p_path, char *node_name,
458     di_node_t node, di_minor_t minor)
459 {
460 	char *buf, s[MAXPATHLEN];
461 	char *lasts = s;
462 	char *vid, *pid;
463 	char *minor_name;
464 	char ugen_RE[128];
465 	devfsadm_enumerate_t ugen_rules[1];
466 	char l_path[PATH_MAX];
467 	int flags = 0;
468 
469 	devfsadm_print(debug_mid, "ugen_create_link: p_path=%s name=%s\n",
470 	    p_path, node_name);
471 
472 	(void) strlcpy(s, node_name, sizeof (s));
473 
474 	/* get vid, pid and minor name strings */
475 	vid = strtok_r(lasts, ".", &lasts);
476 	pid = strtok_r(NULL, ".", &lasts);
477 	minor_name = lasts;
478 
479 	if ((vid == NULL) || (pid == NULL) || (minor_name == NULL)) {
480 		return;
481 	}
482 
483 	/* create regular expression contain vid and pid */
484 	(void) snprintf(ugen_RE, sizeof (ugen_RE),
485 	    "^usb$/^%s\\.%s$/^([0-9]+)$", vid, pid);
486 	devfsadm_print(debug_mid,
487 	    "ugen_create_link: ugen_RE=%s minor_name=%s\n",
488 	    ugen_RE, minor_name);
489 
490 	bzero(ugen_rules, sizeof (ugen_rules));
491 
492 	ugen_rules[0].re = ugen_RE;
493 	ugen_rules[0].subexp = 1;
494 	ugen_rules[0].flags = MATCH_ADDR;
495 
496 	/*
497 	 *  build the physical path from the components.
498 	 *  find the logical usb id, and stuff it in buf
499 	 */
500 	if (devfsadm_enumerate_int(p_path, 0, &buf, ugen_rules, 1)) {
501 		devfsadm_print(debug_mid, "ugen_create_link: exit/continue\n");
502 		return;
503 	}
504 
505 	(void) snprintf(l_path, sizeof (l_path), "usb/%s.%s/%s/%s",
506 	    vid, pid, buf, minor_name);
507 
508 	devfsadm_print(debug_mid, "mklink %s -> %s\n", l_path, p_path);
509 
510 	(void) devfsadm_mklink(l_path, node, minor, flags);
511 
512 	free(buf);
513 }
514 
515 /*
516  * Create a CCID related link.
517  */
518 static void
519 ccid_create_link(char *p_path, char *minor_nm, di_node_t node, di_minor_t minor)
520 {
521 	char l_path[MAXPATHLEN];
522 
523 	(void) snprintf(l_path, sizeof (l_path), "ccid/ccid%d/%s",
524 	    di_instance(node), minor_nm);
525 
526 	devfsadm_print(debug_mid, "mklink %s -> %s\n", l_path, p_path);
527 
528 	(void) devfsadm_mklink(l_path, node, minor, 0);
529 }
530