Robert Mustacchi | 047043c | 2020-04-08 21:35:09 -0700 | [diff] [blame] | 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 | /* |
Keith M Wesolowski | ba215ef | 2022-07-21 06:57:54 -0700 | [diff] [blame] | 13 | * Copyright 2022 Oxide Computer Company |
Robert Mustacchi | 047043c | 2020-04-08 21:35:09 -0700 | [diff] [blame] | 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 | |
Keith M Wesolowski | 4adf43b | 2022-11-09 07:00:30 +0000 | [diff] [blame^] | 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 | |
Robert Mustacchi | 047043c | 2020-04-08 21:35:09 -0700 | [diff] [blame] | 104 | if (cmd == USMN_READ) { |
| 105 | int ret; |
| 106 | |
Robert Mustacchi | f198607 | 2021-07-29 22:27:35 -0700 | [diff] [blame] | 107 | if ((mode & FREAD) == 0) { |
| 108 | return (EINVAL); |
| 109 | } |
| 110 | |
Keith M Wesolowski | 4adf43b | 2022-11-09 07:00:30 +0000 | [diff] [blame^] | 111 | ret = amdzen_c_smn_read(dfno, reg, &usr.usr_data); |
Robert Mustacchi | 047043c | 2020-04-08 21:35:09 -0700 | [diff] [blame] | 112 | if (ret != 0) { |
| 113 | return (ret); |
| 114 | } |
Robert Mustacchi | f198607 | 2021-07-29 22:27:35 -0700 | [diff] [blame] | 115 | } else if (cmd == USMN_WRITE) { |
| 116 | int ret; |
| 117 | |
| 118 | if ((mode & FWRITE) == 0) { |
| 119 | return (EINVAL); |
| 120 | } |
| 121 | |
Keith M Wesolowski | 4adf43b | 2022-11-09 07:00:30 +0000 | [diff] [blame^] | 122 | ret = amdzen_c_smn_write(dfno, reg, usr.usr_data); |
Robert Mustacchi | f198607 | 2021-07-29 22:27:35 -0700 | [diff] [blame] | 123 | if (ret != 0) { |
| 124 | return (ret); |
| 125 | } |
Robert Mustacchi | 047043c | 2020-04-08 21:35:09 -0700 | [diff] [blame] | 126 | } else { |
| 127 | return (ENOTSUP); |
| 128 | } |
| 129 | |
Robert Mustacchi | f198607 | 2021-07-29 22:27:35 -0700 | [diff] [blame] | 130 | if (cmd == USMN_READ && |
| 131 | ddi_copyout(&usr, (void *)arg, sizeof (usr), mode & FKIOCTL) != 0) { |
Robert Mustacchi | 047043c | 2020-04-08 21:35:09 -0700 | [diff] [blame] | 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 | } |