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 2025 Oxide Computer Company
14 */
15
16 /*
17 * A companion to zen_udf(4D) that allows user access to read the data fabric
18 * for development 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 <zen_udf.h>
36
37 typedef struct zen_udf {
38 dev_info_t *zudf_dip;
39 uint_t zudf_ndfs;
40 } zen_udf_t;
41
42 static zen_udf_t zen_udf_data;
43
44 static int
zen_udf_open(dev_t * devp,int flags,int otype,cred_t * credp)45 zen_udf_open(dev_t *devp, int flags, int otype, cred_t *credp)
46 {
47 minor_t m;
48 zen_udf_t *zen_udf = &zen_udf_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 >= zen_udf->zudf_ndfs) {
65 return (ENXIO);
66 }
67
68 return (0);
69 }
70
71 static int
zen_udf_ioctl(dev_t dev,int cmd,intptr_t arg,int mode,cred_t * credp,int * rvalp)72 zen_udf_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
73 int *rvalp)
74 {
75 uint_t dfno;
76 zen_udf_t *zen_udf = &zen_udf_data;
77 zen_udf_io_t zui;
78 df_reg_def_t def;
79 boolean_t bcast, do64;
80
81 if (cmd != ZEN_UDF_READ) {
82 return (ENOTTY);
83 }
84
85 dfno = getminor(dev);
86 if (dfno >= zen_udf->zudf_ndfs) {
87 return (ENXIO);
88 }
89
90 if (crgetzoneid(credp) != GLOBAL_ZONEID ||
91 secpolicy_hwmanip(credp) != 0) {
92 return (EPERM);
93 }
94
95 if (ddi_copyin((void *)arg, &zui, sizeof (zui), mode & FKIOCTL) != 0) {
96 return (EFAULT);
97 }
98
99 if ((zui.zui_flags & ~(ZEN_UDF_F_BCAST | ZEN_UDF_F_64)) != 0) {
100 return (EINVAL);
101 }
102
103 bcast = (zui.zui_flags & ZEN_UDF_F_BCAST) != 0;
104 do64 = (zui.zui_flags & ZEN_UDF_F_64) != 0;
105
106 /*
107 * Cons up a register definition based on the user request. We set the
108 * gen to our current one.
109 */
110 def.drd_gens = amdzen_c_df_rev();
111 def.drd_func = zui.zui_func;
112 def.drd_reg = zui.zui_reg;
113
114 if (!do64) {
115 int ret;
116 uint32_t data;
117
118 ret = bcast ?
119 amdzen_c_df_read32_bcast(dfno, def, &data) :
120 amdzen_c_df_read32(dfno, zui.zui_inst, def, &data);
121 if (ret != 0) {
122 return (ret);
123 }
124
125 zui.zui_data = data;
126 } else {
127 int ret;
128
129 ret = bcast ?
130 amdzen_c_df_read64_bcast(dfno, def, &zui.zui_data) :
131 amdzen_c_df_read64(dfno, zui.zui_inst, def, &zui.zui_data);
132 if (ret != 0) {
133 return (ret);
134 }
135 }
136
137 if (ddi_copyout(&zui, (void *)arg, sizeof (zui), mode & FKIOCTL) != 0) {
138 return (EFAULT);
139 }
140
141 return (0);
142 }
143
144 static int
zen_udf_close(dev_t dev,int flag,int otyp,cred_t * credp)145 zen_udf_close(dev_t dev, int flag, int otyp, cred_t *credp)
146 {
147 return (0);
148 }
149
150 static void
zen_udf_cleanup(zen_udf_t * zen_udf)151 zen_udf_cleanup(zen_udf_t *zen_udf)
152 {
153 ddi_remove_minor_node(zen_udf->zudf_dip, NULL);
154 zen_udf->zudf_ndfs = 0;
155 zen_udf->zudf_dip = NULL;
156 }
157
158 static int
zen_udf_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)159 zen_udf_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
160 {
161 zen_udf_t *zen_udf = &zen_udf_data;
162
163 if (cmd == DDI_RESUME) {
164 return (DDI_SUCCESS);
165 } else if (cmd != DDI_ATTACH) {
166 return (DDI_FAILURE);
167 }
168
169 if (zen_udf->zudf_dip != NULL) {
170 dev_err(dip, CE_WARN, "!zen_udf is already attached to a "
171 "dev_info_t: %p", zen_udf->zudf_dip);
172 return (DDI_FAILURE);
173 }
174
175 zen_udf->zudf_dip = dip;
176 zen_udf->zudf_ndfs = amdzen_c_df_count();
177 for (uint_t i = 0; i < zen_udf->zudf_ndfs; i++) {
178 char buf[32];
179
180 (void) snprintf(buf, sizeof (buf), "zen_udf.%u", i);
181 if (ddi_create_minor_node(dip, buf, S_IFCHR, i, DDI_PSEUDO,
182 0) != DDI_SUCCESS) {
183 dev_err(dip, CE_WARN, "!failed to create minor %s",
184 buf);
185 goto err;
186 }
187 }
188
189 return (DDI_SUCCESS);
190
191 err:
192 zen_udf_cleanup(zen_udf);
193 return (DDI_FAILURE);
194 }
195
196 static int
zen_udf_getinfo(dev_info_t * dip,ddi_info_cmd_t cmd,void * arg,void ** resultp)197 zen_udf_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp)
198 {
199 zen_udf_t *zen_udf = &zen_udf_data;
200 minor_t m;
201
202 switch (cmd) {
203 case DDI_INFO_DEVT2DEVINFO:
204 m = getminor((dev_t)arg);
205 if (m >= zen_udf->zudf_ndfs) {
206 return (DDI_FAILURE);
207 }
208 *resultp = (void *)zen_udf->zudf_dip;
209 break;
210 case DDI_INFO_DEVT2INSTANCE:
211 m = getminor((dev_t)arg);
212 if (m >= zen_udf->zudf_ndfs) {
213 return (DDI_FAILURE);
214 }
215 *resultp = (void *)(uintptr_t)ddi_get_instance(
216 zen_udf->zudf_dip);
217 break;
218 default:
219 return (DDI_FAILURE);
220 }
221 return (DDI_SUCCESS);
222 }
223
224 static int
zen_udf_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)225 zen_udf_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
226 {
227 zen_udf_t *zen_udf = &zen_udf_data;
228
229 if (cmd == DDI_SUSPEND) {
230 return (DDI_SUCCESS);
231 } else if (cmd != DDI_DETACH) {
232 return (DDI_FAILURE);
233 }
234
235 if (zen_udf->zudf_dip != dip) {
236 dev_err(dip, CE_WARN, "!asked to detach zen_udf, but dip "
237 "doesn't match");
238 return (DDI_FAILURE);
239 }
240
241 zen_udf_cleanup(zen_udf);
242 return (DDI_SUCCESS);
243 }
244
245 static struct cb_ops zen_udf_cb_ops = {
246 .cb_open = zen_udf_open,
247 .cb_close = zen_udf_close,
248 .cb_strategy = nodev,
249 .cb_print = nodev,
250 .cb_dump = nodev,
251 .cb_read = nodev,
252 .cb_write = nodev,
253 .cb_ioctl = zen_udf_ioctl,
254 .cb_devmap = nodev,
255 .cb_mmap = nodev,
256 .cb_segmap = nodev,
257 .cb_chpoll = nochpoll,
258 .cb_prop_op = ddi_prop_op,
259 .cb_flag = D_MP,
260 .cb_rev = CB_REV,
261 .cb_aread = nodev,
262 .cb_awrite = nodev
263 };
264
265 static struct dev_ops zen_udf_dev_ops = {
266 .devo_rev = DEVO_REV,
267 .devo_refcnt = 0,
268 .devo_getinfo = zen_udf_getinfo,
269 .devo_identify = nulldev,
270 .devo_probe = nulldev,
271 .devo_attach = zen_udf_attach,
272 .devo_detach = zen_udf_detach,
273 .devo_reset = nodev,
274 .devo_quiesce = ddi_quiesce_not_needed,
275 .devo_cb_ops = &zen_udf_cb_ops
276 };
277
278 static struct modldrv zen_udf_modldrv = {
279 .drv_modops = &mod_driverops,
280 .drv_linkinfo = "AMD User DF Access",
281 .drv_dev_ops = &zen_udf_dev_ops
282 };
283
284 static struct modlinkage zen_udf_modlinkage = {
285 .ml_rev = MODREV_1,
286 .ml_linkage = { &zen_udf_modldrv, NULL }
287 };
288
289 int
_init(void)290 _init(void)
291 {
292 return (mod_install(&zen_udf_modlinkage));
293 }
294
295 int
_info(struct modinfo * modinfop)296 _info(struct modinfo *modinfop)
297 {
298 return (mod_info(&zen_udf_modlinkage, modinfop));
299 }
300
301 int
_fini(void)302 _fini(void)
303 {
304 return (mod_remove(&zen_udf_modlinkage));
305 }
306