// SPDX-License-Identifier: GPL-2.0 /* * Generation of tables for particular device types * * Copyright 2019 Google LLC * Mostly taken from coreboot file of the same name */ #include #include #include #include #include #include #include /** * acpi_device_path_fill() - Find the root device and build a path from there * * This recursively reaches back to the root device and progressively adds path * elements until the device is reached. * * @dev: Device to return path of * @buf: Buffer to hold the path * @buf_len: Length of buffer * @cur: Current position in the buffer * @return new position in buffer after adding @dev, or -ve on error */ static int acpi_device_path_fill(const struct udevice *dev, char *buf, size_t buf_len, int cur) { char name[ACPI_NAME_MAX]; int next = 0; int ret; ret = acpi_get_name(dev, name); if (ret) return ret; /* * Make sure this name segment will fit, including the path segment * separator and possible NULL terminator, if this is the last segment. */ if (cur + strlen(name) + 2 > buf_len) return -ENOSPC; /* Walk up the tree to the root device */ if (dev_get_parent(dev)) { next = acpi_device_path_fill(dev_get_parent(dev), buf, buf_len, cur); if (next < 0) return next; } /* Fill in the path from the root device */ next += snprintf(buf + next, buf_len - next, "%s%s", dev_get_parent(dev) && *name ? "." : "", name); return next; } int acpi_device_path(const struct udevice *dev, char *buf, int maxlen) { int ret; ret = acpi_device_path_fill(dev, buf, maxlen, 0); if (ret < 0) return ret; return 0; } int acpi_device_scope(const struct udevice *dev, char *scope, int maxlen) { int ret; if (!dev_get_parent(dev)) return log_msg_ret("noparent", -EINVAL); ret = acpi_device_path_fill(dev_get_parent(dev), scope, maxlen, 0); if (ret < 0) return log_msg_ret("fill", ret); return 0; } enum acpi_dev_status acpi_device_status(const struct udevice *dev) { return ACPI_DSTATUS_ALL_ON; } /** * largeres_write_len_f() - Write a placeholder word value * * Write a forward length for a large resource (2 bytes) * * @return pointer to the zero word (for fixing up later) */ static void *largeres_write_len_f(struct acpi_ctx *ctx) { u8 *p = acpigen_get_current(ctx); acpigen_emit_word(ctx, 0); return p; } /** * largeres_fill_from_len() - Fill in a length value * * This calculated the number of bytes since the provided @start and writes it * to @ptr, which was previous returned by largeres_write_len_f(). * * @ptr: Word to update * @start: Start address to count from to calculated the length */ static void largeres_fill_from_len(struct acpi_ctx *ctx, char *ptr, u8 *start) { u16 len = acpigen_get_current(ctx) - start; ptr[0] = len & 0xff; ptr[1] = (len >> 8) & 0xff; } /** * largeres_fill_len() - Fill in a length value, excluding the length itself * * Fill in the length field with the value calculated from after the 16bit * field to acpigen current. This is useful since the length value does not * include the length field itself. * * This calls acpi_device_largeres_fill_len() passing @ptr + 2 as @start * * @ptr: Word to update. */ static void largeres_fill_len(struct acpi_ctx *ctx, void *ptr) { largeres_fill_from_len(ctx, ptr, ptr + sizeof(u16)); } /* ACPI 6.3 section 6.4.3.6: Extended Interrupt Descriptor */ static int acpi_device_write_interrupt(struct acpi_ctx *ctx, const struct acpi_irq *irq) { void *desc_length; u8 flags; if (!irq->pin) return -ENOENT; /* This is supported by GpioInt() but not Interrupt() */ if (irq->polarity == ACPI_IRQ_ACTIVE_BOTH) return -EINVAL; /* Byte 0: Descriptor Type */ acpigen_emit_byte(ctx, ACPI_DESCRIPTOR_INTERRUPT); /* Byte 1-2: Length (filled in later) */ desc_length = largeres_write_len_f(ctx); /* * Byte 3: Flags * [7:5]: Reserved * [4]: Wake (0=NO_WAKE 1=WAKE) * [3]: Sharing (0=EXCLUSIVE 1=SHARED) * [2]: Polarity (0=HIGH 1=LOW) * [1]: Mode (0=LEVEL 1=EDGE) * [0]: Resource (0=PRODUCER 1=CONSUMER) */ flags = BIT(0); /* ResourceConsumer */ if (irq->mode == ACPI_IRQ_EDGE_TRIGGERED) flags |= BIT(1); if (irq->polarity == ACPI_IRQ_ACTIVE_LOW) flags |= BIT(2); if (irq->shared == ACPI_IRQ_SHARED) flags |= BIT(3); if (irq->wake == ACPI_IRQ_WAKE) flags |= BIT(4); acpigen_emit_byte(ctx, flags); /* Byte 4: Interrupt Table Entry Count */ acpigen_emit_byte(ctx, 1); /* Byte 5-8: Interrupt Number */ acpigen_emit_dword(ctx, irq->pin); /* Fill in Descriptor Length (account for len word) */ largeres_fill_len(ctx, desc_length); return 0; } int acpi_device_write_interrupt_irq(struct acpi_ctx *ctx, const struct irq *req_irq) { struct acpi_irq irq; int ret; ret = irq_get_acpi(req_irq, &irq); if (ret) return log_msg_ret("get", ret); ret = acpi_device_write_interrupt(ctx, &irq); if (ret) return log_msg_ret("write", ret); return 0; }