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 2022 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 /* 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 104 if (cmd == USMN_READ) { 105 int ret; 106 107 if ((mode & FREAD) == 0) { 108 return (EINVAL); 109 } 110 111 ret = amdzen_c_smn_read(dfno, reg, &usr.usr_data); 112 if (ret != 0) { 113 return (ret); 114 } 115 } else if (cmd == USMN_WRITE) { 116 int ret; 117 118 if ((mode & FWRITE) == 0) { 119 return (EINVAL); 120 } 121 122 ret = amdzen_c_smn_write(dfno, reg, usr.usr_data); 123 if (ret != 0) { 124 return (ret); 125 } 126 } else { 127 return (ENOTSUP); 128 } 129 130 if (cmd == USMN_READ && 131 ddi_copyout(&usr, (void *)arg, sizeof (usr), mode & FKIOCTL) != 0) { 132 return (EFAULT); 133 } 134 135 return (0); 136 } 137 138 static int 139 usmn_close(dev_t dev, int flag, int otyp, cred_t *credp) 140 { 141 return (0); 142 } 143 144 static void 145 usmn_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 152 static int 153 usmn_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 185 err: 186 usmn_cleanup(usmn); 187 return (DDI_FAILURE); 188 } 189 190 static int 191 usmn_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 217 static int 218 usmn_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 238 static 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 258 static 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 271 static 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 277 static struct modlinkage usmn_modlinkage = { 278 .ml_rev = MODREV_1, 279 .ml_linkage = { &usmn_modldrv, NULL } 280 }; 281 282 int 283 _init(void) 284 { 285 return (mod_install(&usmn_modlinkage)); 286 } 287 288 int 289 _info(struct modinfo *modinfop) 290 { 291 return (mod_info(&usmn_modlinkage, modinfop)); 292 } 293 294 int 295 _fini(void) 296 { 297 return (mod_remove(&usmn_modlinkage)); 298 } 299