xref: /illumos-gate/usr/src/uts/i86pc/io/i86pc_ktest/i86pc_ktest.c (revision 4224453d626325281f3fcd656e646f681b9eca8b)
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
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
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
180 _fini(void)
181 {
182 	ktest_unregister_module("i86pc");
183 	return (mod_remove(&i86pc_ktest_modlinkage));
184 }
185 
186 int
187 _info(struct modinfo *modinfop)
188 {
189 	return (mod_info(&i86pc_ktest_modlinkage, modinfop));
190 }
191