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