1 /* $NetBSD: acpi_dev.c,v 1.4 2024/11/12 21:49:11 riastradh 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 AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 __KERNEL_RCSID(0, "$NetBSD: acpi_dev.c,v 1.4 2024/11/12 21:49:11 riastradh Exp $"); 31 32 #include <sys/param.h> 33 #include <sys/conf.h> 34 #include <sys/mman.h> 35 36 #include <dev/acpi/acpireg.h> 37 #include <dev/acpi/acpivar.h> 38 39 #define _COMPONENT ACPI_BUS_COMPONENT 40 ACPI_MODULE_NAME ("acpi_dev") 41 42 static dev_type_read(acpi_read); 43 44 const struct cdevsw acpi_cdevsw = { 45 .d_open = nullopen, 46 .d_close = nullclose, 47 .d_read = acpi_read, 48 .d_write = nowrite, 49 .d_ioctl = noioctl, 50 .d_stop = nostop, 51 .d_tty = notty, 52 .d_poll = nopoll, 53 .d_mmap = nommap, 54 .d_kqfilter = nokqfilter, 55 .d_discard = nodiscard, 56 .d_flag = D_OTHER | D_MPSAFE, 57 }; 58 59 /* 60 * acpi_find_table_rsdp -- 61 * 62 * Returns true if the RSDP table is found and overlaps the specified 63 * physical address. The table's physical start address and length 64 * are placed in 'paddr' and 'plen' when found. 65 * 66 */ 67 static bool 68 acpi_find_table_rsdp(ACPI_PHYSICAL_ADDRESS pa, 69 ACPI_PHYSICAL_ADDRESS *paddr, uint32_t *plen) 70 { 71 ACPI_PHYSICAL_ADDRESS table_pa; 72 73 table_pa = AcpiOsGetRootPointer(); 74 if (table_pa == 0) { 75 return false; 76 } 77 if (pa >= table_pa && pa < table_pa + sizeof(ACPI_TABLE_RSDP)) { 78 *paddr = table_pa; 79 *plen = sizeof(ACPI_TABLE_RSDP); 80 return true; 81 } 82 83 return false; 84 } 85 86 /* 87 * acpi_find_table_sdt -- 88 * 89 * Returns true if the XSDT/RSDT table is found and overlaps the 90 * specified physical address. The table's physical start address 91 * and length are placed in 'paddr' and 'plen' when found. 92 * 93 */ 94 static bool 95 acpi_find_table_sdt(ACPI_PHYSICAL_ADDRESS pa, 96 ACPI_PHYSICAL_ADDRESS *paddr, uint32_t *plen) 97 { 98 ACPI_PHYSICAL_ADDRESS table_pa; 99 ACPI_TABLE_RSDP *rsdp; 100 ACPI_TABLE_HEADER *thdr; 101 uint32_t table_len; 102 103 table_pa = AcpiOsGetRootPointer(); 104 KASSERT(table_pa != 0); 105 106 /* 107 * Find the XSDT/RSDT using the RSDP. 108 */ 109 rsdp = AcpiOsMapMemory(table_pa, sizeof(ACPI_TABLE_RSDP)); 110 if (rsdp == NULL) { 111 return false; 112 } 113 if (rsdp->Revision > 1 && rsdp->XsdtPhysicalAddress) { 114 table_pa = rsdp->XsdtPhysicalAddress; 115 } else { 116 table_pa = rsdp->RsdtPhysicalAddress; 117 } 118 AcpiOsUnmapMemory(rsdp, sizeof(ACPI_TABLE_RSDP)); 119 if (table_pa == 0) { 120 return false; 121 } 122 123 /* 124 * Map the XSDT/RSDT and check access. 125 */ 126 thdr = AcpiOsMapMemory(table_pa, sizeof(ACPI_TABLE_HEADER)); 127 if (thdr == NULL) { 128 return false; 129 } 130 table_len = thdr->Length; 131 AcpiOsUnmapMemory(thdr, sizeof(ACPI_TABLE_HEADER)); 132 if (pa >= table_pa && pa < table_pa + table_len) { 133 *paddr = table_pa; 134 *plen = table_len; 135 return true; 136 } 137 138 return false; 139 } 140 141 /* 142 * acpi_find_table -- 143 * 144 * Find an ACPI table that overlaps the specified physical address. 145 * Returns true if the table is found and places the table start 146 * address into 'paddr' and the length into 'plen'. 147 * 148 */ 149 static bool 150 acpi_find_table(ACPI_PHYSICAL_ADDRESS pa, 151 ACPI_PHYSICAL_ADDRESS *paddr, uint32_t *plen) 152 { 153 ACPI_TABLE_DESC *tdesc; 154 ACPI_TABLE_TCPA_HDR *tcpa = NULL; 155 size_t tcpa_tdesc_len = 0; 156 bool found_table; 157 uint32_t i; 158 159 /* Check for RSDP access. */ 160 if (acpi_find_table_rsdp(pa, paddr, plen)) { 161 return true; 162 } 163 164 /* Check for XSDT/RSDT access. */ 165 if (acpi_find_table_sdt(pa, paddr, plen)) { 166 return true; 167 } 168 169 /* Check for root table access. */ 170 found_table = false; 171 AcpiUtAcquireMutex(ACPI_MTX_TABLES); 172 for (i = 0; i < AcpiGbl_RootTableList.CurrentTableCount; i++) { 173 tdesc = &AcpiGbl_RootTableList.Tables[i]; 174 if (pa >= tdesc->Address && 175 pa < tdesc->Address + tdesc->Length) { 176 177 /* 178 * allow access to all root table objects 179 */ 180 *paddr = tdesc->Address; 181 *plen = tdesc->Length; 182 found_table = true; 183 break; 184 } else if (memcmp(tdesc->Signature.Ascii, ACPI_SIG_TCPA, 4) 185 == 0) { 186 187 /* 188 * allow acces to TCPA (which requires mapping) 189 */ 190 191 /* duplicate TCPA table? buggy firmware? */ 192 if (tcpa != NULL && tcpa_tdesc_len > 0) 193 AcpiOsUnmapMemory(tcpa, tcpa_tdesc_len); 194 195 tcpa = AcpiOsMapMemory(tdesc->Address, tdesc->Length); 196 if (tcpa != NULL) 197 tcpa_tdesc_len = tdesc->Length; 198 } 199 } 200 201 if (!found_table && tcpa != NULL) { 202 ACPI_PHYSICAL_ADDRESS tcpa_addr = 0; 203 uint32_t tcpa_len = 0; 204 205 if (tcpa->PlatformClass == ACPI_TCPA_CLIENT_TABLE) { 206 ACPI_TABLE_TCPA_CLIENT *t = 207 (ACPI_TABLE_TCPA_CLIENT *)(tcpa + 1); 208 tcpa_addr = t->LogAddress; 209 tcpa_len = t->MinimumLogLength; 210 } else if (tcpa->PlatformClass == ACPI_TCPA_SERVER_TABLE) { 211 ACPI_TABLE_TCPA_SERVER *t = 212 (ACPI_TABLE_TCPA_SERVER *)(tcpa + 1); 213 tcpa_addr = t->LogAddress; 214 tcpa_len = t->MinimumLogLength; 215 } 216 if (tcpa_len != 0 && 217 pa >= tcpa_addr && 218 pa < tcpa_addr + tcpa_len) { 219 *paddr = tcpa_addr; 220 *plen = tcpa_len; 221 found_table = true; 222 } 223 } 224 225 if (tcpa != NULL && tcpa_tdesc_len != 0) 226 AcpiOsUnmapMemory(tcpa, tcpa_tdesc_len); 227 228 AcpiUtReleaseMutex(ACPI_MTX_TABLES); 229 230 return found_table; 231 } 232 233 /* 234 * acpi_read -- 235 * 236 * Read data from an ACPI configuration table that resides in 237 * physical memory. Only supports reading one table at a time. 238 * 239 */ 240 static int 241 acpi_read(dev_t dev, struct uio *uio, int flag) 242 { 243 ACPI_PHYSICAL_ADDRESS pa, table_pa; 244 uint32_t table_len; 245 uint8_t *data; 246 int error; 247 size_t len; 248 249 if (uio->uio_rw != UIO_READ) { 250 return EPERM; 251 } 252 253 /* Make sure this is a read of a known table */ 254 if (!acpi_find_table(uio->uio_offset, &table_pa, &table_len)) { 255 return EIO; 256 } 257 258 /* Copy the contents of the table to user-space */ 259 pa = uio->uio_offset; 260 len = uimin(table_len - (pa - table_pa), uio->uio_resid); 261 data = AcpiOsMapMemory(pa, len); 262 if (data == NULL) { 263 return ENOMEM; 264 } 265 error = uiomove(data, len, uio); 266 AcpiOsUnmapMemory(data, len); 267 268 return error; 269 } 270