blob: c9e1608b19ab5658a27c8e88396f29414e689f71 [file] [log] [blame]
Robert Mustacchi047043c2020-04-08 21:35:09 -07001/*
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/*
Keith M Wesolowskiba215ef2022-07-21 06:57:54 -070013 * Copyright 2022 Oxide Computer Company
Robert Mustacchi047043c2020-04-08 21:35:09 -070014 */
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
37typedef struct usmn {
38 dev_info_t *usmn_dip;
39 uint_t usmn_ndfs;
40} usmn_t;
41
42static usmn_t usmn_data;
43
44static int
45usmn_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
71static int
72usmn_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
Keith M Wesolowski4adf43b2022-11-09 07:00:30 +000097 /*
98 * We don't need to check size and alignment here; the client access
99 * routines do so for us and return EINVAL if violated. The same goes
100 * for the value to be written in the USMN_WRITE case below.
101 */
102 const smn_reg_t reg = SMN_MAKE_REG_SIZED(usr.usr_addr, usr.usr_size);
103
Robert Mustacchi047043c2020-04-08 21:35:09 -0700104 if (cmd == USMN_READ) {
105 int ret;
106
Robert Mustacchif1986072021-07-29 22:27:35 -0700107 if ((mode & FREAD) == 0) {
108 return (EINVAL);
109 }
110
Keith M Wesolowski4adf43b2022-11-09 07:00:30 +0000111 ret = amdzen_c_smn_read(dfno, reg, &usr.usr_data);
Robert Mustacchi047043c2020-04-08 21:35:09 -0700112 if (ret != 0) {
113 return (ret);
114 }
Robert Mustacchif1986072021-07-29 22:27:35 -0700115 } else if (cmd == USMN_WRITE) {
116 int ret;
117
118 if ((mode & FWRITE) == 0) {
119 return (EINVAL);
120 }
121
Keith M Wesolowski4adf43b2022-11-09 07:00:30 +0000122 ret = amdzen_c_smn_write(dfno, reg, usr.usr_data);
Robert Mustacchif1986072021-07-29 22:27:35 -0700123 if (ret != 0) {
124 return (ret);
125 }
Robert Mustacchi047043c2020-04-08 21:35:09 -0700126 } else {
127 return (ENOTSUP);
128 }
129
Robert Mustacchif1986072021-07-29 22:27:35 -0700130 if (cmd == USMN_READ &&
131 ddi_copyout(&usr, (void *)arg, sizeof (usr), mode & FKIOCTL) != 0) {
Robert Mustacchi047043c2020-04-08 21:35:09 -0700132 return (EFAULT);
133 }
134
135 return (0);
136}
137
138static int
139usmn_close(dev_t dev, int flag, int otyp, cred_t *credp)
140{
141 return (0);
142}
143
144static void
145usmn_cleanup(usmn_t *usmn)
146{
147 ddi_remove_minor_node(usmn->usmn_dip, NULL);
148 usmn->usmn_ndfs = 0;
149 usmn->usmn_dip = NULL;
150}
151
152static int
153usmn_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
154{
155 usmn_t *usmn = &usmn_data;
156
157 if (cmd == DDI_RESUME) {
158 return (DDI_SUCCESS);
159 } else if (cmd != DDI_ATTACH) {
160 return (DDI_FAILURE);
161 }
162
163 if (usmn->usmn_dip != NULL) {
164 dev_err(dip, CE_WARN, "!usmn is already attached to a "
165 "dev_info_t: %p", usmn->usmn_dip);
166 return (DDI_FAILURE);
167 }
168
169 usmn->usmn_dip = dip;
170 usmn->usmn_ndfs = amdzen_c_df_count();
171 for (uint_t i = 0; i < usmn->usmn_ndfs; i++) {
172 char buf[32];
173
174 (void) snprintf(buf, sizeof (buf), "usmn.%u", i);
175 if (ddi_create_minor_node(dip, buf, S_IFCHR, i, DDI_PSEUDO,
176 0) != DDI_SUCCESS) {
177 dev_err(dip, CE_WARN, "!failed to create minor %s",
178 buf);
179 goto err;
180 }
181 }
182
183 return (DDI_SUCCESS);
184
185err:
186 usmn_cleanup(usmn);
187 return (DDI_FAILURE);
188}
189
190static int
191usmn_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp)
192{
193 usmn_t *usmn = &usmn_data;
194 minor_t m;
195
196 switch (cmd) {
197 case DDI_INFO_DEVT2DEVINFO:
198 m = getminor((dev_t)arg);
199 if (m >= usmn->usmn_ndfs) {
200 return (DDI_FAILURE);
201 }
202 *resultp = (void *)usmn->usmn_dip;
203 break;
204 case DDI_INFO_DEVT2INSTANCE:
205 m = getminor((dev_t)arg);
206 if (m >= usmn->usmn_ndfs) {
207 return (DDI_FAILURE);
208 }
209 *resultp = (void *)(uintptr_t)ddi_get_instance(usmn->usmn_dip);
210 break;
211 default:
212 return (DDI_FAILURE);
213 }
214 return (DDI_SUCCESS);
215}
216
217static int
218usmn_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
219{
220 usmn_t *usmn = &usmn_data;
221
222 if (cmd == DDI_SUSPEND) {
223 return (DDI_SUCCESS);
224 } else if (cmd != DDI_DETACH) {
225 return (DDI_FAILURE);
226 }
227
228 if (usmn->usmn_dip != dip) {
229 dev_err(dip, CE_WARN, "!asked to detach usmn, but dip doesn't "
230 "match");
231 return (DDI_FAILURE);
232 }
233
234 usmn_cleanup(usmn);
235 return (DDI_SUCCESS);
236}
237
238static struct cb_ops usmn_cb_ops = {
239 .cb_open = usmn_open,
240 .cb_close = usmn_close,
241 .cb_strategy = nodev,
242 .cb_print = nodev,
243 .cb_dump = nodev,
244 .cb_read = nodev,
245 .cb_write = nodev,
246 .cb_ioctl = usmn_ioctl,
247 .cb_devmap = nodev,
248 .cb_mmap = nodev,
249 .cb_segmap = nodev,
250 .cb_chpoll = nochpoll,
251 .cb_prop_op = ddi_prop_op,
252 .cb_flag = D_MP,
253 .cb_rev = CB_REV,
254 .cb_aread = nodev,
255 .cb_awrite = nodev
256};
257
258static struct dev_ops usmn_dev_ops = {
259 .devo_rev = DEVO_REV,
260 .devo_refcnt = 0,
261 .devo_getinfo = usmn_getinfo,
262 .devo_identify = nulldev,
263 .devo_probe = nulldev,
264 .devo_attach = usmn_attach,
265 .devo_detach = usmn_detach,
266 .devo_reset = nodev,
267 .devo_quiesce = ddi_quiesce_not_needed,
268 .devo_cb_ops = &usmn_cb_ops
269};
270
271static struct modldrv usmn_modldrv = {
272 .drv_modops = &mod_driverops,
273 .drv_linkinfo = "AMD User SMN Access",
274 .drv_dev_ops = &usmn_dev_ops
275};
276
277static struct modlinkage usmn_modlinkage = {
278 .ml_rev = MODREV_1,
279 .ml_linkage = { &usmn_modldrv, NULL }
280};
281
282int
283_init(void)
284{
285 return (mod_install(&usmn_modlinkage));
286}
287
288int
289_info(struct modinfo *modinfop)
290{
291 return (mod_info(&usmn_modlinkage, modinfop));
292}
293
294int
295_fini(void)
296{
297 return (mod_remove(&usmn_modlinkage));
298}