162306a36Sopenharmony_ci// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
262306a36Sopenharmony_ci/******************************************************************************
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Module Name: hwvalid - I/O request validation
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright (C) 2000 - 2023, Intel Corp.
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci *****************************************************************************/
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <acpi/acpi.h>
1162306a36Sopenharmony_ci#include "accommon.h"
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#define _COMPONENT          ACPI_HARDWARE
1462306a36Sopenharmony_ciACPI_MODULE_NAME("hwvalid")
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci/* Local prototypes */
1762306a36Sopenharmony_cistatic acpi_status
1862306a36Sopenharmony_ciacpi_hw_validate_io_request(acpi_io_address address, u32 bit_width);
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci/*
2162306a36Sopenharmony_ci * Protected I/O ports. Some ports are always illegal, and some are
2262306a36Sopenharmony_ci * conditionally illegal. This table must remain ordered by port address.
2362306a36Sopenharmony_ci *
2462306a36Sopenharmony_ci * The table is used to implement the Microsoft port access rules that
2562306a36Sopenharmony_ci * first appeared in Windows XP. Some ports are always illegal, and some
2662306a36Sopenharmony_ci * ports are only illegal if the BIOS calls _OSI with nothing newer than
2762306a36Sopenharmony_ci * the specific _OSI strings.
2862306a36Sopenharmony_ci *
2962306a36Sopenharmony_ci * This provides ACPICA with the desired port protections and
3062306a36Sopenharmony_ci * Microsoft compatibility.
3162306a36Sopenharmony_ci *
3262306a36Sopenharmony_ci * Description of port entries:
3362306a36Sopenharmony_ci *  DMA:   DMA controller
3462306a36Sopenharmony_ci *  PIC0:  Programmable Interrupt Controller (8259A)
3562306a36Sopenharmony_ci *  PIT1:  System Timer 1
3662306a36Sopenharmony_ci *  PIT2:  System Timer 2 failsafe
3762306a36Sopenharmony_ci *  RTC:   Real-time clock
3862306a36Sopenharmony_ci *  CMOS:  Extended CMOS
3962306a36Sopenharmony_ci *  DMA1:  DMA 1 page registers
4062306a36Sopenharmony_ci *  DMA1L: DMA 1 Ch 0 low page
4162306a36Sopenharmony_ci *  DMA2:  DMA 2 page registers
4262306a36Sopenharmony_ci *  DMA2L: DMA 2 low page refresh
4362306a36Sopenharmony_ci *  ARBC:  Arbitration control
4462306a36Sopenharmony_ci *  SETUP: Reserved system board setup
4562306a36Sopenharmony_ci *  POS:   POS channel select
4662306a36Sopenharmony_ci *  PIC1:  Cascaded PIC
4762306a36Sopenharmony_ci *  IDMA:  ISA DMA
4862306a36Sopenharmony_ci *  ELCR:  PIC edge/level registers
4962306a36Sopenharmony_ci *  PCI:   PCI configuration space
5062306a36Sopenharmony_ci */
5162306a36Sopenharmony_cistatic const struct acpi_port_info acpi_protected_ports[] = {
5262306a36Sopenharmony_ci	{"DMA", 0x0000, 0x000F, ACPI_OSI_WIN_XP},
5362306a36Sopenharmony_ci	{"PIC0", 0x0020, 0x0021, ACPI_ALWAYS_ILLEGAL},
5462306a36Sopenharmony_ci	{"PIT1", 0x0040, 0x0043, ACPI_OSI_WIN_XP},
5562306a36Sopenharmony_ci	{"PIT2", 0x0048, 0x004B, ACPI_OSI_WIN_XP},
5662306a36Sopenharmony_ci	{"RTC", 0x0070, 0x0071, ACPI_OSI_WIN_XP},
5762306a36Sopenharmony_ci	{"CMOS", 0x0074, 0x0076, ACPI_OSI_WIN_XP},
5862306a36Sopenharmony_ci	{"DMA1", 0x0081, 0x0083, ACPI_OSI_WIN_XP},
5962306a36Sopenharmony_ci	{"DMA1L", 0x0087, 0x0087, ACPI_OSI_WIN_XP},
6062306a36Sopenharmony_ci	{"DMA2", 0x0089, 0x008B, ACPI_OSI_WIN_XP},
6162306a36Sopenharmony_ci	{"DMA2L", 0x008F, 0x008F, ACPI_OSI_WIN_XP},
6262306a36Sopenharmony_ci	{"ARBC", 0x0090, 0x0091, ACPI_OSI_WIN_XP},
6362306a36Sopenharmony_ci	{"SETUP", 0x0093, 0x0094, ACPI_OSI_WIN_XP},
6462306a36Sopenharmony_ci	{"POS", 0x0096, 0x0097, ACPI_OSI_WIN_XP},
6562306a36Sopenharmony_ci	{"PIC1", 0x00A0, 0x00A1, ACPI_ALWAYS_ILLEGAL},
6662306a36Sopenharmony_ci	{"IDMA", 0x00C0, 0x00DF, ACPI_OSI_WIN_XP},
6762306a36Sopenharmony_ci	{"ELCR", 0x04D0, 0x04D1, ACPI_ALWAYS_ILLEGAL},
6862306a36Sopenharmony_ci	{"PCI", 0x0CF8, 0x0CFF, ACPI_OSI_WIN_XP}
6962306a36Sopenharmony_ci};
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci#define ACPI_PORT_INFO_ENTRIES      ACPI_ARRAY_LENGTH (acpi_protected_ports)
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci/******************************************************************************
7462306a36Sopenharmony_ci *
7562306a36Sopenharmony_ci * FUNCTION:    acpi_hw_validate_io_request
7662306a36Sopenharmony_ci *
7762306a36Sopenharmony_ci * PARAMETERS:  Address             Address of I/O port/register
7862306a36Sopenharmony_ci *              bit_width           Number of bits (8,16,32)
7962306a36Sopenharmony_ci *
8062306a36Sopenharmony_ci * RETURN:      Status
8162306a36Sopenharmony_ci *
8262306a36Sopenharmony_ci * DESCRIPTION: Validates an I/O request (address/length). Certain ports are
8362306a36Sopenharmony_ci *              always illegal and some ports are only illegal depending on
8462306a36Sopenharmony_ci *              the requests the BIOS AML code makes to the predefined
8562306a36Sopenharmony_ci *              _OSI method.
8662306a36Sopenharmony_ci *
8762306a36Sopenharmony_ci ******************************************************************************/
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_cistatic acpi_status
9062306a36Sopenharmony_ciacpi_hw_validate_io_request(acpi_io_address address, u32 bit_width)
9162306a36Sopenharmony_ci{
9262306a36Sopenharmony_ci	u32 i;
9362306a36Sopenharmony_ci	u32 byte_width;
9462306a36Sopenharmony_ci	acpi_io_address last_address;
9562306a36Sopenharmony_ci	const struct acpi_port_info *port_info;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	ACPI_FUNCTION_TRACE(hw_validate_io_request);
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	/* Supported widths are 8/16/32 */
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	if ((bit_width != 8) && (bit_width != 16) && (bit_width != 32)) {
10262306a36Sopenharmony_ci		ACPI_ERROR((AE_INFO,
10362306a36Sopenharmony_ci			    "Bad BitWidth parameter: %8.8X", bit_width));
10462306a36Sopenharmony_ci		return_ACPI_STATUS(AE_BAD_PARAMETER);
10562306a36Sopenharmony_ci	}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	port_info = acpi_protected_ports;
10862306a36Sopenharmony_ci	byte_width = ACPI_DIV_8(bit_width);
10962306a36Sopenharmony_ci	last_address = address + byte_width - 1;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	ACPI_DEBUG_PRINT((ACPI_DB_IO,
11262306a36Sopenharmony_ci			  "Address %8.8X%8.8X LastAddress %8.8X%8.8X Length %X",
11362306a36Sopenharmony_ci			  ACPI_FORMAT_UINT64(address),
11462306a36Sopenharmony_ci			  ACPI_FORMAT_UINT64(last_address), byte_width));
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	/* Maximum 16-bit address in I/O space */
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	if (last_address > ACPI_UINT16_MAX) {
11962306a36Sopenharmony_ci		ACPI_ERROR((AE_INFO,
12062306a36Sopenharmony_ci			    "Illegal I/O port address/length above 64K: %8.8X%8.8X/0x%X",
12162306a36Sopenharmony_ci			    ACPI_FORMAT_UINT64(address), byte_width));
12262306a36Sopenharmony_ci		return_ACPI_STATUS(AE_LIMIT);
12362306a36Sopenharmony_ci	}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	/* Exit if requested address is not within the protected port table */
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	if (address > acpi_protected_ports[ACPI_PORT_INFO_ENTRIES - 1].end) {
12862306a36Sopenharmony_ci		return_ACPI_STATUS(AE_OK);
12962306a36Sopenharmony_ci	}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	/* Check request against the list of protected I/O ports */
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	for (i = 0; i < ACPI_PORT_INFO_ENTRIES; i++, port_info++) {
13462306a36Sopenharmony_ci		/*
13562306a36Sopenharmony_ci		 * Check if the requested address range will write to a reserved
13662306a36Sopenharmony_ci		 * port. There are four cases to consider:
13762306a36Sopenharmony_ci		 *
13862306a36Sopenharmony_ci		 * 1) Address range is contained completely in the port address range
13962306a36Sopenharmony_ci		 * 2) Address range overlaps port range at the port range start
14062306a36Sopenharmony_ci		 * 3) Address range overlaps port range at the port range end
14162306a36Sopenharmony_ci		 * 4) Address range completely encompasses the port range
14262306a36Sopenharmony_ci		 */
14362306a36Sopenharmony_ci		if ((address <= port_info->end)
14462306a36Sopenharmony_ci		    && (last_address >= port_info->start)) {
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci			/* Port illegality may depend on the _OSI calls made by the BIOS */
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci			if (port_info->osi_dependency == ACPI_ALWAYS_ILLEGAL ||
14962306a36Sopenharmony_ci			    acpi_gbl_osi_data == port_info->osi_dependency) {
15062306a36Sopenharmony_ci				ACPI_DEBUG_PRINT((ACPI_DB_VALUES,
15162306a36Sopenharmony_ci						  "Denied AML access to port 0x%8.8X%8.8X/%X (%s 0x%.4X-0x%.4X)\n",
15262306a36Sopenharmony_ci						  ACPI_FORMAT_UINT64(address),
15362306a36Sopenharmony_ci						  byte_width, port_info->name,
15462306a36Sopenharmony_ci						  port_info->start,
15562306a36Sopenharmony_ci						  port_info->end));
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci				return_ACPI_STATUS(AE_AML_ILLEGAL_ADDRESS);
15862306a36Sopenharmony_ci			}
15962306a36Sopenharmony_ci		}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci		/* Finished if address range ends before the end of this port */
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci		if (last_address <= port_info->end) {
16462306a36Sopenharmony_ci			break;
16562306a36Sopenharmony_ci		}
16662306a36Sopenharmony_ci	}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	return_ACPI_STATUS(AE_OK);
16962306a36Sopenharmony_ci}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci/******************************************************************************
17262306a36Sopenharmony_ci *
17362306a36Sopenharmony_ci * FUNCTION:    acpi_hw_read_port
17462306a36Sopenharmony_ci *
17562306a36Sopenharmony_ci * PARAMETERS:  Address             Address of I/O port/register to read
17662306a36Sopenharmony_ci *              Value               Where value (data) is returned
17762306a36Sopenharmony_ci *              Width               Number of bits
17862306a36Sopenharmony_ci *
17962306a36Sopenharmony_ci * RETURN:      Status and value read from port
18062306a36Sopenharmony_ci *
18162306a36Sopenharmony_ci * DESCRIPTION: Read data from an I/O port or register. This is a front-end
18262306a36Sopenharmony_ci *              to acpi_os_read_port that performs validation on both the port
18362306a36Sopenharmony_ci *              address and the length.
18462306a36Sopenharmony_ci *
18562306a36Sopenharmony_ci *****************************************************************************/
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ciacpi_status acpi_hw_read_port(acpi_io_address address, u32 *value, u32 width)
18862306a36Sopenharmony_ci{
18962306a36Sopenharmony_ci	acpi_status status;
19062306a36Sopenharmony_ci	u32 one_byte;
19162306a36Sopenharmony_ci	u32 i;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	/* Truncate address to 16 bits if requested */
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	if (acpi_gbl_truncate_io_addresses) {
19662306a36Sopenharmony_ci		address &= ACPI_UINT16_MAX;
19762306a36Sopenharmony_ci	}
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	/* Validate the entire request and perform the I/O */
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	status = acpi_hw_validate_io_request(address, width);
20262306a36Sopenharmony_ci	if (ACPI_SUCCESS(status)) {
20362306a36Sopenharmony_ci		status = acpi_os_read_port(address, value, width);
20462306a36Sopenharmony_ci		return (status);
20562306a36Sopenharmony_ci	}
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	if (status != AE_AML_ILLEGAL_ADDRESS) {
20862306a36Sopenharmony_ci		return (status);
20962306a36Sopenharmony_ci	}
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	/*
21262306a36Sopenharmony_ci	 * There has been a protection violation within the request. Fall
21362306a36Sopenharmony_ci	 * back to byte granularity port I/O and ignore the failing bytes.
21462306a36Sopenharmony_ci	 * This provides compatibility with other ACPI implementations.
21562306a36Sopenharmony_ci	 */
21662306a36Sopenharmony_ci	for (i = 0, *value = 0; i < width; i += 8) {
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci		/* Validate and read one byte */
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci		if (acpi_hw_validate_io_request(address, 8) == AE_OK) {
22162306a36Sopenharmony_ci			status = acpi_os_read_port(address, &one_byte, 8);
22262306a36Sopenharmony_ci			if (ACPI_FAILURE(status)) {
22362306a36Sopenharmony_ci				return (status);
22462306a36Sopenharmony_ci			}
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci			*value |= (one_byte << i);
22762306a36Sopenharmony_ci		}
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci		address++;
23062306a36Sopenharmony_ci	}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	return (AE_OK);
23362306a36Sopenharmony_ci}
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci/******************************************************************************
23662306a36Sopenharmony_ci *
23762306a36Sopenharmony_ci * FUNCTION:    acpi_hw_write_port
23862306a36Sopenharmony_ci *
23962306a36Sopenharmony_ci * PARAMETERS:  Address             Address of I/O port/register to write
24062306a36Sopenharmony_ci *              Value               Value to write
24162306a36Sopenharmony_ci *              Width               Number of bits
24262306a36Sopenharmony_ci *
24362306a36Sopenharmony_ci * RETURN:      Status
24462306a36Sopenharmony_ci *
24562306a36Sopenharmony_ci * DESCRIPTION: Write data to an I/O port or register. This is a front-end
24662306a36Sopenharmony_ci *              to acpi_os_write_port that performs validation on both the port
24762306a36Sopenharmony_ci *              address and the length.
24862306a36Sopenharmony_ci *
24962306a36Sopenharmony_ci *****************************************************************************/
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ciacpi_status acpi_hw_write_port(acpi_io_address address, u32 value, u32 width)
25262306a36Sopenharmony_ci{
25362306a36Sopenharmony_ci	acpi_status status;
25462306a36Sopenharmony_ci	u32 i;
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	/* Truncate address to 16 bits if requested */
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	if (acpi_gbl_truncate_io_addresses) {
25962306a36Sopenharmony_ci		address &= ACPI_UINT16_MAX;
26062306a36Sopenharmony_ci	}
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	/* Validate the entire request and perform the I/O */
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	status = acpi_hw_validate_io_request(address, width);
26562306a36Sopenharmony_ci	if (ACPI_SUCCESS(status)) {
26662306a36Sopenharmony_ci		status = acpi_os_write_port(address, value, width);
26762306a36Sopenharmony_ci		return (status);
26862306a36Sopenharmony_ci	}
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	if (status != AE_AML_ILLEGAL_ADDRESS) {
27162306a36Sopenharmony_ci		return (status);
27262306a36Sopenharmony_ci	}
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	/*
27562306a36Sopenharmony_ci	 * There has been a protection violation within the request. Fall
27662306a36Sopenharmony_ci	 * back to byte granularity port I/O and ignore the failing bytes.
27762306a36Sopenharmony_ci	 * This provides compatibility with other ACPI implementations.
27862306a36Sopenharmony_ci	 */
27962306a36Sopenharmony_ci	for (i = 0; i < width; i += 8) {
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci		/* Validate and write one byte */
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci		if (acpi_hw_validate_io_request(address, 8) == AE_OK) {
28462306a36Sopenharmony_ci			status =
28562306a36Sopenharmony_ci			    acpi_os_write_port(address, (value >> i) & 0xFF, 8);
28662306a36Sopenharmony_ci			if (ACPI_FAILURE(status)) {
28762306a36Sopenharmony_ci				return (status);
28862306a36Sopenharmony_ci			}
28962306a36Sopenharmony_ci		}
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci		address++;
29262306a36Sopenharmony_ci	}
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	return (AE_OK);
29562306a36Sopenharmony_ci}
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci/******************************************************************************
29862306a36Sopenharmony_ci *
29962306a36Sopenharmony_ci * FUNCTION:    acpi_hw_validate_io_block
30062306a36Sopenharmony_ci *
30162306a36Sopenharmony_ci * PARAMETERS:  Address             Address of I/O port/register blobk
30262306a36Sopenharmony_ci *              bit_width           Number of bits (8,16,32) in each register
30362306a36Sopenharmony_ci *              count               Number of registers in the block
30462306a36Sopenharmony_ci *
30562306a36Sopenharmony_ci * RETURN:      Status
30662306a36Sopenharmony_ci *
30762306a36Sopenharmony_ci * DESCRIPTION: Validates a block of I/O ports/registers.
30862306a36Sopenharmony_ci *
30962306a36Sopenharmony_ci ******************************************************************************/
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ciacpi_status acpi_hw_validate_io_block(u64 address, u32 bit_width, u32 count)
31262306a36Sopenharmony_ci{
31362306a36Sopenharmony_ci	acpi_status status;
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	while (count--) {
31662306a36Sopenharmony_ci		status = acpi_hw_validate_io_request((acpi_io_address)address,
31762306a36Sopenharmony_ci						     bit_width);
31862306a36Sopenharmony_ci		if (ACPI_FAILURE(status))
31962306a36Sopenharmony_ci			return_ACPI_STATUS(status);
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci		address += ACPI_DIV_8(bit_width);
32262306a36Sopenharmony_ci	}
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci	return_ACPI_STATUS(AE_OK);
32562306a36Sopenharmony_ci}
326