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 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 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 145 zen_udf_close(dev_t dev, int flag, int otyp, cred_t *credp) 146 { 147 return (0); 148 } 149 150 static void 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 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 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 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 290 _init(void) 291 { 292 return (mod_install(&zen_udf_modlinkage)); 293 } 294 295 int 296 _info(struct modinfo *modinfop) 297 { 298 return (mod_info(&zen_udf_modlinkage, modinfop)); 299 } 300 301 int 302 _fini(void) 303 { 304 return (mod_remove(&zen_udf_modlinkage)); 305 } 306