1 /* $NetBSD: acpi_pcc.c,v 1.1 2020/12/13 20:27:53 jmcneill Exp $ */ 2 3 /*- 4 * Copyright (c) 2020 Jared McNeill <jmcneill@invisible.ca> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 /* 30 * Platform Communications Channel (PCC) device driver. 31 */ 32 33 #include <sys/cdefs.h> 34 __KERNEL_RCSID(0, "$NetBSD: acpi_pcc.c,v 1.1 2020/12/13 20:27:53 jmcneill Exp $"); 35 36 #include <sys/param.h> 37 #include <sys/bus.h> 38 #include <sys/cpu.h> 39 #include <sys/device.h> 40 #include <sys/kmem.h> 41 42 #include <dev/acpi/acpireg.h> 43 #include <dev/acpi/acpivar.h> 44 #include <dev/acpi/acpi_pcc.h> 45 46 #define PCC_MEMORY_BARRIER() membar_sync() 47 #if defined(__aarch64__) 48 #define PCC_DMA_BARRIER() __asm __volatile("dsb sy" ::: "memory") 49 #else 50 #error PCC_BARRIER not implemented on this platform 51 #endif 52 53 #define PCC_SIGNATURE(space_id) (0x50434300 | (space_id)) 54 #define PCC_STATUS_COMMAND_COMPLETE __BIT(0) 55 #define PCC_STATUS_COMMAND_ERROR __BIT(2) 56 57 struct pcc_register { 58 ACPI_GENERIC_ADDRESS reg_addr; 59 uint64_t reg_preserve; 60 uint64_t reg_set; 61 }; 62 63 struct pcc_subspace { 64 uint8_t ss_id; 65 uint8_t ss_type; 66 void * ss_data; 67 size_t ss_len; 68 struct pcc_register ss_doorbell_reg; 69 uint32_t ss_latency; 70 uint32_t ss_turnaround; 71 kmutex_t ss_lock; 72 }; 73 74 struct pcc_softc { 75 device_t sc_dev; 76 struct pcc_subspace * sc_ss; 77 u_int sc_nss; 78 }; 79 80 typedef void (*pcc_subspace_callback)(ACPI_SUBTABLE_HEADER *, uint8_t, void *); 81 82 static int pcc_match(device_t, cfdata_t, void *); 83 static void pcc_attach(device_t, device_t, void *); 84 85 static uint8_t pcc_subspace_foreach(ACPI_TABLE_PCCT *, 86 pcc_subspace_callback, void *); 87 static void pcc_subspace_attach(ACPI_SUBTABLE_HEADER *, uint8_t, void *); 88 89 static struct pcc_softc *pcc_softc = NULL; 90 91 CFATTACH_DECL_NEW(acpipcc, sizeof(struct pcc_softc), 92 pcc_match, pcc_attach, NULL, NULL); 93 94 static int 95 pcc_match(device_t parent, cfdata_t cf, void *aux) 96 { 97 ACPI_TABLE_HEADER *hdrp = aux; 98 99 return memcmp(hdrp->Signature, ACPI_SIG_PCCT, ACPI_NAMESEG_SIZE) == 0; 100 } 101 102 static void 103 pcc_attach(device_t parent, device_t self, void *aux) 104 { 105 struct pcc_softc * const sc = device_private(self); 106 ACPI_TABLE_PCCT *pcct = aux; 107 u_int count; 108 109 if (pcc_softc != NULL) { 110 aprint_error(": Already attached!\n"); 111 return; 112 } 113 114 count = pcc_subspace_foreach(pcct, NULL, NULL); 115 if (count == 0) { 116 aprint_error(": No subspaces found!\n"); 117 return; 118 } 119 120 aprint_naive("\n"); 121 aprint_normal(": Platform Communications Channel, %u subspace%s\n", 122 count, count == 1 ? "" : "s"); 123 124 sc->sc_dev = self; 125 sc->sc_nss = count; 126 sc->sc_ss = kmem_zalloc(count * sizeof(*sc->sc_ss), KM_SLEEP); 127 pcc_subspace_foreach(pcct, pcc_subspace_attach, sc); 128 129 pcc_softc = sc; 130 } 131 132 /* 133 * pcc_subspace_foreach -- 134 * 135 * Find all subspaces defined in the PCCT table. If a 'func' callback 136 * is provided, the callback is invoked for each subspace with the 137 * table pointer, subspace ID, and 'arg' as arguments. Returns the 138 * number of subspaces found. 139 */ 140 static uint8_t 141 pcc_subspace_foreach(ACPI_TABLE_PCCT *pcct, pcc_subspace_callback func, 142 void *arg) 143 { 144 ACPI_SUBTABLE_HEADER *header; 145 char *ptr, *end; 146 uint8_t count; 147 148 end = (char *)pcct + pcct->Header.Length; 149 ptr = (char *)pcct + sizeof(*pcct); 150 count = 0; 151 152 while (ptr < end && count < 255) { 153 header = (ACPI_SUBTABLE_HEADER *)ptr; 154 if (header->Length == 0 || ptr + header->Length > end) { 155 break; 156 } 157 if (func != NULL) { 158 func(header, count, arg); 159 } 160 ++count; 161 ptr += header->Length; 162 } 163 164 return count; 165 } 166 167 /* 168 * pcc_subspace_attach -- 169 * 170 * Allocate resources for a PCC subspace. 171 */ 172 static void 173 pcc_subspace_attach(ACPI_SUBTABLE_HEADER *header, uint8_t id, void *arg) 174 { 175 struct pcc_softc * const sc = arg; 176 struct pcc_subspace * const ss = &sc->sc_ss[id]; 177 ACPI_PCCT_SUBSPACE *generic = (ACPI_PCCT_SUBSPACE *)header; 178 179 ss->ss_id = id; 180 ss->ss_type = header->Type; 181 ss->ss_data = AcpiOsMapMemory(generic->BaseAddress, generic->Length); 182 if (ss->ss_data == NULL) { 183 panic("%s: couldn't map subspace 0x%02x at 0x%016" PRIx64, 184 device_xname(sc->sc_dev), id, generic->BaseAddress); 185 } 186 ss->ss_len = generic->Length; 187 ss->ss_doorbell_reg.reg_addr = generic->DoorbellRegister; 188 ss->ss_doorbell_reg.reg_preserve = generic->PreserveMask; 189 ss->ss_doorbell_reg.reg_set = generic->WriteMask; 190 mutex_init(&ss->ss_lock, MUTEX_DEFAULT, IPL_NONE); 191 } 192 193 /* 194 * pcc_wait_command -- 195 * 196 * Wait for command complete to be set on a PCC subspace, with timeout. 197 */ 198 static ACPI_STATUS 199 pcc_wait_command(struct pcc_subspace *ss, bool first) 200 { 201 volatile ACPI_PCCT_SHARED_MEMORY *shmem; 202 int retry; 203 204 switch (ss->ss_type) { 205 case ACPI_PCCT_TYPE_GENERIC_SUBSPACE: 206 /* 207 * OSPM does not have to wait for command complete before 208 * sending the first command. 209 */ 210 if (first) { 211 return AE_OK; 212 } 213 /* FALLTHROUGH */ 214 215 case ACPI_PCCT_TYPE_HW_REDUCED_SUBSPACE: 216 case ACPI_PCCT_TYPE_HW_REDUCED_SUBSPACE_TYPE2: 217 /* 218 * Wait for the command complete bit to be set in the status 219 * register. 220 */ 221 shmem = ss->ss_data; 222 retry = imax(1000, ss->ss_turnaround + ss->ss_latency * 100); 223 while (retry > 0) { 224 PCC_MEMORY_BARRIER(); 225 if ((shmem->Status & PCC_STATUS_COMMAND_COMPLETE) != 0) 226 return AE_OK; 227 delay(10); 228 retry -= 10; 229 } 230 return AE_TIME; 231 232 default: 233 return AE_NOT_IMPLEMENTED; 234 } 235 } 236 237 /* 238 * pcc_doorbell -- 239 * 240 * Ring the door bell by writing to the doorbell register. 241 */ 242 static ACPI_STATUS 243 pcc_doorbell(struct pcc_register *reg) 244 { 245 ACPI_STATUS rv; 246 UINT64 val; 247 248 rv = AcpiRead(&val, ®->reg_addr); 249 if (ACPI_FAILURE(rv)) { 250 return rv; 251 } 252 val &= reg->reg_preserve; 253 val |= reg->reg_set; 254 return AcpiWrite(val, ®->reg_addr); 255 } 256 257 /* 258 * pcc_send_command -- 259 * 260 * Write a command into PCC subspace and ring the doorbell. 261 */ 262 static ACPI_STATUS 263 pcc_send_command(struct pcc_subspace *ss, ACPI_GENERIC_ADDRESS *reg, 264 uint32_t command, int flags, ACPI_INTEGER val) 265 { 266 volatile ACPI_PCCT_SHARED_MEMORY *shmem = ss->ss_data; 267 uint8_t *data = __UNVOLATILE(shmem + 1); 268 UINT64 tmp, mask; 269 270 KASSERT(ss->ss_type == ACPI_PCCT_TYPE_GENERIC_SUBSPACE || 271 ss->ss_type == ACPI_PCCT_TYPE_HW_REDUCED_SUBSPACE || 272 ss->ss_type == ACPI_PCCT_TYPE_HW_REDUCED_SUBSPACE_TYPE2); 273 274 shmem->Signature = PCC_SIGNATURE(ss->ss_id); 275 shmem->Command = command & 0xff; 276 if ((flags & PCC_WRITE) != 0) { 277 mask = __BITS(reg->BitOffset + reg->BitWidth - 1, 278 reg->BitOffset); 279 ACPI_MOVE_64_TO_64(&tmp, data + reg->Address); 280 tmp &= mask; 281 tmp |= __SHIFTIN(val, mask); 282 ACPI_MOVE_64_TO_64(data + reg->Address, &tmp); 283 } 284 PCC_MEMORY_BARRIER(); 285 shmem->Status &= ~(PCC_STATUS_COMMAND_COMPLETE | 286 PCC_STATUS_COMMAND_ERROR); 287 PCC_DMA_BARRIER(); 288 289 return pcc_doorbell(&ss->ss_doorbell_reg); 290 } 291 292 /* 293 * pcc_receive_response -- 294 * 295 * Process a command response in PCC subspace. 296 */ 297 static ACPI_STATUS 298 pcc_receive_response(struct pcc_subspace *ss, ACPI_GENERIC_ADDRESS *reg, 299 int flags, ACPI_INTEGER *val) 300 { 301 volatile ACPI_PCCT_SHARED_MEMORY *shmem = ss->ss_data; 302 const uint8_t *data = __UNVOLATILE(shmem + 1); 303 UINT64 tmp, mask; 304 305 KASSERT(ss->ss_type == ACPI_PCCT_TYPE_GENERIC_SUBSPACE || 306 ss->ss_type == ACPI_PCCT_TYPE_HW_REDUCED_SUBSPACE || 307 ss->ss_type == ACPI_PCCT_TYPE_HW_REDUCED_SUBSPACE_TYPE2); 308 309 KASSERT((shmem->Status & PCC_STATUS_COMMAND_COMPLETE) != 0); 310 311 if ((shmem->Status & PCC_STATUS_COMMAND_ERROR) != 0) { 312 return AE_ERROR; 313 } 314 315 if ((flags & PCC_READ) != 0) { 316 mask = __BITS(reg->BitOffset + reg->BitWidth - 1, 317 reg->BitOffset); 318 ACPI_MOVE_64_TO_64(&tmp, data + reg->Address); 319 *val = __SHIFTOUT(tmp, mask); 320 } 321 322 return AE_OK; 323 } 324 325 /* 326 * pcc_message -- 327 * 328 * Send or receive a command over a PCC subspace. 329 */ 330 ACPI_STATUS 331 pcc_message(ACPI_GENERIC_ADDRESS *reg, uint32_t command, int flags, 332 ACPI_INTEGER *val) 333 { 334 struct pcc_softc * const sc = pcc_softc; 335 struct pcc_subspace *ss; 336 uint8_t ss_id; 337 ACPI_STATUS rv; 338 339 /* The "Access Width" in the PCC GAS is the Subspace ID */ 340 ss_id = reg->AccessWidth; 341 342 if (sc == NULL) { 343 return AE_NOT_CONFIGURED; 344 } 345 if (ss_id >= sc->sc_nss) { 346 return AE_NOT_FOUND; 347 } 348 349 ss = &sc->sc_ss[ss_id]; 350 switch (ss->ss_type) { 351 case ACPI_PCCT_TYPE_GENERIC_SUBSPACE: 352 case ACPI_PCCT_TYPE_HW_REDUCED_SUBSPACE: 353 case ACPI_PCCT_TYPE_HW_REDUCED_SUBSPACE_TYPE2: 354 break; 355 default: 356 return AE_NOT_IMPLEMENTED; 357 } 358 359 mutex_enter(&ss->ss_lock); 360 361 /* Wait for any previous commands to finish. */ 362 rv = pcc_wait_command(ss, true); 363 if (ACPI_FAILURE(rv)) { 364 goto unlock; 365 } 366 367 /* Place command into subspace and ring the doorbell */ 368 rv = pcc_send_command(ss, reg, command, flags, *val); 369 if (ACPI_FAILURE(rv)) { 370 goto unlock; 371 } 372 373 /* Wait for command to complete. */ 374 rv = pcc_wait_command(ss, false); 375 if (ACPI_FAILURE(rv)) { 376 goto unlock; 377 } 378 379 /* Process the command response */ 380 rv = pcc_receive_response(ss, reg, flags, val); 381 382 if (ss->ss_turnaround != 0) 383 delay(ss->ss_turnaround); 384 385 unlock: 386 mutex_exit(&ss->ss_lock); 387 388 return rv; 389 } 390