1 /*-
2 * Copyright (c) 2026 Capabilities Limited
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 *
6 * This software was developed by Capabilities Limited with funding from
7 * Innovate UK and the Department for Science, Innovation and Technology
8 * for the adoption and diffusion of CHERI technology under project
9 * 10168042 (“CheriBSD feature extraction, maturity, and testing”).
10 *
11 */
12
13 #define EXTERR_CATEGORY_DYNAMIC "kern/subr_exterr.c"
14
15 #include <sys/param.h>
16 #include <sys/exterrvar.h>
17 #include <sys/exterr_cat.h>
18 #include <sys/kernel.h>
19 #include <sys/libkern.h>
20 #include <sys/linker_set.h>
21 #include <sys/malloc.h>
22 #include <sys/linker.h> /* Need MALLOC_DECLARE */
23 #include <sys/rwlock.h>
24 #include <sys/stddef.h>
25 #include <sys/sysctl.h>
26
27 struct exterr_cat_span {
28 unsigned int first;
29 unsigned int count;
30 struct exterr_cat **cat_sets;
31 TAILQ_ENTRY(exterr_cat_span) entries;
32 };
33
34 TAILQ_HEAD(exterr_cat_span_head, exterr_cat_span) cat_span_head;
35
36 SET_DECLARE(exterr_cats, struct exterr_cat);
37
38 static struct exterr_cat_span kern_cats;
39 unsigned int ncats;
40 struct rwlock cat_lock;
41
42 static bool
exterr_cat_register_set(struct exterr_cat_span * span,struct exterr_cat ** start,struct exterr_cat ** stop)43 exterr_cat_register_set(struct exterr_cat_span *span, struct exterr_cat **start,
44 struct exterr_cat **stop)
45 {
46 struct exterr_cat **catp;
47 ptrdiff_t count;
48
49 count = stop - start;
50 if (count < 1)
51 return (true);
52
53 rw_wlock(&cat_lock);
54
55 if (ncats + count < ncats) {
56 printf("too many exterror categories\n");
57 rw_wunlock(&cat_lock);
58 return (false);
59 }
60
61 span->first = ncats + 1;
62 for (catp = start; catp < stop; catp++)
63 (*catp)->cat = ++ncats;
64 span->count = count;
65 span->cat_sets = start;
66 TAILQ_INSERT_TAIL(&cat_span_head, span, entries);
67
68 rw_wunlock(&cat_lock);
69
70 return (true);
71 }
72
73 void
exterr_cat_register_module(struct exterr_cat ** start,struct exterr_cat ** stop)74 exterr_cat_register_module(struct exterr_cat **start, struct exterr_cat **stop)
75 {
76 struct exterr_cat_span *span;
77
78 span = malloc(sizeof(*span), M_LINKER, M_WAITOK | M_ZERO);
79 if (!exterr_cat_register_set(span, start, stop))
80 free(span, M_LINKER);
81 }
82
83 void
exterr_cat_unregister_module(struct exterr_cat ** start,struct exterr_cat ** stop)84 exterr_cat_unregister_module(struct exterr_cat **start,
85 struct exterr_cat **stop)
86 {
87 struct exterr_cat_span *span;
88
89 if (stop - start < 1)
90 return;
91
92 rw_wlock(&cat_lock);
93
94 TAILQ_FOREACH(span, &cat_span_head, entries) {
95 if (span->cat_sets == start) {
96 MPASS(span->first > 1);
97 MPASS(span->count == stop - start);
98 TAILQ_REMOVE(&cat_span_head, span, entries);
99 break;
100 }
101 }
102 KASSERT(span != NULL, ("start not found in spans"));
103
104 /*
105 * NB: we leak category numbers on module unload because we can't
106 * reasonably know which ones are in use in running software.
107 */
108
109 rw_wunlock(&cat_lock);
110
111 free(span, M_LINKER);
112 }
113
114 static void
exterr_cat_register_kern(void * arg)115 exterr_cat_register_kern(void *arg)
116 {
117 rw_init(&cat_lock, "exterr dynamic categories");
118
119 TAILQ_INIT(&cat_span_head);
120
121 if (SET_COUNT(exterr_cats) == 0)
122 return;
123
124 exterr_cat_register_set(&kern_cats, SET_BEGIN(exterr_cats),
125 SET_LIMIT(exterr_cats));
126 }
127 SYSINIT(exterr, SI_SUB_KMEM, SI_ORDER_FIRST, exterr_cat_register_kern, NULL);
128
129 static int
sysctl_exterr_categories(SYSCTL_HANDLER_ARGS)130 sysctl_exterr_categories(SYSCTL_HANDLER_ARGS)
131 {
132 struct exterr_cat_span *span;
133 const struct exterr_cat *cat = NULL;
134 int idx;
135
136 if (arg2 != 1)
137 return (EXTERROR(EINVAL,
138 "too many args to kern.exterr.categories %d", arg2));
139
140 idx = *(int *)arg1;
141
142 rw_rlock(&cat_lock);
143 if (idx < 1 || idx > ncats) {
144 rw_runlock(&cat_lock);
145 return (EXTERROR(EINVAL, "category %d out of range (1...%d)",
146 idx, ncats));
147 }
148
149 TAILQ_FOREACH(span, &cat_span_head, entries) {
150 if (idx < span->first)
151 break; /* Not here any more */
152
153 if (idx < span->first + span->count)
154 cat = span->cat_sets[idx - span->first];
155 }
156 rw_runlock(&cat_lock);
157
158 if (cat == NULL)
159 return (EXTERROR(ENOENT, "category not found %d", idx));
160 MPASS(cat->cat == idx);
161 return (SYSCTL_OUT(req, cat->file, strlen(cat->file) + 1));
162 }
163
164 SYSCTL_NODE(_kern, OID_AUTO, exterr, CTLFLAG_RD | CTLFLAG_MPSAFE, 0,
165 "Extended error information");
166 SYSCTL_UINT(_kern_exterr, OID_AUTO, ncategories, CTLFLAG_RD | CTLFLAG_MPSAFE,
167 &ncats, 0, "Number of dynamic categories");
168 SYSCTL_NODE(_kern_exterr, OID_AUTO, categories, CTLFLAG_RD | CTLFLAG_MPSAFE,
169 sysctl_exterr_categories, "Extended error categories");
170