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