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 */
52struct apei_mapreg *
53apei_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 */
127void
128apei_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 */
149uint64_t
150apei_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 */
220void
221apei_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