ssdt.c
1 /* SPDX-License-Identifier: GPL-2.0-or-later */ 2 3 #include <superio/common/ssdt.h> 4 5 #include <device/device.h> 6 #include <device/pnp.h> 7 #include <acpi/acpigen.h> 8 #include <acpi/acpi.h> 9 #include <console/console.h> 10 #include <types.h> 11 #include <stdio.h> 12 #include <string.h> 13 14 struct superio_dev { 15 const char *acpi_hid; 16 u16 io_base[4]; 17 u8 irq[2]; 18 }; 19 20 static const struct superio_dev superio_devs[] = { 21 {ACPI_HID_FDC, {0x3f0, 0x3f2, 0x3f7}, {6, } }, 22 {ACPI_HID_KEYBOARD, {60, 64, }, {1, } }, 23 {ACPI_HID_MOUSE, {60, 64, }, {12, } }, 24 {ACPI_HID_COM, {0x3f8, 0x2f8, 0x3e8, 0x2e8}, {4, 3} }, 25 {ACPI_HID_LPT, {0x378, }, {7, } }, 26 }; 27 28 static const u8 io_idx[] = {PNP_IDX_IO0, PNP_IDX_IO1, PNP_IDX_IO2, PNP_IDX_IO3}; 29 static const u8 irq_idx[] = {PNP_IDX_IRQ0, PNP_IDX_IRQ1}; 30 31 static const struct superio_dev *superio_guess_function(const struct device *dev) 32 { 33 for (size_t i = 0; i < ARRAY_SIZE(io_idx); i++) { 34 struct resource *res = probe_resource(dev, io_idx[i]); 35 if (!res || !res->base) 36 continue; 37 38 for (size_t j = 0; j < ARRAY_SIZE(superio_devs); j++) { 39 for (size_t k = 0; k < 4; k++) { 40 if (!superio_devs[j].io_base[k]) 41 continue; 42 if (superio_devs[j].io_base[k] == res->base) 43 return &superio_devs[j]; 44 } 45 } 46 } 47 for (size_t i = 0; i < ARRAY_SIZE(irq_idx); i++) { 48 struct resource *res = probe_resource(dev, irq_idx[i]); 49 if (!res || !res->size) 50 continue; 51 for (size_t j = 0; j < ARRAY_SIZE(superio_devs); j++) { 52 for (size_t k = 0; k < 2; k++) { 53 if (!superio_devs[j].irq[k]) 54 continue; 55 if (superio_devs[j].irq[k] == res->base) 56 return &superio_devs[j]; 57 } 58 } 59 } 60 return NULL; 61 } 62 63 /* Return true if there are resources to report */ 64 static bool has_resources(const struct device *dev) 65 { 66 for (size_t i = 0; i < ARRAY_SIZE(io_idx); i++) { 67 struct resource *res = probe_resource(dev, io_idx[i]); 68 if (!res || !res->base || !res->size) 69 continue; 70 return 1; 71 } 72 for (size_t i = 0; i < ARRAY_SIZE(irq_idx); i++) { 73 struct resource *res = probe_resource(dev, irq_idx[i]); 74 if (!res || !res->size || res->base > 16) 75 continue; 76 return 1; 77 } 78 return 0; 79 } 80 81 /* Add IO and IRQ resources for _CRS or _PRS */ 82 static void ldn_gen_resources(const struct device *dev) 83 { 84 uint16_t irq = 0; 85 for (size_t i = 0; i < ARRAY_SIZE(io_idx); i++) { 86 struct resource *res = probe_resource(dev, io_idx[i]); 87 if (!res || !res->base) 88 continue; 89 resource_t base = res->base; 90 resource_t size = res->size; 91 while (size > 0) { 92 resource_t sz = size > 255 ? 255 : size; 93 /* TODO: Needs test with regions >= 256 bytes */ 94 acpigen_write_io16(base, base, 1, sz, 1); 95 size -= sz; 96 base += sz; 97 } 98 } 99 for (size_t i = 0; i < ARRAY_SIZE(irq_idx); i++) { 100 struct resource *res = probe_resource(dev, irq_idx[i]); 101 if (!res || !res->size || res->base >= 16) 102 continue; 103 irq |= 1 << res->base; 104 } 105 if (irq) 106 acpigen_write_irq(irq); 107 } 108 109 /* Add resource base and size for additional SuperIO code */ 110 static void ldn_gen_resources_use(const struct device *dev) 111 { 112 char name[5]; 113 for (size_t i = 0; i < ARRAY_SIZE(io_idx); i++) { 114 struct resource *res = probe_resource(dev, io_idx[i]); 115 if (!res || !res->base || !res->size) 116 continue; 117 118 snprintf(name, sizeof(name), "IO%zXB", i); 119 name[4] = '\0'; 120 acpigen_write_name_integer(name, res->base); 121 122 snprintf(name, sizeof(name), "IO%zXS", i); 123 name[4] = '\0'; 124 acpigen_write_name_integer(name, res->size); 125 } 126 } 127 128 const char *superio_common_ldn_acpi_name(const struct device *dev) 129 { 130 u8 ldn = dev->path.pnp.device & 0xff; 131 u8 vldn = (dev->path.pnp.device >> 8) & 0x7; 132 static char name[5]; 133 134 snprintf(name, sizeof(name), "L%02X%01X", ldn, vldn); 135 136 name[4] = '\0'; 137 138 return name; 139 } 140 141 static const char *name_from_hid(const char *hid) 142 { 143 static const struct { 144 const char *hid; 145 const char *name; 146 } lookup[] = { 147 {ACPI_HID_FDC, "FDC" }, 148 {ACPI_HID_KEYBOARD, "PS2 Keyboard" }, 149 {ACPI_HID_MOUSE, "PS2 Mouse"}, 150 {ACPI_HID_COM, "COM port" }, 151 {ACPI_HID_LPT, "LPT" }, 152 {ACPI_HID_PNP, "Generic PNP device" }, 153 }; 154 155 for (size_t i = 0; hid && i < ARRAY_SIZE(lookup); i++) { 156 if (strcmp(hid, lookup[i].hid) == 0) 157 return lookup[i].name; 158 } 159 return "Generic device"; 160 } 161 162 void superio_common_fill_ssdt_generator(const struct device *dev) 163 { 164 if (!dev || !dev->upstream || !dev->upstream->dev) { 165 printk(BIOS_CRIT, "BUG: Invalid argument in %s!\n", __func__); 166 return; 167 } 168 169 const char *scope = acpi_device_scope(dev); 170 const char *name = acpi_device_name(dev); 171 const u8 ldn = dev->path.pnp.device & 0xff; 172 const u8 vldn = (dev->path.pnp.device >> 8) & 0x7; 173 const char *hid; 174 175 /* Validate devicetree settings */ 176 bool bug = false; 177 if (dev->upstream->dev->path.type != DEVICE_PATH_PNP) { 178 bug = true; 179 printk(BIOS_CRIT, "BUG: Parent of device %s is not a PNP device\n", 180 dev_path(dev)); 181 } else if (dev->upstream->dev->path.pnp.port != dev->path.pnp.port) { 182 bug = true; 183 printk(BIOS_CRIT, "BUG: Parent of device %s has wrong I/O port\n", 184 dev_path(dev)); 185 } 186 if (bug) { 187 printk(BIOS_CRIT, "BUG: Check your devicetree!\n"); 188 return; 189 } 190 191 if (!scope || !name) { 192 printk(BIOS_ERR, "%s: Missing ACPI path/scope\n", dev_path(dev)); 193 return; 194 } 195 if (vldn) { 196 printk(BIOS_DEBUG, "%s: Ignoring virtual LDN\n", dev_path(dev)); 197 return; 198 } 199 200 printk(BIOS_DEBUG, "%s.%s: %s\n", scope, name, dev_path(dev)); 201 202 /* Scope */ 203 acpigen_write_scope(scope); 204 205 /* Device */ 206 acpigen_write_device(name); 207 208 acpi_device_write_uid(dev); 209 210 acpigen_write_name_byte("LDN", ldn); 211 acpigen_write_name_byte("VLDN", vldn); 212 213 acpigen_write_method("_STA", 0); 214 { 215 acpigen_write_store(); 216 acpigen_emit_namestring("^^QLDN"); 217 acpigen_write_integer(ldn); 218 acpigen_emit_byte(LOCAL0_OP); 219 220 /* Multiply (Local0, 0xf, Local0) */ 221 acpigen_emit_byte(MULTIPLY_OP); 222 acpigen_emit_byte(LOCAL0_OP); 223 acpigen_write_integer(0xf); 224 acpigen_emit_byte(LOCAL0_OP); 225 226 acpigen_emit_byte(RETURN_OP); 227 acpigen_emit_byte(LOCAL0_OP); 228 } 229 acpigen_pop_len(); /* Method */ 230 231 /* 232 * The ACPI6.2 spec Chapter 6.1.5 requires to set a _HID if no _ADR 233 * is present. Tests on Windows 10 showed that this is also true for 234 * disabled (_STA = 0) devices, otherwise it BSODs. 235 */ 236 237 hid = acpi_device_hid(dev); 238 if (!hid) { 239 printk(BIOS_ERR, "%s: SuperIO driver doesn't provide a _HID\n", dev_path(dev)); 240 /* Try to guess it... */ 241 const struct superio_dev *sdev = superio_guess_function(dev); 242 if (sdev && sdev->acpi_hid) { 243 hid = sdev->acpi_hid; 244 printk(BIOS_WARNING, "%s: Guessed _HID is '%s'\n", dev_path(dev), hid); 245 } else { 246 hid = ACPI_HID_PNP; 247 printk(BIOS_ERR, "%s: Failed to guessed _HID\n", dev_path(dev)); 248 } 249 } 250 251 acpigen_write_name_string("_HID", hid); 252 acpigen_write_name_string("_DDN", name_from_hid(hid)); 253 254 acpigen_write_method("_DIS", 0); 255 { 256 acpigen_emit_namestring("^^DLDN"); 257 acpigen_write_integer(ldn); 258 } 259 acpigen_pop_len(); /* Method */ 260 261 if (dev->enabled && has_resources(dev)) { 262 /* Resources - _CRS */ 263 acpigen_write_name("_CRS"); 264 acpigen_write_resourcetemplate_header(); 265 ldn_gen_resources(dev); 266 acpigen_write_resourcetemplate_footer(); 267 268 /* Resources - _PRS */ 269 acpigen_write_name("_PRS"); 270 acpigen_write_resourcetemplate_header(); 271 ldn_gen_resources(dev); 272 acpigen_write_resourcetemplate_footer(); 273 274 /* Resources base and size for 3rd party ACPI code */ 275 ldn_gen_resources_use(dev); 276 } 277 278 acpigen_pop_len(); /* Device */ 279 acpigen_pop_len(); /* Scope */ 280 }