xref: /freebsd/sys/dev/asmc/asmcmmio.c (revision a48b900300ebdbd5c47e664b4cc06e705da91bd8)
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