acpi_dev.c revision 1.4 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 (at) 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