xref: /illumos-gate/usr/src/uts/intel/io/amdzen/usmn.c (revision f198607d)
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 2021 Oxide Computer Company
14  */
15 
16 /*
17  * A device driver that provides user access to the AMD System Management
18  * Network for debugging purposes.
19  */
20 
21 #include <sys/types.h>
22 #include <sys/file.h>
23 #include <sys/errno.h>
24 #include <sys/open.h>
25 #include <sys/cred.h>
26 #include <sys/ddi.h>
27 #include <sys/sunddi.h>
28 #include <sys/stat.h>
29 #include <sys/conf.h>
30 #include <sys/devops.h>
31 #include <sys/cmn_err.h>
32 #include <sys/policy.h>
33 #include <amdzen_client.h>
34 
35 #include "usmn.h"
36 
37 typedef struct usmn {
38 	dev_info_t *usmn_dip;
39 	uint_t usmn_ndfs;
40 } usmn_t;
41 
42 static usmn_t usmn_data;
43 
44 static int
45 usmn_open(dev_t *devp, int flags, int otype, cred_t *credp)
46 {
47 	minor_t m;
48 	usmn_t *usmn = &usmn_data;
49 
50 	if (crgetzoneid(credp) != GLOBAL_ZONEID ||
51 	    secpolicy_hwmanip(credp) != 0) {
52 		return (EPERM);
53 	}
54 
55 	if ((flags & (FEXCL | FNDELAY | FNONBLOCK)) != 0) {
56 		return (EINVAL);
57 	}
58 
59 	if (otype != OTYP_CHR) {
60 		return (EINVAL);
61 	}
62 
63 	m = getminor(*devp);
64 	if (m >= usmn->usmn_ndfs) {
65 		return (ENXIO);
66 	}
67 
68 	return (0);
69 }
70 
71 static int
72 usmn_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
73     int *rvalp)
74 {
75 	uint_t dfno;
76 	usmn_t *usmn = &usmn_data;
77 	usmn_reg_t usr;
78 
79 	if (cmd != USMN_READ && cmd != USMN_WRITE) {
80 		return (ENOTTY);
81 	}
82 
83 	dfno = getminor(dev);
84 	if (dfno >= usmn->usmn_ndfs) {
85 		return (ENXIO);
86 	}
87 
88 	if (crgetzoneid(credp) != GLOBAL_ZONEID ||
89 	    secpolicy_hwmanip(credp) != 0) {
90 		return (EPERM);
91 	}
92 
93 	if (ddi_copyin((void *)arg, &usr, sizeof (usr), mode & FKIOCTL) != 0) {
94 		return (EFAULT);
95 	}
96 
97 	if (cmd == USMN_READ) {
98 		int ret;
99 
100 		if ((mode & FREAD) == 0) {
101 			return (EINVAL);
102 		}
103 
104 		ret = amdzen_c_smn_read32(dfno, usr.usr_addr, &usr.usr_data);
105 		if (ret != 0) {
106 			return (ret);
107 		}
108 	} else if (cmd == USMN_WRITE) {
109 		int ret;
110 
111 		if ((mode & FWRITE) == 0) {
112 			return (EINVAL);
113 		}
114 
115 		ret = amdzen_c_smn_write32(dfno, usr.usr_addr, usr.usr_data);
116 		if (ret != 0) {
117 			return (ret);
118 		}
119 	} else {
120 		return (ENOTSUP);
121 	}
122 
123 	if (cmd == USMN_READ &&
124 	    ddi_copyout(&usr, (void *)arg, sizeof (usr), mode & FKIOCTL) != 0) {
125 		return (EFAULT);
126 	}
127 
128 	return (0);
129 }
130 
131 static int
132 usmn_close(dev_t dev, int flag, int otyp, cred_t *credp)
133 {
134 	return (0);
135 }
136 
137 static void
138 usmn_cleanup(usmn_t *usmn)
139 {
140 	ddi_remove_minor_node(usmn->usmn_dip, NULL);
141 	usmn->usmn_ndfs = 0;
142 	usmn->usmn_dip = NULL;
143 }
144 
145 static int
146 usmn_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
147 {
148 	usmn_t *usmn = &usmn_data;
149 
150 	if (cmd == DDI_RESUME) {
151 		return (DDI_SUCCESS);
152 	} else if (cmd != DDI_ATTACH) {
153 		return (DDI_FAILURE);
154 	}
155 
156 	if (usmn->usmn_dip != NULL) {
157 		dev_err(dip, CE_WARN, "!usmn is already attached to a "
158 		    "dev_info_t: %p", usmn->usmn_dip);
159 		return (DDI_FAILURE);
160 	}
161 
162 	usmn->usmn_dip = dip;
163 	usmn->usmn_ndfs = amdzen_c_df_count();
164 	for (uint_t i = 0; i < usmn->usmn_ndfs; i++) {
165 		char buf[32];
166 
167 		(void) snprintf(buf, sizeof (buf), "usmn.%u", i);
168 		if (ddi_create_minor_node(dip, buf, S_IFCHR, i, DDI_PSEUDO,
169 		    0) != DDI_SUCCESS) {
170 			dev_err(dip, CE_WARN, "!failed to create minor %s",
171 			    buf);
172 			goto err;
173 		}
174 	}
175 
176 	return (DDI_SUCCESS);
177 
178 err:
179 	usmn_cleanup(usmn);
180 	return (DDI_FAILURE);
181 }
182 
183 static int
184 usmn_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp)
185 {
186 	usmn_t *usmn = &usmn_data;
187 	minor_t m;
188 
189 	switch (cmd) {
190 	case DDI_INFO_DEVT2DEVINFO:
191 		m = getminor((dev_t)arg);
192 		if (m >= usmn->usmn_ndfs) {
193 			return (DDI_FAILURE);
194 		}
195 		*resultp = (void *)usmn->usmn_dip;
196 		break;
197 	case DDI_INFO_DEVT2INSTANCE:
198 		m = getminor((dev_t)arg);
199 		if (m >= usmn->usmn_ndfs) {
200 			return (DDI_FAILURE);
201 		}
202 		*resultp = (void *)(uintptr_t)ddi_get_instance(usmn->usmn_dip);
203 		break;
204 	default:
205 		return (DDI_FAILURE);
206 	}
207 	return (DDI_SUCCESS);
208 }
209 
210 static int
211 usmn_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
212 {
213 	usmn_t *usmn = &usmn_data;
214 
215 	if (cmd == DDI_SUSPEND) {
216 		return (DDI_SUCCESS);
217 	} else if (cmd != DDI_DETACH) {
218 		return (DDI_FAILURE);
219 	}
220 
221 	if (usmn->usmn_dip != dip) {
222 		dev_err(dip, CE_WARN, "!asked to detach usmn, but dip doesn't "
223 		    "match");
224 		return (DDI_FAILURE);
225 	}
226 
227 	usmn_cleanup(usmn);
228 	return (DDI_SUCCESS);
229 }
230 
231 static struct cb_ops usmn_cb_ops = {
232 	.cb_open = usmn_open,
233 	.cb_close = usmn_close,
234 	.cb_strategy = nodev,
235 	.cb_print = nodev,
236 	.cb_dump = nodev,
237 	.cb_read = nodev,
238 	.cb_write = nodev,
239 	.cb_ioctl = usmn_ioctl,
240 	.cb_devmap = nodev,
241 	.cb_mmap = nodev,
242 	.cb_segmap = nodev,
243 	.cb_chpoll = nochpoll,
244 	.cb_prop_op = ddi_prop_op,
245 	.cb_flag = D_MP,
246 	.cb_rev = CB_REV,
247 	.cb_aread = nodev,
248 	.cb_awrite = nodev
249 };
250 
251 static struct dev_ops usmn_dev_ops = {
252 	.devo_rev = DEVO_REV,
253 	.devo_refcnt = 0,
254 	.devo_getinfo = usmn_getinfo,
255 	.devo_identify = nulldev,
256 	.devo_probe = nulldev,
257 	.devo_attach = usmn_attach,
258 	.devo_detach = usmn_detach,
259 	.devo_reset = nodev,
260 	.devo_quiesce = ddi_quiesce_not_needed,
261 	.devo_cb_ops = &usmn_cb_ops
262 };
263 
264 static struct modldrv usmn_modldrv = {
265 	.drv_modops = &mod_driverops,
266 	.drv_linkinfo = "AMD User SMN Access",
267 	.drv_dev_ops = &usmn_dev_ops
268 };
269 
270 static struct modlinkage usmn_modlinkage = {
271 	.ml_rev = MODREV_1,
272 	.ml_linkage = { &usmn_modldrv, NULL }
273 };
274 
275 int
276 _init(void)
277 {
278 	return (mod_install(&usmn_modlinkage));
279 }
280 
281 int
282 _info(struct modinfo *modinfop)
283 {
284 	return (mod_info(&usmn_modlinkage, modinfop));
285 }
286 
287 int
288 _fini(void)
289 {
290 	return (mod_remove(&usmn_modlinkage));
291 }
292