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