16875 ndpd/ipmgmtd addrconf race
[illumos-gate.git] / usr / src / uts / common / io / devfm.c
blob889a4b8e41f967a867c89e102065730d7263f3ca
1 /*
2 * CDDL HEADER START
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
19 * CDDL HEADER END
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
27 * Copyright 2023 Oxide Computer Company
30 #include <sys/stat.h>
31 #include <sys/types.h>
32 #include <sys/param.h>
33 #include <sys/cred.h>
34 #include <sys/policy.h>
35 #include <sys/file.h>
36 #include <sys/errno.h>
37 #include <sys/modctl.h>
38 #include <sys/ddi.h>
39 #include <sys/sunddi.h>
40 #include <sys/conf.h>
41 #include <sys/debug.h>
42 #include <sys/systeminfo.h>
44 #include <sys/fm/protocol.h>
45 #include <sys/devfm.h>
47 extern int fm_get_paddr(nvlist_t *, uint64_t *);
48 #if defined(__x86)
49 extern int fm_ioctl_physcpu_info(int, nvlist_t *, nvlist_t **);
50 extern int fm_ioctl_cpu_retire(int, nvlist_t *, nvlist_t **);
51 extern int fm_ioctl_gentopo_legacy(int, nvlist_t *, nvlist_t **);
52 #endif /* __x86 */
53 extern int fm_ioctl_cache_info(int, nvlist_t *, nvlist_t **);
55 static int fm_ioctl_versions(int, nvlist_t *, nvlist_t **);
56 static int fm_ioctl_page_retire(int, nvlist_t *, nvlist_t **);
59 * The driver's capabilities are strictly versioned, allowing userland patching
60 * without a reboot. The userland should start with a FM_VERSIONS ioctl to
61 * query the versions of the kernel interfaces, then it's all userland's
62 * responsibility to prepare arguments etc to match the current kenrel.
63 * The version of FM_VERSIONS itself is FM_DRV_VERSION.
65 typedef struct fm_version {
66 char *interface; /* interface name */
67 uint32_t version; /* interface version */
68 } fm_vers_t;
70 typedef struct fm_subroutine {
71 int cmd; /* ioctl cmd */
72 boolean_t priv; /* require privilege */
73 char *version; /* version name */
74 int (*func)(int, nvlist_t *, nvlist_t **); /* handler */
75 } fm_subr_t;
77 static const fm_vers_t fm_versions[] = {
78 { FM_VERSIONS_VERSION, FM_DRV_VERSION },
79 { FM_PAGE_OP_VERSION, 1 },
80 { FM_CPU_OP_VERSION, 1 },
81 { FM_CPU_INFO_VERSION, 1 },
82 { FM_TOPO_LEGACY_VERSION, 1 },
83 { FM_CACHE_INFO_VERSION, 1 },
84 { NULL, 0 }
87 static const fm_subr_t fm_subrs[] = {
88 { FM_IOC_VERSIONS, B_FALSE, FM_VERSIONS_VERSION, fm_ioctl_versions },
89 { FM_IOC_PAGE_RETIRE, B_TRUE, FM_PAGE_OP_VERSION,
90 fm_ioctl_page_retire },
91 { FM_IOC_PAGE_STATUS, B_FALSE, FM_PAGE_OP_VERSION,
92 fm_ioctl_page_retire },
93 { FM_IOC_PAGE_UNRETIRE, B_TRUE, FM_PAGE_OP_VERSION,
94 fm_ioctl_page_retire },
95 #if defined(__x86)
96 { FM_IOC_PHYSCPU_INFO, B_FALSE, FM_CPU_INFO_VERSION,
97 fm_ioctl_physcpu_info },
98 { FM_IOC_CPU_RETIRE, B_TRUE, FM_CPU_OP_VERSION,
99 fm_ioctl_cpu_retire },
100 { FM_IOC_CPU_STATUS, B_FALSE, FM_CPU_OP_VERSION,
101 fm_ioctl_cpu_retire },
102 { FM_IOC_CPU_UNRETIRE, B_TRUE, FM_CPU_OP_VERSION,
103 fm_ioctl_cpu_retire },
104 { FM_IOC_GENTOPO_LEGACY, B_FALSE, FM_TOPO_LEGACY_VERSION,
105 fm_ioctl_gentopo_legacy },
106 #endif /* __x86 */
107 { FM_IOC_CACHE_INFO, B_FALSE, FM_CACHE_INFO_VERSION,
108 fm_ioctl_cache_info },
109 { -1, B_FALSE, NULL, NULL },
112 static dev_info_t *fm_dip;
113 static boolean_t is_i86xpv;
114 static nvlist_t *fm_vers_nvl;
116 static int
117 fm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
119 switch (cmd) {
120 case DDI_ATTACH:
121 if (ddi_create_minor_node(dip, ddi_get_name(dip), S_IFCHR,
122 ddi_get_instance(dip), DDI_PSEUDO, 0) != DDI_SUCCESS) {
123 ddi_remove_minor_node(dip, NULL);
124 return (DDI_FAILURE);
126 fm_dip = dip;
127 is_i86xpv = (strcmp(platform, "i86xpv") == 0);
128 break;
129 case DDI_RESUME:
130 break;
131 default:
132 return (DDI_FAILURE);
134 return (DDI_SUCCESS);
137 static int
138 fm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
140 int ret = DDI_SUCCESS;
142 switch (cmd) {
143 case DDI_DETACH:
144 ddi_remove_minor_node(dip, NULL);
145 fm_dip = NULL;
146 break;
147 default:
148 ret = DDI_FAILURE;
150 return (ret);
153 /*ARGSUSED*/
154 static int
155 fm_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
157 int error;
159 switch (infocmd) {
160 case DDI_INFO_DEVT2DEVINFO:
161 *result = fm_dip;
162 error = DDI_SUCCESS;
163 break;
164 case DDI_INFO_DEVT2INSTANCE:
165 *result = NULL;
166 error = DDI_SUCCESS;
167 break;
168 default:
169 error = DDI_FAILURE;
171 return (error);
174 /*ARGSUSED1*/
175 static int
176 fm_open(dev_t *devp, int flag, int typ, struct cred *cred)
178 if (typ != OTYP_CHR)
179 return (EINVAL);
180 if (getminor(*devp) != 0)
181 return (ENXIO);
183 return (0);
186 /*ARGSUSED*/
187 static int
188 fm_ioctl_versions(int cmd, nvlist_t *invl, nvlist_t **onvlp)
190 nvlist_t *nvl;
191 int err;
193 if ((err = nvlist_dup(fm_vers_nvl, &nvl, KM_SLEEP)) == 0)
194 *onvlp = nvl;
196 return (err);
200 * Given a mem-scheme FMRI for a page, execute the given page retire
201 * command on it.
203 /*ARGSUSED*/
204 static int
205 fm_ioctl_page_retire(int cmd, nvlist_t *invl, nvlist_t **onvlp)
207 uint64_t pa;
208 nvlist_t *fmri;
209 int err;
211 if (is_i86xpv)
212 return (ENOTSUP);
214 if ((err = nvlist_lookup_nvlist(invl, FM_PAGE_RETIRE_FMRI, &fmri))
215 != 0)
216 return (err);
218 if ((err = fm_get_paddr(fmri, &pa)) != 0)
219 return (err);
221 switch (cmd) {
222 case FM_IOC_PAGE_STATUS:
223 return (page_retire_check(pa, NULL));
225 case FM_IOC_PAGE_RETIRE:
226 return (page_retire(pa, PR_FMA));
228 case FM_IOC_PAGE_UNRETIRE:
229 return (page_unretire(pa));
232 return (ENOTTY);
235 /*ARGSUSED*/
236 static int
237 fm_ioctl(dev_t dev, int cmd, intptr_t data, int flag, cred_t *cred, int *rvalp)
239 char *buf;
240 int err;
241 uint_t model;
242 const fm_subr_t *subr;
243 uint32_t vers;
244 fm_ioc_data_t fid;
245 nvlist_t *invl = NULL, *onvl = NULL;
246 #ifdef _MULTI_DATAMODEL
247 fm_ioc_data32_t fid32;
248 #endif
250 if (getminor(dev) != 0)
251 return (ENXIO);
253 for (subr = fm_subrs; subr->cmd != cmd; subr++)
254 if (subr->cmd == -1)
255 return (ENOTTY);
257 if (subr->priv && (flag & FWRITE) == 0 &&
258 secpolicy_sys_config(CRED(), 0) != 0)
259 return (EPERM);
261 model = ddi_model_convert_from(flag & FMODELS);
263 switch (model) {
264 #ifdef _MULTI_DATAMODEL
265 case DDI_MODEL_ILP32:
266 if (ddi_copyin((void *)data, &fid32,
267 sizeof (fm_ioc_data32_t), flag) != 0)
268 return (EFAULT);
269 fid.fid_version = fid32.fid_version;
270 fid.fid_insz = fid32.fid_insz;
271 fid.fid_inbuf = (caddr_t)(uintptr_t)fid32.fid_inbuf;
272 fid.fid_outsz = fid32.fid_outsz;
273 fid.fid_outbuf = (caddr_t)(uintptr_t)fid32.fid_outbuf;
274 break;
275 #endif /* _MULTI_DATAMODEL */
276 case DDI_MODEL_NONE:
277 default:
278 if (ddi_copyin((void *)data, &fid, sizeof (fm_ioc_data_t),
279 flag) != 0)
280 return (EFAULT);
283 if (nvlist_lookup_uint32(fm_vers_nvl, subr->version, &vers) != 0 ||
284 fid.fid_version != vers)
285 return (ENOTSUP);
287 if (fid.fid_insz > FM_IOC_MAXBUFSZ)
288 return (ENAMETOOLONG);
289 if (fid.fid_outsz > FM_IOC_OUT_MAXBUFSZ)
290 return (EINVAL);
293 * Copy in and unpack the input nvlist.
295 if (fid.fid_insz != 0 && fid.fid_inbuf != (caddr_t)0) {
296 buf = kmem_alloc(fid.fid_insz, KM_SLEEP);
297 if (ddi_copyin(fid.fid_inbuf, buf, fid.fid_insz, flag) != 0) {
298 kmem_free(buf, fid.fid_insz);
299 return (EFAULT);
301 err = nvlist_unpack(buf, fid.fid_insz, &invl, KM_SLEEP);
302 kmem_free(buf, fid.fid_insz);
303 if (err != 0)
304 return (err);
307 err = subr->func(cmd, invl, &onvl);
309 nvlist_free(invl);
311 if (err != 0) {
312 nvlist_free(onvl);
313 return (err);
317 * If the output nvlist contains any data, pack it and copyout.
319 if (onvl != NULL) {
320 size_t sz;
322 if ((err = nvlist_size(onvl, &sz, NV_ENCODE_NATIVE)) != 0) {
323 nvlist_free(onvl);
324 return (err);
326 if (sz > fid.fid_outsz) {
327 nvlist_free(onvl);
328 return (ENAMETOOLONG);
331 buf = kmem_alloc(sz, KM_SLEEP);
332 if ((err = nvlist_pack(onvl, &buf, &sz, NV_ENCODE_NATIVE,
333 KM_SLEEP)) != 0) {
334 kmem_free(buf, sz);
335 nvlist_free(onvl);
336 return (err);
338 nvlist_free(onvl);
339 if (ddi_copyout(buf, fid.fid_outbuf, sz, flag) != 0) {
340 kmem_free(buf, sz);
341 return (EFAULT);
343 kmem_free(buf, sz);
344 fid.fid_outsz = sz;
346 switch (model) {
347 #ifdef _MULTI_DATAMODEL
348 case DDI_MODEL_ILP32:
349 fid32.fid_outsz = (size32_t)fid.fid_outsz;
350 if (ddi_copyout(&fid32, (void *)data,
351 sizeof (fm_ioc_data32_t), flag) != 0)
352 return (EFAULT);
353 break;
354 #endif /* _MULTI_DATAMODEL */
355 case DDI_MODEL_NONE:
356 default:
357 if (ddi_copyout(&fid, (void *)data,
358 sizeof (fm_ioc_data_t), flag) != 0)
359 return (EFAULT);
363 return (err);
366 static struct cb_ops fm_cb_ops = {
367 fm_open, /* open */
368 nulldev, /* close */
369 nodev, /* strategy */
370 nodev, /* print */
371 nodev, /* dump */
372 nodev, /* read */
373 nodev, /* write */
374 fm_ioctl, /* ioctl */
375 nodev, /* devmap */
376 nodev, /* mmap */
377 nodev, /* segmap */
378 nochpoll, /* poll */
379 ddi_prop_op, /* prop_op */
380 NULL, /* streamtab */
381 D_NEW | D_MP | D_64BIT | D_U64BIT
384 static struct dev_ops fm_ops = {
385 DEVO_REV, /* devo_rev, */
386 0, /* refcnt */
387 fm_info, /* get_dev_info */
388 nulldev, /* identify */
389 nulldev, /* probe */
390 fm_attach, /* attach */
391 fm_detach, /* detach */
392 nodev, /* reset */
393 &fm_cb_ops, /* driver operations */
394 (struct bus_ops *)0 /* bus operations */
397 static struct modldrv modldrv = {
398 &mod_driverops, "fault management driver", &fm_ops,
401 static struct modlinkage modlinkage = {
402 MODREV_1, &modldrv, NULL
406 _init(void)
408 const fm_vers_t *p;
409 int ret;
412 if ((ret = mod_install(&modlinkage)) == 0) {
413 (void) nvlist_alloc(&fm_vers_nvl, NV_UNIQUE_NAME, KM_SLEEP);
414 for (p = fm_versions; p->interface != NULL; p++)
415 (void) nvlist_add_uint32(fm_vers_nvl, p->interface,
416 p->version);
419 return (ret);
423 _info(struct modinfo *modinfop)
425 return (mod_info(&modlinkage, modinfop));
429 _fini(void)
431 int ret;
433 if ((ret = mod_remove(&modlinkage)) == 0) {
434 nvlist_free(fm_vers_nvl);
437 return (ret);