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/*
22 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 * Copyright 2018 Joyent, Inc.  All rights reserved.
25 */
26
27#include <regex.h>
28#include <devfsadm.h>
29#include <stdio.h>
30#include <strings.h>
31#include <stdlib.h>
32#include <limits.h>
33#include <ctype.h>
34#include <sys/mc_amd.h>
35#include <bsm/devalloc.h>
36
37extern int system_labeled;
38
39static int ln_minor_name(di_minor_t minor, di_node_t node);
40static int lp(di_minor_t minor, di_node_t node);
41static int serial_dialout(di_minor_t minor, di_node_t node);
42static int serial(di_minor_t minor, di_node_t node);
43static int diskette(di_minor_t minor, di_node_t node);
44static int vt00(di_minor_t minor, di_node_t node);
45static int kdmouse(di_minor_t minor, di_node_t node);
46static int ipmi(di_minor_t minor, di_node_t node);
47static int mc_node(di_minor_t minor, di_node_t node);
48static int vmmctl(di_minor_t minor, di_node_t node);
49static int ppt(di_minor_t minor, di_node_t node);
50
51static devfsadm_create_t misc_cbt[] = {
52	{ "vt00", "ddi_display", NULL,
53	    TYPE_EXACT, ILEVEL_0,	vt00
54	},
55	{ "mouse", "ddi_mouse", "mouse8042",
56	    TYPE_EXACT | DRV_EXACT, ILEVEL_0, kdmouse
57	},
58	{ "pseudo", "ddi_pseudo", "ipmi",
59	    TYPE_EXACT | DRV_EXACT, ILEVEL_0, ipmi,
60	},
61	{ "pseudo", "ddi_pseudo", "smbios",
62	    TYPE_EXACT | DRV_EXACT, ILEVEL_1, ln_minor_name,
63	},
64	/* floppies share the same class, but not link regex, as hard disks */
65	{ "disk",  "ddi_block:diskette", NULL,
66	    TYPE_EXACT, ILEVEL_1, diskette
67	},
68	{ "parallel",  "ddi_printer", NULL,
69	    TYPE_EXACT, ILEVEL_1, lp
70	},
71	{ "serial", "ddi_serial:mb", NULL,
72	    TYPE_EXACT, ILEVEL_1, serial
73	},
74	{ "serial",  "ddi_serial:dialout,mb", NULL,
75	    TYPE_EXACT, ILEVEL_1, serial_dialout
76	},
77	{ "pseudo", "ddi_pseudo", "xsvc",
78	    TYPE_EXACT | DRV_EXACT, ILEVEL_0, ln_minor_name,
79	},
80	{ "pseudo", "ddi_pseudo", "srn",
81	    TYPE_EXACT | DRV_EXACT, ILEVEL_0, ln_minor_name,
82	},
83	{ "memory-controller", "ddi_mem_ctrl", NULL,
84	    TYPE_EXACT, ILEVEL_0, mc_node
85	},
86	{ "pseudo", "ddi_pseudo", "ucode",
87	    TYPE_EXACT | DRV_EXACT, ILEVEL_0, ln_minor_name,
88	},
89	{ "pseudo", "ddi_pseudo", "viona",
90	    TYPE_EXACT | DRV_EXACT, ILEVEL_0, ln_minor_name,
91	},
92	{ "pseudo", "ddi_pseudo", "vmm",
93	    TYPE_EXACT | DRV_EXACT, ILEVEL_0, vmmctl,
94	},
95	{ "pseudo", "ddi_pseudo", "ppt",
96	    TYPE_EXACT | DRV_EXACT, ILEVEL_0, ppt,
97	}
98};
99
100DEVFSADM_CREATE_INIT_V0(misc_cbt);
101
102static devfsadm_remove_t misc_remove_cbt[] = {
103	{ "vt", "vt[0-9][0-9]", RM_PRE|RM_ALWAYS,
104		ILEVEL_0, devfsadm_rm_all
105	},
106	{ "pseudo", "^ucode$", RM_ALWAYS | RM_PRE | RM_HOT,
107		ILEVEL_0, devfsadm_rm_all
108	},
109	{ "mouse", "^kdmouse$", RM_ALWAYS | RM_PRE,
110		ILEVEL_0, devfsadm_rm_all
111	},
112	{ "disk", "^(diskette|rdiskette)([0-9]*)$",
113		RM_ALWAYS | RM_PRE, ILEVEL_1, devfsadm_rm_all
114	},
115	{ "parallel", "^(lp|ecpp)([0-9]+)$", RM_ALWAYS | RM_PRE,
116		ILEVEL_1, devfsadm_rm_all
117	},
118	{ "serial", "^(tty|ttyd)([0-9]+)$", RM_ALWAYS | RM_PRE,
119		ILEVEL_1, devfsadm_rm_all
120	},
121	{ "serial", "^tty[a-z]$", RM_ALWAYS | RM_PRE,
122		ILEVEL_1, devfsadm_rm_all
123	},
124	{ "pseudo", "^viona$", RM_ALWAYS | RM_PRE | RM_HOT,
125		ILEVEL_0, devfsadm_rm_all
126	},
127	{ "pseudo", "^vmmctl$", RM_ALWAYS | RM_PRE | RM_HOT,
128		ILEVEL_0, devfsadm_rm_all
129	},
130	{ "pseudo", "^ppt$", RM_ALWAYS | RM_PRE | RM_HOT,
131		ILEVEL_0, devfsadm_rm_all
132	}
133};
134
135DEVFSADM_REMOVE_INIT_V0(misc_remove_cbt);
136
137/*
138 * Any /dev/foo entry named after the minor name such as
139 * /devices/.../driver@0:foo
140 */
141static int
142ln_minor_name(di_minor_t minor, di_node_t node)
143{
144	(void) devfsadm_mklink(di_minor_name(minor), node, minor, 0);
145	return (DEVFSADM_CONTINUE);
146}
147
148/*
149 * Handles minor node type "ddi_display", in addition to generic processing
150 * done by display().
151 *
152 * This creates a /dev/vt00 link to /dev/fb, for backwards compatibility.
153 */
154/* ARGSUSED */
155int
156vt00(di_minor_t minor, di_node_t node)
157{
158	(void) devfsadm_secondary_link("vt00", "fb", 0);
159	return (DEVFSADM_CONTINUE);
160}
161
162/*
163 * type=ddi_block:diskette;addr=0,0;minor=c        diskette
164 * type=ddi_block:diskette;addr=0,0;minor=c,raw    rdiskette
165 * type=ddi_block:diskette;addr1=0;minor=c diskette\A2
166 * type=ddi_block:diskette;addr1=0;minor=c,raw     rdiskette\A2
167 */
168static int
169diskette(di_minor_t minor, di_node_t node)
170{
171	int flags = 0;
172	char *a2;
173	char link[PATH_MAX];
174	char *addr = di_bus_addr(node);
175	char *mn = di_minor_name(minor);
176
177	if (system_labeled)
178		flags = DA_ADD|DA_FLOPPY;
179
180	if (strcmp(addr, "0,0") == 0) {
181		if (strcmp(mn, "c") == 0) {
182			(void) devfsadm_mklink("diskette", node, minor, flags);
183		} else if (strcmp(mn, "c,raw") == 0) {
184			(void) devfsadm_mklink("rdiskette", node, minor, flags);
185		}
186
187	}
188
189	if (addr[0] == '0') {
190		if ((a2 = strchr(addr, ',')) != NULL) {
191			a2++;
192			if (strcmp(mn, "c") == 0) {
193				(void) strcpy(link, "diskette");
194				(void) strcat(link, a2);
195				(void) devfsadm_mklink(link, node, minor,
196				    flags);
197			} else if (strcmp(mn, "c,raw") == 0) {
198				(void) strcpy(link, "rdiskette");
199				(void) strcat(link, a2);
200				(void) devfsadm_mklink(link, node, minor,
201				    flags);
202			}
203		}
204	}
205
206	return (DEVFSADM_CONTINUE);
207}
208
209/*
210 * type=ddi_printer;name=lp;addr=1,3bc      lp0
211 * type=ddi_printer;name=lp;addr=1,378      lp1
212 * type=ddi_printer;name=lp;addr=1,278      lp2
213 */
214static int
215lp(di_minor_t minor, di_node_t node)
216{
217	char *addr = di_bus_addr(node);
218	char *buf;
219	char path[PATH_MAX + 1];
220	devfsadm_enumerate_t rules[1] = {"^ecpp([0-9]+)$", 1, MATCH_ALL};
221
222	if (strcmp(addr, "1,3bc") == 0) {
223		(void) devfsadm_mklink("lp0", node, minor, 0);
224
225	} else if (strcmp(addr, "1,378") == 0) {
226		(void) devfsadm_mklink("lp1", node, minor, 0);
227
228	} else if (strcmp(addr, "1,278") == 0) {
229		(void) devfsadm_mklink("lp2", node, minor, 0);
230	}
231
232	if (strcmp(di_driver_name(node), "ecpp") != 0) {
233		return (DEVFSADM_CONTINUE);
234	}
235
236	if ((buf = di_devfs_path(node)) == NULL) {
237		return (DEVFSADM_CONTINUE);
238	}
239
240	(void) snprintf(path, sizeof (path), "%s:%s",
241	    buf, di_minor_name(minor));
242
243	di_devfs_path_free(buf);
244
245	if (devfsadm_enumerate_int(path, 0, &buf, rules, 1)) {
246		return (DEVFSADM_CONTINUE);
247	}
248
249	(void) snprintf(path, sizeof (path), "ecpp%s", buf);
250	free(buf);
251	(void) devfsadm_mklink(path, node, minor, 0);
252	return (DEVFSADM_CONTINUE);
253}
254
255/*
256 * type=ddi_serial:mb;minor=a      tty00
257 * type=ddi_serial:mb;minor=b      tty01
258 * type=ddi_serial:mb;minor=c      tty02
259 * type=ddi_serial:mb;minor=d      tty03
260 */
261static int
262serial(di_minor_t minor, di_node_t node)
263{
264
265	char *mn = di_minor_name(minor);
266	char link[PATH_MAX];
267
268	(void) strcpy(link, "tty");
269	(void) strcat(link, mn);
270	(void) devfsadm_mklink(link, node, minor, 0);
271
272	if (strcmp(mn, "a") == 0) {
273		(void) devfsadm_mklink("tty00", node, minor, 0);
274
275	} else if (strcmp(mn, "b") == 0) {
276		(void) devfsadm_mklink("tty01", node, minor, 0);
277
278	} else if (strcmp(mn, "c") == 0) {
279		(void) devfsadm_mklink("tty02", node, minor, 0);
280
281	} else if (strcmp(mn, "d") == 0) {
282		(void) devfsadm_mklink("tty03", node, minor, 0);
283	}
284	return (DEVFSADM_CONTINUE);
285}
286
287/*
288 * type=ddi_serial:dialout,mb;minor=a,cu   ttyd0
289 * type=ddi_serial:dialout,mb;minor=b,cu   ttyd1
290 * type=ddi_serial:dialout,mb;minor=c,cu   ttyd2
291 * type=ddi_serial:dialout,mb;minor=d,cu   ttyd3
292 */
293static int
294serial_dialout(di_minor_t minor, di_node_t node)
295{
296	char *mn = di_minor_name(minor);
297
298	if (strcmp(mn, "a,cu") == 0) {
299		(void) devfsadm_mklink("ttyd0", node, minor, 0);
300		(void) devfsadm_mklink("cua0", node, minor, 0);
301
302	} else if (strcmp(mn, "b,cu") == 0) {
303		(void) devfsadm_mklink("ttyd1", node, minor, 0);
304		(void) devfsadm_mklink("cua1", node, minor, 0);
305
306	} else if (strcmp(mn, "c,cu") == 0) {
307		(void) devfsadm_mklink("ttyd2", node, minor, 0);
308		(void) devfsadm_mklink("cua2", node, minor, 0);
309
310	} else if (strcmp(mn, "d,cu") == 0) {
311		(void) devfsadm_mklink("ttyd3", node, minor, 0);
312		(void) devfsadm_mklink("cua3", node, minor, 0);
313	}
314	return (DEVFSADM_CONTINUE);
315}
316
317static int
318kdmouse(di_minor_t minor, di_node_t node)
319{
320	(void) devfsadm_mklink("kdmouse", node, minor, 0);
321	return (DEVFSADM_CONTINUE);
322}
323
324static int
325ipmi(di_minor_t minor, di_node_t node)
326{
327	/*
328	 * Follow convention from other systems, and include an instance#,
329	 * even though there will only be one.
330	 */
331	(void) devfsadm_mklink("ipmi0", node, minor, 0);
332	return (DEVFSADM_CONTINUE);
333}
334
335/*
336 * /dev/mc/mc<chipid> -> /devices/.../pci1022,1102@<chipid+24>,2:mc-amd
337 */
338static int
339mc_node(di_minor_t minor, di_node_t node)
340{
341	const char *minorname = di_minor_name(minor);
342	const char *busaddr = di_bus_addr(node);
343	char linkpath[PATH_MAX];
344	int unitaddr;
345	char *c;
346
347	if (minorname == NULL || busaddr == NULL)
348		return (DEVFSADM_CONTINUE);
349
350	errno = 0;
351	unitaddr = strtol(busaddr, &c, 16);
352
353	if (errno != 0)
354		return (DEVFSADM_CONTINUE);
355
356	if (unitaddr == 0) {
357		(void) snprintf(linkpath, sizeof (linkpath), "mc/mc");
358	} else if (unitaddr >= MC_AMD_DEV_OFFSET) {
359		(void) snprintf(linkpath, sizeof (linkpath), "mc/mc%u",
360		    unitaddr - MC_AMD_DEV_OFFSET);
361	} else {
362		(void) snprintf(linkpath, sizeof (linkpath), "mc/mc%u",
363		    minor->dev_minor);
364	}
365	(void) devfsadm_mklink(linkpath, node, minor, 0);
366	return (DEVFSADM_CONTINUE);
367}
368
369/*
370 *	/dev/vmmctl	->	/devices/pseudo/vmm@0:ctl
371 */
372static int
373vmmctl(di_minor_t minor, di_node_t node)
374{
375	if (strcmp(di_minor_name(minor), "ctl") == 0)
376		(void) devfsadm_mklink("vmmctl", node, minor, 0);
377	return (DEVFSADM_CONTINUE);
378}
379
380static int
381ppt(di_minor_t minor, di_node_t node)
382{
383	char linkpath[PATH_MAX];
384
385	(void) snprintf(linkpath, sizeof (linkpath), "ppt%d",
386	    di_instance(node));
387
388	(void) devfsadm_mklink(linkpath, node, minor, 0);
389	return (DEVFSADM_CONTINUE);
390}
391