1/* @title: Command Line */
2#pragma once
3#include <compiler/core.h>
4#include <compiler/diagnostic.h>
5#include <err.h>
6#include <linker/symbols.h>
7#include <math/bit.h>
8#include <math/fixed.h>
9#include <math/range.h>
10#include <stdbool.h>
11#include <stddef.h>
12#include <types/type_enum.h>
13
14struct cmdline_entry;
15struct cpu_mask;
16
17enum cmdline_entry_status {
18 CMDLINE_ENTRY_NOT_FOUND = 0,
19 CMDLINE_ENTRY_DEFAULTED,
20 CMDLINE_ENTRY_FOUND,
21};
22
23enum cmdline_entry_flags {
24 CMDLINE_ENTRY_FLAGS_NONE = 0,
25 CMDLINE_ENTRY_SYMBOLIC = 1, /* for namespace parenting purposes */
26 CMDLINE_ENTRY_REQUIRED = 1 << 1,
27 CMDLINE_ENTRY_HIDDEN = 1 << 2, /* exclude from help message */
28};
29
30enum cmdline_value_mode {
31 CMDLINE_MODE_POLYMORPHIC = 0,
32 CMDLINE_MODE_VAR,
33 CMDLINE_MODE_TYPED = CMDLINE_MODE_VAR,
34 CMDLINE_MODE_CUSTOM = CMDLINE_MODE_VAR,
35};
36
37#define CMDLINE_TYPE_OFFSET 6
38
39enum cmdline_type {
40 CMDLINE_TYPE_FIRST_ = CMDLINE_TYPE_OFFSET - 1,
41 CMDLINE_TYPE_BOOL = CMDLINE_TYPE_OFFSET,
42 CMDLINE_TYPE_INT,
43 CMDLINE_TYPE_UINT,
44 CMDLINE_TYPE_FX,
45 CMDLINE_TYPE_DURATION,
46 CMDLINE_TYPE_DATA_SIZE,
47 CMDLINE_TYPE_RANGE,
48 CMDLINE_TYPE_CPU_MASK,
49 CMDLINE_TYPE_MAC,
50 CMDLINE_TYPE_STRING,
51 CMDLINE_TYPE_LIST,
52 CMDLINE_TYPE_ERR,
53 CMDLINE_TYPE_NONE,
54};
55
56#define CMDLINE_TYPES(...) PP_CALL(CMDLINE_IMPL_TYPE_BIT, __VA_ARGS__)
57
58struct cmdline_range {
59 uint64_t start;
60 uint64_t end;
61};
62
63struct cmdline_list {
64 size_t count;
65 struct cmdline_value *items;
66};
67
68struct cmdline_value {
69 enum cmdline_value_mode mode;
70
71 union {
72 enum type_enum c_type;
73 enum cmdline_type type;
74 };
75
76 union {
77 void *write_to;
78 void *data;
79 uint64_t u64;
80 int64_t i64;
81 bool b;
82 fx32_32_t fx;
83 time_ns_t duration;
84 };
85
86 /* this is used when mode == CMDLINE_MODE_TYPED or CMDLINE_MODE_CUSTOM */
87 enum err (*parse)(void *write_to, const char *text);
88};
89
90/* The idea with parent-child relationships:
91 *
92 * The dot is used as a namespace marker. With a given cmdline_entry, we trace
93 * its lineage to build out what name it *really* has.
94 *
95 * For instance, if we have
96 *
97 * apple->parent = orange
98 * orange->parent = pineapple
99 * pineapple->parent = NULL
100 *
101 * the "functional name" of apple is
102 *
103 * pineapple.orange.apple */
104struct cmdline_entry {
105 const char *name;
106 const char *desc; /* human readable description */
107 const char *arg; /* value format hint e.g. "<hex bytes>", "<device>" */
108 const char *default_val;
109
110 enum cmdline_entry_flags flags;
111 enum cmdline_entry_status status;
112
113 struct cmdline_entry *parent;
114
115 uint64_t types;
116
117 struct range range;
118
119 /* Regarding the variable to write to */
120 struct cmdline_value value;
121
122 const char *const *choices;
123 const struct cmdline_map *mappings;
124 const struct cmdline_flag *flags_table;
125} cc_aligned(8);
126
127struct cmdline_map {
128 const char *name;
129 uint64_t value;
130};
131
132struct cmdline_flag {
133 const char *name;
134 uint64_t value;
135};
136
137#define CMDLINE_MAP(str, enum_val) \
138 {.name = (str), .value = (uint64_t) (enum_val)}
139#define CMDLINE_MAPPINGS(...) \
140 ((const struct cmdline_map[]) {__VA_ARGS__, {NULL, 0}})
141
142#define CMDLINE_FLAG(str, bit_val) \
143 {.name = (str), .value = (uint64_t) (bit_val)}
144#define CMDLINE_FLAGS(...) \
145 ((const struct cmdline_flag[]) {__VA_ARGS__, {NULL, 0}})
146
147#define CMDLINE_CHOICES(...) ((const char *const[]) {__VA_ARGS__, NULL})
148#define CMDLINE_PARSER(fn) .value.parse = (fn)
149
150#define _CMDLINE_DEFINE(n, storage, ...) \
151 storage LINKER_SECTION_OBJECT(struct cmdline_entry, cmdline_entries) \
152 __cmdline_##n = {.name = #n, \
153 .status = CMDLINE_ENTRY_NOT_FOUND, \
154 .types = 0, \
155 .range = RANGE(1, 0), \
156 .choices = NULL, \
157 .mappings = NULL, \
158 .flags_table = NULL, \
159 .value.mode = CMDLINE_MODE_POLYMORPHIC, \
160 .value.type = CMDLINE_TYPE_NONE, \
161 .flags = CMDLINE_ENTRY_FLAGS_NONE, \
162 __VA_ARGS__}
163
164#define _CMDLINE_DEFINE_VAR(n, storage, var, ...) \
165 storage LINKER_SECTION_OBJECT(struct cmdline_entry, cmdline_entries) \
166 __cmdline_##n = {.name = #n, \
167 .status = CMDLINE_ENTRY_NOT_FOUND, \
168 .types = 0, \
169 .range = RANGE(1, 0), \
170 .choices = NULL, \
171 .mappings = NULL, \
172 .flags_table = NULL, \
173 .value.mode = CMDLINE_MODE_VAR, \
174 .value.write_to = &(var), \
175 .value.c_type = TYPE_TO_ENUM((var)), \
176 .value.parse = NULL, \
177 .flags = CMDLINE_ENTRY_FLAGS_NONE, \
178 __VA_ARGS__}
179
180#define CMDLINE_DEFINE(n, ...) \
181 cc_wno_override_init_start _CMDLINE_DEFINE(n, , __VA_ARGS__); \
182 cc_wno_override_init_end
183
184#define CMDLINE_DEFINE_VAR(n, var, ...) \
185 cc_wno_override_init_start _CMDLINE_DEFINE_VAR(n, , var, __VA_ARGS__); \
186 cc_wno_override_init_end
187
188#define CMDLINE_DEFINE_STATIC(n, ...) \
189 cc_wno_override_init_start _CMDLINE_DEFINE(n, static, __VA_ARGS__); \
190 cc_wno_override_init_end
191
192#define CMDLINE_DEFINE_VAR_STATIC(n, var, ...) \
193 cc_wno_override_init_start _CMDLINE_DEFINE_VAR(n, static, var, \
194 __VA_ARGS__); \
195 cc_wno_override_init_end
196
197#define CMDLINE_CHILD_DEFINE(parent_n, n, ...) \
198 CMDLINE_DEFINE(parent_n##_##n, .name = #n, .parent = CMDLINE(parent_n), \
199 __VA_ARGS__)
200
201#define CMDLINE_CHILD_DEFINE_VAR(parent_n, n, var, ...) \
202 CMDLINE_DEFINE_VAR(parent_n##_##n, var, .name = #n, \
203 .parent = CMDLINE(parent_n), __VA_ARGS__)
204
205#define CMDLINE_INNER(n, ...) (_CMDLINE_CHILD_KIND_POLY, n, 0, ##__VA_ARGS__)
206
207#define CMDLINE_INNER_VAR(n, var, ...) \
208 (_CMDLINE_CHILD_KIND_VAR, n, var, ##__VA_ARGS__)
209
210#define CMDLINE_CHILDREN_DEFINE(parent_n, ...) \
211 PP_OVERLOAD(_CMDLINE_CHILDREN_MAP, __VA_ARGS__)(parent_n, __VA_ARGS__)
212
213#define CMDLINE_EXTRACT(val, var) \
214 cc_generic(&(var), bool * : cmdline_extract_bool((val), (bool *) &(var)), \
215 uint64_t * : cmdline_extract_u64((val), (uint64_t *) &(var)), \
216 int64_t * : cmdline_extract_i64((val), (int64_t *) &(var)), \
217 uint32_t * : cmdline_extract_u32((val), (uint32_t *) &(var)), \
218 int32_t * : cmdline_extract_i32((val), (int32_t *) &(var)), \
219 uint16_t * : cmdline_extract_u16((val), (uint16_t *) &(var)), \
220 int16_t * : cmdline_extract_i16((val), (int16_t *) &(var)), \
221 uint8_t * : cmdline_extract_u8((val), (uint8_t *) &(var)), \
222 int8_t * : cmdline_extract_i8((val), (int8_t *) &(var)), \
223 struct cmdline_range * : cmdline_extract_range( \
224 (val), (struct cmdline_range *) &(var)), \
225 char ** : cmdline_extract_string((val), (char **) &(var)), \
226 struct cpu_mask * : cmdline_extract_cpu_mask( \
227 (val), (struct cpu_mask *) &(var)), \
228 struct cmdline_list * : cmdline_extract_list( \
229 (val), (struct cmdline_list *) &(var)), \
230 const char ** : cmdline_extract_const_string( \
231 (val), (const char **) &(var)))
232
233#define CMDLINE_EXTRACT_LIST(list, type, out_buf, max_count, parse_fn) \
234 ({ \
235 size_t __cmdl_n = 0; \
236 const struct cmdline_list *__cmdl_l = (list); \
237 const size_t __cmdl_max = (max_count); \
238 for (size_t __cmdl_i = 0; \
239 __cmdl_i < __cmdl_l->count && __cmdl_n < __cmdl_max; \
240 __cmdl_i++) { \
241 type __cmdl_v = (type) {0}; \
242 if ((parse_fn) (&__cmdl_l->items[__cmdl_i], &__cmdl_v) == ERR_OK) \
243 (out_buf)[__cmdl_n++] = __cmdl_v; \
244 } \
245 __cmdl_n; \
246 })
247
248#define CMDLINE_NODE_1(a) a
249#define CMDLINE_NODE_2(a, b) a##_##b
250#define CMDLINE_NODE_3(a, b, c) a##_##b##_##c
251#define CMDLINE_NODE_4(a, b, c, d) a##_##b##_##c##_##d
252#define CMDLINE_NODE(...) PP_CALL(CMDLINE_NODE, __VA_ARGS__)
253
254#define CMDLINE_EXTERN(n) extern struct cmdline_entry PP_CONCAT(__cmdline_, n)
255#define CMDLINE(n) (&PP_CONCAT(__cmdline_, n))
256#define CMDLINE_VALUE(n) cmdline_entry_value_u64(CMDLINE(n))
257
258#define CMDLINE_CHILD_EXTERN(...) CMDLINE_EXTERN(CMDLINE_NODE(__VA_ARGS__))
259#define CMDLINE_CHILD(...) CMDLINE(CMDLINE_NODE(__VA_ARGS__))
260#define CMDLINE_CHILD_VALUE(...) \
261 cmdline_entry_value_u64(CMDLINE_CHILD(__VA_ARGS__))
262
263#define CMDLINE_ENTRY_NAME_LEN_MAX 256
264
265#define cmdline_list_for_each(val_, list_) \
266 for (size_t __cmdl_idx = 0, __cmdl_count = (list_)->count; \
267 __cmdl_idx < __cmdl_count && \
268 (((val_) = (list_)->items[__cmdl_idx]), true); \
269 __cmdl_idx++)
270
271/* Schema-Driven Subsystem Definitions */
272struct cmdline_schema_prop {
273 const char *name;
274 const char *desc;
275 size_t offset;
276 enum type_enum c_type;
277 uint64_t types;
278 enum err (*parse)(void *dst, const char *text);
279
280 struct range range;
281
282 const char *const *choices;
283 const struct cmdline_map *mappings;
284 const struct cmdline_flag *flags_table;
285};
286
287typedef void *(*cmdline_instance_resolver_t)(const char *path, size_t path_len);
288
289struct cmdline_schema {
290 const char *prefix;
291 const char *path_hint;
292 const char *desc;
293 cmdline_instance_resolver_t resolve;
294 const struct cmdline_schema_prop *props;
295 size_t prop_count;
296} cc_aligned(8);
297
298#define CMDLINE_SCHEMA_PROP_PARSER(fn) .parse = (fn)
299
300#define CMDLINE_SCHEMA_PROP(struct_type, member, ...) \
301 {.name = #member, \
302 .offset = offsetof(struct_type, member), \
303 .c_type = TYPE_TO_ENUM(((struct_type *) 0)->member), \
304 .types = 0, \
305 .parse = NULL, \
306 .range = RANGE(1, 0), \
307 .choices = NULL, \
308 .mappings = NULL, \
309 .flags_table = NULL, \
310 __VA_ARGS__}
311
312#define CMDLINE_SCHEMA_DEFINE(n, prefix_str, path_hint_str, desc_str, \
313 resolver_fn, ...) \
314 cc_wno_override_init_start static const struct cmdline_schema_prop \
315 __cmdline_schema_props_##n[] = {__VA_ARGS__}; \
316 LINKER_SECTION_OBJECT(struct cmdline_schema, cmdline_schemas) \
317 __cmdline_schema_##n = { \
318 .prefix = (prefix_str), \
319 .path_hint = (path_hint_str), \
320 .desc = (desc_str), \
321 .resolve = (resolver_fn), \
322 .props = __cmdline_schema_props_##n, \
323 .prop_count = ct_array_size(__cmdline_schema_props_##n), \
324 }; \
325 cc_wno_override_init_end
326
327#define CMDLINE_GET(key, type, fallback) \
328 ({ \
329 type __out = (fallback); \
330 struct cmdline_entry *__e = cmdline_lookup(key); \
331 if (__e && (__e->status == CMDLINE_ENTRY_FOUND || \
332 __e->status == CMDLINE_ENTRY_DEFAULTED)) { \
333 CMDLINE_EXTRACT(&__e->value, __out); \
334 } \
335 __out; \
336 })
337
338#define cmdline_read_or(key, target_var, fallback) \
339 ({ \
340 bool __cmdl_overridden = false; \
341 struct cmdline_entry *__cmdl_e = cmdline_lookup(key); \
342 if (__cmdl_e && __cmdl_e->status == CMDLINE_ENTRY_FOUND && \
343 CMDLINE_EXTRACT(&__cmdl_e->value, target_var) == ERR_OK) { \
344 __cmdl_overridden = true; \
345 } else { \
346 (target_var) = (fallback); \
347 } \
348 __cmdl_overridden; \
349 })
350
351void cmdline_parse(const char *input);
352bool cmdline_wants_help(const char *input);
353cc_noreturn void cmdline_dump_help(void);
354void cmdline_debug_hook(void);
355
356struct cmdline_entry *cmdline_lookup(const char *key);
357uint64_t cmdline_entry_value_u64(const struct cmdline_entry *e);
358
359#include "cmdline_api_internal.h"
360