apei_mapreg.c revision 1.1 1 /* $NetBSD: apei_mapreg.c,v 1.1 2024/03/20 17:11:44 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.1 2024/03/20 17:11:44 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 *
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 case 4: /* 64-bit */
77 break;
78 default:
79 return NULL;
80 }
81
82 /*
83 * Verify we don't need to shift anything, because I can't
84 * figure out how the shifting is supposed to work in five
85 * minutes of looking at the spec.
86 */
87 switch (reg->BitOffset) {
88 case 0:
89 break;
90 default:
91 return NULL;
92 }
93
94 /*
95 * Verify the bit width is a multiple of the access width so
96 * we're not accessing more than we need.
97 */
98 if (reg->BitWidth % (8*(1 << (reg->AccessWidth - 1))))
99 return NULL;
100
101 /*
102 * Dispatch on the space id.
103 *
104 * Currently this only handles memory space because I/O space
105 * is too painful to contemplate reimplementing here.
106 */
107 switch (reg->SpaceId) {
108 case ACPI_ADR_SPACE_SYSTEM_MEMORY:
109 return AcpiOsMapMemory(reg->Address,
110 1 << (reg->AccessWidth - 1));
111 default:
112 return NULL;
113 }
114 }
115
116 /*
117 * apei_mapreg_unmap(reg, map)
118 *
119 * Unmap a mapping previously returned by apei_mapreg_map.
120 */
121 void
122 apei_mapreg_unmap(const ACPI_GENERIC_ADDRESS *reg,
123 struct apei_mapreg *map)
124 {
125
126 AcpiOsUnmapMemory(map, 1 << (reg->AccessWidth - 1));
127 }
128
129 /*
130 * apei_mapreg_read(reg, map)
131 *
132 * Read from reg via map previously obtained by apei_mapreg_map.
133 */
134 uint64_t
135 apei_mapreg_read(const ACPI_GENERIC_ADDRESS *reg,
136 const struct apei_mapreg *map)
137 {
138 unsigned chunkbits = NBBY*(1 << (reg->AccessWidth - 1));
139 unsigned i, n = reg->BitWidth % chunkbits;
140 uint64_t v = 0;
141
142 for (i = 0; i < n; i++) {
143 uint64_t chunk;
144
145 switch (reg->AccessWidth) {
146 case 1:
147 chunk = *(volatile const uint8_t *)map;
148 break;
149 case 2:
150 chunk = *(volatile const uint16_t *)map;
151 break;
152 case 3:
153 chunk = *(volatile const uint32_t *)map;
154 break;
155 case 4:
156 chunk = *(volatile const uint64_t *)map;
157 break;
158 default:
159 __unreachable();
160 }
161 v |= chunk << (i*chunkbits);
162 }
163
164 membar_acquire(); /* XXX probably not right for MMIO */
165 return v;
166 }
167
168 /*
169 * apei_mapreg_write(reg, map, v)
170 *
171 * Write to reg via map previously obtained by apei_mapreg_map.
172 */
173 void
174 apei_mapreg_write(const ACPI_GENERIC_ADDRESS *reg, struct apei_mapreg *map,
175 uint64_t v)
176 {
177 unsigned chunkbits = NBBY*(1 << (reg->AccessWidth - 1));
178 unsigned i, n = reg->BitWidth % chunkbits;
179
180 membar_release(); /* XXX probably not right for MMIO */
181 for (i = 0; i < n; i++) {
182 uint64_t chunk = v >> (i*chunkbits);
183
184 switch (reg->AccessWidth) {
185 case 1:
186 *(volatile uint8_t *)map = chunk;
187 break;
188 case 2:
189 *(volatile uint16_t *)map = chunk;
190 break;
191 case 3:
192 *(volatile uint32_t *)map = chunk;
193 break;
194 case 4:
195 *(volatile uint64_t *)map = chunk;
196 break;
197 default:
198 __unreachable();
199 }
200 }
201 }
202