1 /*        $NetBSD: apei_mapreg.c,v 1.4 2024/03/22 20:47:52 riastradh Exp $      */
2 
3 /*-
4  * Copyright (c) 2024 The NetBSD Foundation, Inc.
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  * Pre-mapped ACPI register access
31  *
32  * XXX This isn't APEI-specific -- it should be moved into the general
33  * ACPI API, and unified with the AcpiRead/AcpiWrite implementation.
34  */
35 
36 #include <sys/cdefs.h>
37 __KERNEL_RCSID(0, "$NetBSD: apei_mapreg.c,v 1.4 2024/03/22 20:47:52 riastradh Exp $");
38 
39 #include <sys/types.h>
40 
41 #include <sys/atomic.h>
42 
43 #include <dev/acpi/acpivar.h>
44 #include <dev/acpi/apei_mapreg.h>
45 
46 /*
47  * apei_mapreg_map(reg)
48  *
49  *        Return a mapping for use with apei_mapreg_read, or NULL if it
50  *        can't be mapped.
51  */
52 struct apei_mapreg *
apei_mapreg_map(const ACPI_GENERIC_ADDRESS * reg)53 apei_mapreg_map(const ACPI_GENERIC_ADDRESS *reg)
54 {
55 
56           /*
57            * Verify the result is reasonable.
58            */
59           switch (reg->BitWidth) {
60           case 8:
61           case 16:
62           case 32:
63           case 64:
64                     break;
65           default:
66                     return NULL;
67           }
68 
69           /*
70            * Verify we know how to do the access width.
71            */
72           switch (reg->AccessWidth) {
73           case 1:                       /* 8-bit */
74           case 2:                       /* 16-bit */
75           case 3:                       /* 32-bit */
76                     break;
77           case 4:                       /* 64-bit */
78                     if (reg->SpaceId == ACPI_ADR_SPACE_SYSTEM_IO)
79                               return NULL;
80                     break;
81           default:
82                     return NULL;
83           }
84 
85           /*
86            * Verify we don't need to shift anything, because I can't
87            * figure out how the shifting is supposed to work in five
88            * minutes of looking at the spec.
89            */
90           switch (reg->BitOffset) {
91           case 0:
92                     break;
93           default:
94                     return NULL;
95           }
96 
97           /*
98            * Verify the bit width is a multiple of the access width so
99            * we're not accessing more than we need.
100            */
101           if (reg->BitWidth % (8*(1 << (reg->AccessWidth - 1))))
102                     return NULL;
103 
104           /*
105            * Dispatch on the space id.
106            */
107           switch (reg->SpaceId) {
108           case ACPI_ADR_SPACE_SYSTEM_IO:
109                     /*
110                      * Just need to return something non-null -- no state
111                      * to record for a mapping.
112                      */
113                     return (struct apei_mapreg *)__UNCONST(reg);
114           case ACPI_ADR_SPACE_SYSTEM_MEMORY:
115                     return AcpiOsMapMemory(reg->Address,
116                         1 << (reg->AccessWidth - 1));
117           default:
118                     return NULL;
119           }
120 }
121 
122 /*
123  * apei_mapreg_unmap(reg, map)
124  *
125  *        Unmap a mapping previously returned by apei_mapreg_map.
126  */
127 void
apei_mapreg_unmap(const ACPI_GENERIC_ADDRESS * reg,struct apei_mapreg * map)128 apei_mapreg_unmap(const ACPI_GENERIC_ADDRESS *reg,
129     struct apei_mapreg *map)
130 {
131 
132           switch (reg->SpaceId) {
133           case ACPI_ADR_SPACE_SYSTEM_IO:
134                     KASSERT(map == __UNCONST(reg));
135                     break;
136           case ACPI_ADR_SPACE_SYSTEM_MEMORY:
137                     AcpiOsUnmapMemory(map, 1 << (reg->AccessWidth - 1));
138                     break;
139           default:
140                     panic("invalid register mapping");
141           }
142 }
143 
144 /*
145  * apei_mapreg_read(reg, map)
146  *
147  *        Read from reg via map previously obtained by apei_mapreg_map.
148  */
149 uint64_t
apei_mapreg_read(const ACPI_GENERIC_ADDRESS * reg,const struct apei_mapreg * map)150 apei_mapreg_read(const ACPI_GENERIC_ADDRESS *reg,
151     const struct apei_mapreg *map)
152 {
153           unsigned chunkbits = NBBY*(1 << (reg->AccessWidth - 1));
154           unsigned i, n = reg->BitWidth / chunkbits;
155           uint64_t v = 0;
156 
157           for (i = 0; i < n; i++) {
158                     uint64_t chunk;
159 
160                     switch (reg->SpaceId) {
161                     case ACPI_ADR_SPACE_SYSTEM_IO: {
162                               ACPI_IO_ADDRESS addr;
163                               uint32_t chunk32 = 0;
164                               ACPI_STATUS rv;
165 
166                               switch (reg->AccessWidth) {
167                               case 1:
168                                         addr = reg->Address + i;
169                                         break;
170                               case 2:
171                                         addr = reg->Address + 2*i;
172                                         break;
173                               case 3:
174                                         addr = reg->Address + 4*i;
175                                         break;
176                               case 4:   /* no 64-bit I/O ports */
177                               default:
178                                         __unreachable();
179                               }
180                               rv = AcpiOsReadPort(addr, &chunk32,
181                                   NBBY*(1 << (reg->AccessWidth - 1)));
182                               KASSERTMSG(!ACPI_FAILURE(rv), "%s",
183                                   AcpiFormatException(rv));
184                               chunk = chunk32;
185                               break;
186                     }
187                     case ACPI_ADR_SPACE_SYSTEM_MEMORY:
188                               switch (reg->AccessWidth) {
189                               case 1:
190                                         chunk = *((volatile const uint8_t *)map + i);
191                                         break;
192                               case 2:
193                                         chunk = *((volatile const uint16_t *)map + i);
194                                         break;
195                               case 3:
196                                         chunk = *((volatile const uint32_t *)map + i);
197                                         break;
198                               case 4:
199                                         chunk = *((volatile const uint64_t *)map + i);
200                                         break;
201                               default:
202                                         __unreachable();
203                               }
204                               break;
205                     default:
206                               __unreachable();
207                     }
208                     v |= chunk << (i*chunkbits);
209           }
210 
211           membar_acquire();   /* XXX probably not right for MMIO */
212           return v;
213 }
214 
215 /*
216  * apei_mapreg_write(reg, map, v)
217  *
218  *        Write to reg via map previously obtained by apei_mapreg_map.
219  */
220 void
apei_mapreg_write(const ACPI_GENERIC_ADDRESS * reg,struct apei_mapreg * map,uint64_t v)221 apei_mapreg_write(const ACPI_GENERIC_ADDRESS *reg, struct apei_mapreg *map,
222     uint64_t v)
223 {
224           unsigned chunkbits = NBBY*(1 << (reg->AccessWidth - 1));
225           unsigned i, n = reg->BitWidth / chunkbits;
226 
227           membar_release();   /* XXX probably not right for MMIO */
228           for (i = 0; i < n; i++) {
229                     uint64_t chunk = v >> (i*chunkbits);
230 
231                     switch (reg->SpaceId) {
232                     case ACPI_ADR_SPACE_SYSTEM_IO: {
233                               ACPI_IO_ADDRESS addr;
234                               ACPI_STATUS rv;
235 
236                               switch (reg->AccessWidth) {
237                               case 1:
238                                         addr = reg->Address + i;
239                                         break;
240                               case 2:
241                                         addr = reg->Address + 2*i;
242                                         break;
243                               case 3:
244                                         addr = reg->Address + 4*i;
245                                         break;
246                               case 4:   /* no 64-bit I/O ports */
247                               default:
248                                         __unreachable();
249                               }
250                               rv = AcpiOsWritePort(addr, chunk,
251                                   NBBY*(1 << (reg->AccessWidth - 1)));
252                               KASSERTMSG(!ACPI_FAILURE(rv), "%s",
253                                   AcpiFormatException(rv));
254                               break;
255                     }
256                     case ACPI_ADR_SPACE_SYSTEM_MEMORY:
257                               switch (reg->AccessWidth) {
258                               case 1:
259                                         *((volatile uint8_t *)map + i) = chunk;
260                                         break;
261                               case 2:
262                                         *((volatile uint16_t *)map + i) = chunk;
263                                         break;
264                               case 3:
265                                         *((volatile uint32_t *)map + i) = chunk;
266                                         break;
267                               case 4:
268                                         *((volatile uint64_t *)map + i) = chunk;
269                                         break;
270                               default:
271                                         __unreachable();
272                               }
273                               break;
274                     default:
275                               __unreachable();
276                     }
277           }
278 }
279