Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sys/dev/asmc/asmcmmio.c
288927 views
1
/*
2
* Copyright (c) 2026 Abdelkader Boudih <[email protected]>
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
404