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
asmc_mmio_wait(device_t dev)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
asmc_mmio_key_read(device_t dev,const char * key,uint8_t * buf,uint8_t len)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
asmc_mmio_key_write(device_t dev,const char * key,uint8_t * buf,uint8_t len)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
asmc_mmio_key_getinfo(device_t dev,const char * key,uint8_t * len,char * type)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
asmc_mmio_key_getbyindex(device_t dev,int index,char * key)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
asmc_mmio_probe(device_t dev)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
asmc_mmio_detach(device_t dev,struct asmc_softc * sc)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
asmc_float_to_u32(uint32_t d)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
asmc_u32_to_float(uint32_t d)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
asmc_bclm_sysctl(SYSCTL_HANDLER_ARGS)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