1
2 /*
3 * This file and its contents are supplied under the terms of the
4 * Common Development and Distribution License ("CDDL"), version 1.0.
5 * You may only use this file in accordance with the terms of version
6 * 1.0 of the CDDL.
7 *
8 * A full copy of the text of the CDDL should have accompanied this
9 * source. A copy of the CDDL is also available via the Internet at
10 * http://www.illumos.org/license/CDDL.
11 */
12
13 /*
14 * Copyright 2025 Oxide Computer Company
15 */
16
17 #include <sys/ktest.h>
18 #include <sys/modctl.h>
19 #include <sys/kobj.h>
20 #include <sys/comm_page.h>
21
22 static void
comm_page_vars_test(ktest_ctx_hdl_t * ctx)23 comm_page_vars_test(ktest_ctx_hdl_t *ctx)
24 {
25 modctl_t *hdl = NULL;
26
27 if ((hdl = mod_hold_by_name("unix")) == NULL) {
28 KT_ERROR(ctx, "failed to hold 'unix' module");
29 return;
30 }
31
32 const uintptr_t base = kobj_lookup(hdl->mod_mp, "comm_page");
33 if (base == 0) {
34 KT_ERROR(ctx, "failed to locate 'comm_page' symbol");
35 goto cleanup;
36 }
37
38 /*
39 * Check field offsets in comm page, ensuring they match up with the
40 * offsets of the variables they represent.
41 */
42 typedef struct var_check {
43 const char *name;
44 uintptr_t offset;
45 } var_check_t;
46 const var_check_t var_checks[] = {
47 {
48 .name = "tsc_last",
49 .offset = offsetof(comm_page_t, cp_tsc_last),
50 },
51 {
52 .name = "tsc_hrtime_base",
53 .offset = offsetof(comm_page_t, cp_tsc_hrtime_base),
54 },
55 {
56 .name = "tsc_resume_cap",
57 .offset = offsetof(comm_page_t, cp_tsc_resume_cap),
58 },
59 {
60 .name = "tsc_type",
61 .offset = offsetof(comm_page_t, cp_tsc_type),
62 },
63 {
64 .name = "tsc_max_delta",
65 .offset = offsetof(comm_page_t, cp_tsc_max_delta),
66 },
67 {
68 .name = "hres_lock",
69 .offset = offsetof(comm_page_t, cp_hres_lock),
70 },
71 {
72 .name = "nsec_scale",
73 .offset = offsetof(comm_page_t, cp_nsec_scale),
74 },
75 {
76 .name = "hrestime_adj",
77 .offset = offsetof(comm_page_t, cp_hrestime_adj),
78 },
79 {
80 .name = "hres_last_tick",
81 .offset = offsetof(comm_page_t, cp_hres_last_tick),
82 },
83 {
84 .name = "tsc_ncpu",
85 .offset = offsetof(comm_page_t, cp_tsc_ncpu),
86 },
87 {
88 .name = "hrestime",
89 .offset = offsetof(comm_page_t, cp_hrestime),
90 },
91 {
92 .name = "tsc_sync_tick_delta",
93 .offset = offsetof(comm_page_t, cp_tsc_sync_tick_delta),
94 },
95 };
96 for (uint_t i = 0; i < ARRAY_SIZE(var_checks); i++) {
97 const var_check_t *var = &var_checks[i];
98
99 const uintptr_t addr = kobj_lookup(hdl->mod_mp, var->name);
100 if (addr == 0) {
101 KT_ERROR(ctx, "failed to locate '%s' symbol",
102 var->name);
103 goto cleanup;
104 }
105 const uintptr_t var_off = (addr - base);
106 if (var_off != var->offset) {
107 KT_FAIL(ctx,
108 "unexpected offset for symbol '%s': %lu != %lu",
109 var->name, var_off, var->offset);
110 goto cleanup;
111 }
112 }
113
114 /*
115 * Check that if cp_tsc_ncpu is non-zero, that a tsc_tick_delta-aware
116 * gethrtime has been selected.
117 */
118 const comm_page_t *cp = (const comm_page_t *)base;
119 if (cp->cp_tsc_ncpu != 0) {
120 const uintptr_t *ghrt_func =
121 (const uintptr_t *)kobj_lookup(hdl->mod_mp, "gethrtimef");
122 if (ghrt_func == NULL) {
123 KT_ERROR(ctx, "failed to locate 'gethrtimef' symbol");
124 goto cleanup;
125 }
126 const uintptr_t ghrt_delta =
127 kobj_lookup(hdl->mod_mp, "tsc_gethrtime_delta");
128 if (*ghrt_func != ghrt_delta) {
129 KT_FAIL(ctx,
130 "tsc_gethrtime_delta not used for gethrtimef: "
131 "%x != %x\n",
132 ghrt_delta, *ghrt_func);
133 goto cleanup;
134 }
135 }
136
137 KT_PASS(ctx);
138
139 cleanup:
140 mod_release_mod(hdl);
141 }
142
143
144 static struct modlmisc i86pc_ktest_modlmisc = {
145 .misc_modops = &mod_miscops,
146 .misc_linkinfo = "i86pc ktest module"
147 };
148
149 static struct modlinkage i86pc_ktest_modlinkage = {
150 .ml_rev = MODREV_1,
151 .ml_linkage = { &i86pc_ktest_modlmisc, NULL }
152 };
153
154 int
_init()155 _init()
156 {
157 int ret;
158 ktest_module_hdl_t *km = NULL;
159 ktest_suite_hdl_t *ks = NULL;
160
161 VERIFY0(ktest_create_module("i86pc", &km));
162 VERIFY0(ktest_add_suite(km, "comm_page", &ks));
163 VERIFY0(ktest_add_test(ks, "comm_page_vars_test",
164 comm_page_vars_test, 0));
165
166 if ((ret = ktest_register_module(km)) != 0) {
167 ktest_free_module(km);
168 return (ret);
169 }
170
171 if ((ret = mod_install(&i86pc_ktest_modlinkage)) != 0) {
172 ktest_unregister_module("i86pc");
173 return (ret);
174 }
175
176 return (0);
177 }
178
179 int
_fini(void)180 _fini(void)
181 {
182 ktest_unregister_module("i86pc");
183 return (mod_remove(&i86pc_ktest_modlinkage));
184 }
185
186 int
_info(struct modinfo * modinfop)187 _info(struct modinfo *modinfop)
188 {
189 return (mod_info(&i86pc_ktest_modlinkage, modinfop));
190 }
191