1 /* 2 * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7 /* 8 * MMIO backend for Apple SMC (T2 and later Macs). 9 * 10 * T2 Macs expose the SMC via memory-mapped registers instead of I/O ports. 11 * Protocol: clear status, write key/cmd, poll for ready, read result. 12 */ 13 14 #include <sys/param.h> 15 #include <sys/bus.h> 16 #include <sys/endian.h> 17 #include <sys/kernel.h> 18 #include <sys/lock.h> 19 #include <sys/mutex.h> 20 #include <sys/module.h> 21 #include <sys/rman.h> 22 #include <sys/sysctl.h> 23 #include <sys/systm.h> 24 #include <sys/taskqueue.h> 25 26 #include <machine/bus.h> 27 28 #include <dev/asmc/asmcvar.h> 29 #include <dev/asmc/asmcmmio.h> 30 31 /* 32 * Wait for MMIO status register bit 5 (ready) with exponential backoff. 33 * Caller must hold sc_mtx. 34 */ 35 static int 36 asmc_mmio_wait(device_t dev) 37 { 38 struct asmc_softc *sc = device_get_softc(dev); 39 int i; 40 uint8_t status; 41 int delay_us = 10; 42 43 for (i = 0; i < ASMC_MMIO_MAX_WAIT; i++) { 44 status = bus_read_1(sc->sc_iomem, ASMC_MMIO_STATUS); 45 if (status & ASMC_MMIO_STATUS_READY) 46 return (0); 47 DELAY(delay_us); 48 if (delay_us < 3200) 49 delay_us *= 2; 50 } 51 52 return (ETIMEDOUT); 53 } 54 55 int 56 asmc_mmio_key_read(device_t dev, const char *key, uint8_t *buf, uint8_t len) 57 { 58 struct asmc_softc *sc = device_get_softc(dev); 59 uint32_t key_int; 60 int error, i; 61 uint8_t cmd_result, rlen; 62 63 if (len > ASMC_MAXVAL) 64 return (EINVAL); 65 66 mtx_lock_spin(&sc->sc_mtx); 67 68 /* Clear status if non-zero */ 69 if (bus_read_1(sc->sc_iomem, ASMC_MMIO_STATUS)) 70 bus_write_1(sc->sc_iomem, ASMC_MMIO_STATUS, 0); 71 72 /* Write key name as raw 4 bytes */ 73 memcpy(&key_int, key, 4); 74 bus_write_4(sc->sc_iomem, ASMC_MMIO_KEY_NAME, key_int); 75 76 /* Write SMC ID (always 0) and command */ 77 bus_write_1(sc->sc_iomem, ASMC_MMIO_SMC_ID, 0); 78 bus_write_1(sc->sc_iomem, ASMC_MMIO_CMD, ASMC_CMDREAD); 79 80 /* Wait for ready */ 81 error = asmc_mmio_wait(dev); 82 if (error != 0) { 83 uint8_t st = bus_read_1(sc->sc_iomem, ASMC_MMIO_STATUS); 84 uint8_t cm = bus_read_1(sc->sc_iomem, ASMC_MMIO_CMD); 85 mtx_unlock_spin(&sc->sc_mtx); 86 device_printf(dev, 87 "%s: timeout key %.4s status=0x%02x cmd=0x%02x\n", 88 __func__, key, st, cm); 89 return (error); 90 } 91 92 /* Check command result (0 = success, 0x84 = key not found) */ 93 cmd_result = bus_read_1(sc->sc_iomem, ASMC_MMIO_CMD); 94 if (cmd_result != 0) { 95 mtx_unlock_spin(&sc->sc_mtx); 96 device_printf(dev, 97 "%s: key %.4s cmd error 0x%02x\n", 98 __func__, key, cmd_result); 99 return (EIO); 100 } 101 102 /* Read data length and data bytes; zero-fill remainder */ 103 rlen = bus_read_1(sc->sc_iomem, ASMC_MMIO_DATA_LEN); 104 rlen = MIN(rlen, len); 105 for (i = rlen; i < len; i++) 106 buf[i] = 0; 107 for (i = 0; i < rlen; i++) 108 buf[i] = bus_read_1(sc->sc_iomem, ASMC_MMIO_DATA + i); 109 110 mtx_unlock_spin(&sc->sc_mtx); 111 return (0); 112 } 113 114 int 115 asmc_mmio_key_write(device_t dev, const char *key, uint8_t *buf, uint8_t len) 116 { 117 struct asmc_softc *sc = device_get_softc(dev); 118 uint32_t key_int; 119 int error, i; 120 uint8_t cmd_result; 121 122 if (len > ASMC_MAXVAL) 123 return (EINVAL); 124 125 mtx_lock_spin(&sc->sc_mtx); 126 127 /* Clear status */ 128 bus_write_1(sc->sc_iomem, ASMC_MMIO_STATUS, 0); 129 130 /* Write data bytes first */ 131 for (i = 0; i < len; i++) 132 bus_write_1(sc->sc_iomem, ASMC_MMIO_DATA + i, buf[i]); 133 134 /* Write key name as raw 4 bytes */ 135 memcpy(&key_int, key, 4); 136 bus_write_4(sc->sc_iomem, ASMC_MMIO_KEY_NAME, key_int); 137 138 /* Write length, SMC ID, command */ 139 bus_write_1(sc->sc_iomem, ASMC_MMIO_DATA_LEN, len); 140 bus_write_1(sc->sc_iomem, ASMC_MMIO_SMC_ID, 0); 141 bus_write_1(sc->sc_iomem, ASMC_MMIO_CMD, ASMC_CMDWRITE); 142 143 /* Wait for ready */ 144 error = asmc_mmio_wait(dev); 145 if (error != 0) { 146 mtx_unlock_spin(&sc->sc_mtx); 147 device_printf(dev, "%s: timeout writing key %.4s\n", 148 __func__, key); 149 return (error); 150 } 151 152 cmd_result = bus_read_1(sc->sc_iomem, ASMC_MMIO_CMD); 153 mtx_unlock_spin(&sc->sc_mtx); 154 155 return (cmd_result == 0 ? 0 : EIO); 156 } 157 158 int 159 asmc_mmio_key_getinfo(device_t dev, const char *key, uint8_t *len, char *type) 160 { 161 struct asmc_softc *sc = device_get_softc(dev); 162 uint32_t key_int; 163 int error, i; 164 uint8_t cmd_result; 165 166 mtx_lock_spin(&sc->sc_mtx); 167 168 /* Clear status */ 169 bus_write_1(sc->sc_iomem, ASMC_MMIO_STATUS, 0); 170 171 /* Write key name as raw 4 bytes */ 172 memcpy(&key_int, key, 4); 173 bus_write_4(sc->sc_iomem, ASMC_MMIO_KEY_NAME, key_int); 174 175 bus_write_1(sc->sc_iomem, ASMC_MMIO_SMC_ID, 0); 176 bus_write_1(sc->sc_iomem, ASMC_MMIO_CMD, ASMC_CMDGETINFO); 177 178 error = asmc_mmio_wait(dev); 179 if (error != 0) { 180 mtx_unlock_spin(&sc->sc_mtx); 181 return (error); 182 } 183 184 cmd_result = bus_read_1(sc->sc_iomem, ASMC_MMIO_CMD); 185 if (cmd_result != 0) { 186 mtx_unlock_spin(&sc->sc_mtx); 187 return (EIO); 188 } 189 190 /* 191 * GETINFO response layout (MMIO): 192 * data[0..3] = type code (4 chars) 193 * data[4] = reserved 194 * data[5] = data length 195 * data[6] = flags/attributes 196 */ 197 if (type != NULL) { 198 for (i = 0; i < ASMC_TYPELEN; i++) 199 type[i] = bus_read_1(sc->sc_iomem, 200 ASMC_MMIO_DATA + i); 201 type[ASMC_TYPELEN] = '\0'; 202 } 203 if (len != NULL) 204 *len = bus_read_1(sc->sc_iomem, ASMC_MMIO_DATA + 5); 205 206 mtx_unlock_spin(&sc->sc_mtx); 207 return (0); 208 } 209 210 int 211 asmc_mmio_key_getbyindex(device_t dev, int index, char *key) 212 { 213 struct asmc_softc *sc = device_get_softc(dev); 214 uint32_t idx_val; 215 int error, i; 216 uint8_t cmd_result; 217 218 mtx_lock_spin(&sc->sc_mtx); 219 220 bus_write_1(sc->sc_iomem, ASMC_MMIO_STATUS, 0); 221 222 /* Write index as big-endian 4 bytes to key name register */ 223 idx_val = htobe32(index); 224 bus_write_4(sc->sc_iomem, ASMC_MMIO_KEY_NAME, idx_val); 225 226 bus_write_1(sc->sc_iomem, ASMC_MMIO_SMC_ID, 0); 227 bus_write_1(sc->sc_iomem, ASMC_MMIO_CMD, ASMC_CMDGETBYINDEX); 228 229 error = asmc_mmio_wait(dev); 230 if (error != 0) { 231 mtx_unlock_spin(&sc->sc_mtx); 232 return (error); 233 } 234 235 cmd_result = bus_read_1(sc->sc_iomem, ASMC_MMIO_CMD); 236 if (cmd_result != 0) { 237 mtx_unlock_spin(&sc->sc_mtx); 238 return (EIO); 239 } 240 241 /* Result: 4-byte key name in DATA */ 242 for (i = 0; i < ASMC_KEYLEN; i++) 243 key[i] = bus_read_1(sc->sc_iomem, ASMC_MMIO_DATA + i); 244 key[ASMC_KEYLEN] = '\0'; 245 246 mtx_unlock_spin(&sc->sc_mtx); 247 return (0); 248 } 249 250 /* 251 * Validate MMIO and detect T2. 252 * Check that status register is accessible and LDKN firmware version >= 2. 253 */ 254 int 255 asmc_mmio_probe(device_t dev) 256 { 257 struct asmc_softc *sc = device_get_softc(dev); 258 rman_res_t size; 259 uint8_t status, ldkn; 260 int error; 261 262 size = rman_get_size(sc->sc_iomem); 263 if (size < ASMC_MMIO_MIN_SIZE) { 264 device_printf(dev, "MMIO region too small (%jd < %d)\n", 265 (intmax_t)size, ASMC_MMIO_MIN_SIZE); 266 return (ENXIO); 267 } 268 269 /* Check status register isn't stuck at 0xFF */ 270 status = bus_read_1(sc->sc_iomem, ASMC_MMIO_STATUS); 271 if (status == 0xFF) { 272 device_printf(dev, "MMIO status register reads 0xFF\n"); 273 return (ENXIO); 274 } 275 276 /* 277 * We need the mutex initialized before calling mmio_key_read, 278 * but attach hasn't done it yet. Initialize early. 279 */ 280 if (!mtx_initialized(&sc->sc_mtx)) 281 mtx_init(&sc->sc_mtx, "asmc", NULL, MTX_SPIN); 282 283 /* Read LDKN (firmware version) -- must be >= 2 for MMIO */ 284 error = asmc_mmio_key_read(dev, ASMC_KEY_LDKN, &ldkn, 1); 285 if (error != 0) { 286 device_printf(dev, "MMIO: failed to read LDKN key\n"); 287 return (ENXIO); 288 } 289 290 if (ldkn < 2) { 291 device_printf(dev, "MMIO: LDKN=%d (need >= 2)\n", ldkn); 292 return (ENXIO); 293 } 294 295 device_printf(dev, "MMIO: LDKN=%d, T2 SMC detected\n", ldkn); 296 sc->sc_is_t2 = 1; 297 298 return (0); 299 } 300 301 void 302 asmc_mmio_detach(device_t dev, struct asmc_softc *sc) 303 { 304 305 if (sc->sc_iomem != NULL) { 306 bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_rid_mem, 307 sc->sc_iomem); 308 sc->sc_iomem = NULL; 309 } 310 sc->sc_is_mmio = 0; 311 sc->sc_is_t2 = 0; 312 } 313 314 /* 315 * Convert IEEE 754 float (as u32) to unsigned integer. 316 * Kernel soft-float: extract integer part only. 317 * Used for T2 fan RPM values (always positive, reasonable range). 318 */ 319 uint32_t 320 asmc_float_to_u32(uint32_t d) 321 { 322 int32_t exp; 323 uint32_t fr; 324 325 /* Negative or zero */ 326 if (d == 0 || (d >> 31) != 0) 327 return (0); 328 329 exp = (int32_t)((d >> 23) & 0xff) - 0x7f; 330 fr = d & 0x7fffff; /* 23-bit mantissa */ 331 332 if (exp < 0) 333 return (0); 334 if (exp > 23) { 335 if (exp > 30) 336 return (0xffffffffu); 337 return ((1u << exp) | (fr << (exp - 23))); 338 } 339 /* Normal case: 0 <= exp <= 23 */ 340 return ((1u << exp) + (fr >> (23 - exp))); 341 } 342 343 /* 344 * Convert unsigned integer to IEEE 754 float (as u32). 345 * Only handles values in fan RPM range (0-65535). 346 */ 347 uint32_t 348 asmc_u32_to_float(uint32_t d) 349 { 350 uint32_t dc, bc, exp; 351 352 if (d == 0) 353 return (0); 354 355 /* Find highest set bit position */ 356 dc = d; 357 bc = 0; 358 while (dc >>= 1) 359 ++bc; 360 361 bc = MIN(bc, 30); 362 363 exp = 0x7f + bc; 364 365 /* 366 * Mantissa: strip the implicit leading 1-bit and place 367 * remaining bits into the 23-bit mantissa field. 368 */ 369 if (bc >= 23) 370 return ((exp << 23) | ((d >> (bc - 23)) & 0x7fffff)); 371 else 372 return ((exp << 23) | ((d << (23 - bc)) & 0x7fffff)); 373 } 374 375 /* 376 * Battery charge limit sysctl (T2 Macs). 377 * BCLM key: 1 byte, 0-100 (percentage). 378 */ 379 int 380 asmc_bclm_sysctl(SYSCTL_HANDLER_ARGS) 381 { 382 device_t dev = (device_t)arg1; 383 uint8_t bclm; 384 int val, error; 385 386 error = asmc_mmio_key_read(dev, ASMC_KEY_BCLM, &bclm, 1); 387 if (error != 0) 388 return (EIO); 389 390 val = (int)bclm; 391 error = sysctl_handle_int(oidp, &val, 0, req); 392 if (error != 0 || req->newptr == NULL) 393 return (error); 394 395 if (val < 0 || val > 100) 396 return (EINVAL); 397 398 bclm = (uint8_t)val; 399 error = asmc_mmio_key_write(dev, ASMC_KEY_BCLM, &bclm, 1); 400 401 return (error != 0 ? EIO : 0); 402 } 403