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 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, SMN_MAKE_REG(usr.usr_addr), 105 &usr.usr_data); 106 if (ret != 0) { 107 return (ret); 108 } 109 } else if (cmd == USMN_WRITE) { 110 int ret; 111 112 if ((mode & FWRITE) == 0) { 113 return (EINVAL); 114 } 115 116 ret = amdzen_c_smn_write32(dfno, SMN_MAKE_REG(usr.usr_addr), 117 usr.usr_data); 118 if (ret != 0) { 119 return (ret); 120 } 121 } else { 122 return (ENOTSUP); 123 } 124 125 if (cmd == USMN_READ && 126 ddi_copyout(&usr, (void *)arg, sizeof (usr), mode & FKIOCTL) != 0) { 127 return (EFAULT); 128 } 129 130 return (0); 131 } 132 133 static int 134 usmn_close(dev_t dev, int flag, int otyp, cred_t *credp) 135 { 136 return (0); 137 } 138 139 static void 140 usmn_cleanup(usmn_t *usmn) 141 { 142 ddi_remove_minor_node(usmn->usmn_dip, NULL); 143 usmn->usmn_ndfs = 0; 144 usmn->usmn_dip = NULL; 145 } 146 147 static int 148 usmn_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) 149 { 150 usmn_t *usmn = &usmn_data; 151 152 if (cmd == DDI_RESUME) { 153 return (DDI_SUCCESS); 154 } else if (cmd != DDI_ATTACH) { 155 return (DDI_FAILURE); 156 } 157 158 if (usmn->usmn_dip != NULL) { 159 dev_err(dip, CE_WARN, "!usmn is already attached to a " 160 "dev_info_t: %p", usmn->usmn_dip); 161 return (DDI_FAILURE); 162 } 163 164 usmn->usmn_dip = dip; 165 usmn->usmn_ndfs = amdzen_c_df_count(); 166 for (uint_t i = 0; i < usmn->usmn_ndfs; i++) { 167 char buf[32]; 168 169 (void) snprintf(buf, sizeof (buf), "usmn.%u", i); 170 if (ddi_create_minor_node(dip, buf, S_IFCHR, i, DDI_PSEUDO, 171 0) != DDI_SUCCESS) { 172 dev_err(dip, CE_WARN, "!failed to create minor %s", 173 buf); 174 goto err; 175 } 176 } 177 178 return (DDI_SUCCESS); 179 180 err: 181 usmn_cleanup(usmn); 182 return (DDI_FAILURE); 183 } 184 185 static int 186 usmn_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp) 187 { 188 usmn_t *usmn = &usmn_data; 189 minor_t m; 190 191 switch (cmd) { 192 case DDI_INFO_DEVT2DEVINFO: 193 m = getminor((dev_t)arg); 194 if (m >= usmn->usmn_ndfs) { 195 return (DDI_FAILURE); 196 } 197 *resultp = (void *)usmn->usmn_dip; 198 break; 199 case DDI_INFO_DEVT2INSTANCE: 200 m = getminor((dev_t)arg); 201 if (m >= usmn->usmn_ndfs) { 202 return (DDI_FAILURE); 203 } 204 *resultp = (void *)(uintptr_t)ddi_get_instance(usmn->usmn_dip); 205 break; 206 default: 207 return (DDI_FAILURE); 208 } 209 return (DDI_SUCCESS); 210 } 211 212 static int 213 usmn_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) 214 { 215 usmn_t *usmn = &usmn_data; 216 217 if (cmd == DDI_SUSPEND) { 218 return (DDI_SUCCESS); 219 } else if (cmd != DDI_DETACH) { 220 return (DDI_FAILURE); 221 } 222 223 if (usmn->usmn_dip != dip) { 224 dev_err(dip, CE_WARN, "!asked to detach usmn, but dip doesn't " 225 "match"); 226 return (DDI_FAILURE); 227 } 228 229 usmn_cleanup(usmn); 230 return (DDI_SUCCESS); 231 } 232 233 static struct cb_ops usmn_cb_ops = { 234 .cb_open = usmn_open, 235 .cb_close = usmn_close, 236 .cb_strategy = nodev, 237 .cb_print = nodev, 238 .cb_dump = nodev, 239 .cb_read = nodev, 240 .cb_write = nodev, 241 .cb_ioctl = usmn_ioctl, 242 .cb_devmap = nodev, 243 .cb_mmap = nodev, 244 .cb_segmap = nodev, 245 .cb_chpoll = nochpoll, 246 .cb_prop_op = ddi_prop_op, 247 .cb_flag = D_MP, 248 .cb_rev = CB_REV, 249 .cb_aread = nodev, 250 .cb_awrite = nodev 251 }; 252 253 static struct dev_ops usmn_dev_ops = { 254 .devo_rev = DEVO_REV, 255 .devo_refcnt = 0, 256 .devo_getinfo = usmn_getinfo, 257 .devo_identify = nulldev, 258 .devo_probe = nulldev, 259 .devo_attach = usmn_attach, 260 .devo_detach = usmn_detach, 261 .devo_reset = nodev, 262 .devo_quiesce = ddi_quiesce_not_needed, 263 .devo_cb_ops = &usmn_cb_ops 264 }; 265 266 static struct modldrv usmn_modldrv = { 267 .drv_modops = &mod_driverops, 268 .drv_linkinfo = "AMD User SMN Access", 269 .drv_dev_ops = &usmn_dev_ops 270 }; 271 272 static struct modlinkage usmn_modlinkage = { 273 .ml_rev = MODREV_1, 274 .ml_linkage = { &usmn_modldrv, NULL } 275 }; 276 277 int 278 _init(void) 279 { 280 return (mod_install(&usmn_modlinkage)); 281 } 282 283 int 284 _info(struct modinfo *modinfop) 285 { 286 return (mod_info(&usmn_modlinkage, modinfop)); 287 } 288 289 int 290 _fini(void) 291 { 292 return (mod_remove(&usmn_modlinkage)); 293 } 294