| @@ -0,0 +1,763 @@ |
| | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
| | 2 | /* |
| | 3 | * Asus Armoury (WMI) attributes driver. |
| | 4 | * |
| | 5 | * This driver uses the fw_attributes class to expose various WMI functions |
| | 6 | * that are present in many gaming and some non-gaming ASUS laptops. |
| | 7 | * |
| | 8 | * These typically don't fit anywhere else in the sysfs such as under LED class, |
| | 9 | * hwmon or others, and are set in Windows using the ASUS Armoury Crate tool. |
| | 10 | * |
| | 11 | * Copyright(C) 2024 Luke Jones <luke@ljones.dev> |
| | 12 | */ |
| | 13 | |
| | 14 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| | 15 | |
| | 16 | #include <linux/acpi.h> |
| | 17 | #include <linux/array_size.h> |
| | 18 | #include <linux/bitfield.h> |
| | 19 | #include <linux/device.h> |
| | 20 | #include <linux/dmi.h> |
| | 21 | #include <linux/err.h> |
| | 22 | #include <linux/errno.h> |
| | 23 | #include <linux/fs.h> |
| | 24 | #include <linux/kernel.h> |
| | 25 | #include <linux/kmod.h> |
| | 26 | #include <linux/kobject.h> |
| | 27 | #include <linux/kstrtox.h> |
| | 28 | #include <linux/module.h> |
| | 29 | #include <linux/mutex.h> |
| | 30 | #include <linux/pci.h> |
| | 31 | #include <linux/platform_data/x86/asus-wmi.h> |
| | 32 | #include <linux/printk.h> |
| | 33 | #include <linux/sysfs.h> |
| | 34 | |
| | 35 | #include "asus-armoury.h" |
| | 36 | #include "firmware_attributes_class.h" |
| | 37 | |
| | 38 | #define ASUS_NB_WMI_EVENT_GUID "0B3CBB35-E3C2-45ED-91C2-4C5A6D195D1C" |
| | 39 | |
| | 40 | #define ASUS_MINI_LED_MODE_MASK GENMASK(1, 0) |
| | 41 | /* Standard modes for devices with only on/off */ |
| | 42 | #define ASUS_MINI_LED_OFF 0x00 |
| | 43 | #define ASUS_MINI_LED_ON 0x01 |
| | 44 | /* Like "on" but the effect is more vibrant or brighter */ |
| | 45 | #define ASUS_MINI_LED_STRONG_MODE 0x02 |
| | 46 | /* New modes for devices with 3 mini-led mode types */ |
| | 47 | #define ASUS_MINI_LED_2024_WEAK 0x00 |
| | 48 | #define ASUS_MINI_LED_2024_STRONG 0x01 |
| | 49 | #define ASUS_MINI_LED_2024_OFF 0x02 |
| | 50 | |
| | 51 | struct asus_armoury_priv { |
| | 52 | struct device *fw_attr_dev; |
| | 53 | struct kset *fw_attr_kset; |
| | 54 | |
| | 55 | /* |
| | 56 | * Mutex to protect eGPU activation/deactivation |
| | 57 | * sequences and dGPU connection status: |
| | 58 | * do not allow concurrent changes or changes |
| | 59 | * before a reboot if dGPU got disabled. |
| | 60 | */ |
| | 61 | struct mutex egpu_mutex; |
| | 62 | |
| | 63 | u32 mini_led_dev_id; |
| | 64 | u32 gpu_mux_dev_id; |
| | 65 | }; |
| | 66 | |
| | 67 | static struct asus_armoury_priv asus_armoury = { |
| | 68 | .egpu_mutex = __MUTEX_INITIALIZER(asus_armoury.egpu_mutex), |
| | 69 | }; |
| | 70 | |
| | 71 | struct fw_attrs_group { |
| | 72 | bool pending_reboot; |
| | 73 | }; |
| | 74 | |
| | 75 | static struct fw_attrs_group fw_attrs = { |
| | 76 | .pending_reboot = false, |
| | 77 | }; |
| | 78 | |
| | 79 | struct asus_attr_group { |
| | 80 | const struct attribute_group *attr_group; |
| | 81 | u32 wmi_devid; |
| | 82 | }; |
| | 83 | |
| | 84 | static void asus_set_reboot_and_signal_event(void) |
| | 85 | { |
| | 86 | fw_attrs.pending_reboot = true; |
| | 87 | kobject_uevent(&asus_armoury.fw_attr_dev->kobj, KOBJ_CHANGE); |
| | 88 | } |
| | 89 | |
| | 90 | static ssize_t pending_reboot_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) |
| | 91 | { |
| | 92 | return sysfs_emit(buf, "%d\n", fw_attrs.pending_reboot); |
| | 93 | } |
| | 94 | |
| | 95 | static struct kobj_attribute pending_reboot = __ATTR_RO(pending_reboot); |
| | 96 | |
| | 97 | static bool asus_bios_requires_reboot(struct kobj_attribute *attr) |
| | 98 | { |
| | 99 | return !strcmp(attr->attr.name, "gpu_mux_mode"); |
| | 100 | } |
| | 101 | |
| | 102 | /** |
| | 103 | * armoury_has_devstate() - Check presence of the WMI function state. |
| | 104 | * |
| | 105 | * @dev_id: The WMI method ID to check for presence. |
| | 106 | * |
| | 107 | * Returns: true iif method is supported. |
| | 108 | */ |
| | 109 | static bool armoury_has_devstate(u32 dev_id) |
| | 110 | { |
| | 111 | u32 retval; |
| | 112 | int status; |
| | 113 | |
| | 114 | status = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, dev_id, 0, &retval); |
| | 115 | pr_debug("%s called (0x%08x), retval: 0x%08x\n", __func__, dev_id, retval); |
| | 116 | |
| | 117 | return status == 0 && (retval & ASUS_WMI_DSTS_PRESENCE_BIT); |
| | 118 | } |
| | 119 | |
| | 120 | /** |
| | 121 | * armoury_get_devstate() - Get the WMI function state. |
| | 122 | * @attr: NULL or the kobj_attribute associated to called WMI function. |
| | 123 | * @dev_id: The WMI method ID to call. |
| | 124 | * @retval: |
| | 125 | * * non-NULL pointer to where to store the value returned from WMI |
| | 126 | * * with the function presence bit cleared. |
| | 127 | * |
| | 128 | * Intended usage is from sysfs attribute checking associated WMI function. |
| | 129 | * |
| | 130 | * Returns: |
| | 131 | * * %-ENODEV - method ID is unsupported. |
| | 132 | * * %0 - successful and retval is filled. |
| | 133 | * * %other - error from WMI call. |
| | 134 | */ |
| | 135 | static int armoury_get_devstate(struct kobj_attribute *attr, u32 *retval, u32 dev_id) |
| | 136 | { |
| | 137 | int err; |
| | 138 | |
| | 139 | err = asus_wmi_get_devstate_dsts(dev_id, retval); |
| | 140 | if (err) { |
| | 141 | if (attr) |
| | 142 | pr_err("Failed to get %s: %d\n", attr->attr.name, err); |
| | 143 | else |
| | 144 | pr_err("Failed to get devstate for 0x%x: %d\n", dev_id, err); |
| | 145 | |
| | 146 | return err; |
| | 147 | } |
| | 148 | |
| | 149 | /* |
| | 150 | * asus_wmi_get_devstate_dsts will populate retval with WMI return, but |
| | 151 | * the true value is expressed when ASUS_WMI_DSTS_PRESENCE_BIT is clear. |
| | 152 | */ |
| | 153 | *retval &= ~ASUS_WMI_DSTS_PRESENCE_BIT; |
| | 154 | |
| | 155 | return 0; |
| | 156 | } |
| | 157 | |
| | 158 | /** |
| | 159 | * armoury_set_devstate() - Set the WMI function state. |
| | 160 | * @attr: The kobj_attribute associated to called WMI function. |
| | 161 | * @dev_id: The WMI method ID to call. |
| | 162 | * @value: The new value to be set. |
| | 163 | * @retval: Where to store the value returned from WMI or NULL. |
| | 164 | * |
| | 165 | * Intended usage is from sysfs attribute setting associated WMI function. |
| | 166 | * Before calling the presence of the function should be checked. |
| | 167 | * |
| | 168 | * Every WMI write MUST go through this function to enforce safety checks. |
| | 169 | * |
| | 170 | * Results !1 is usually considered a fail by ASUS, but some WMI methods |
| | 171 | * (like eGPU or CPU cores) do use > 1 to return a status code or similar: |
| | 172 | * in these cases caller is interested in the actual return value |
| | 173 | * and should perform relevant checks. |
| | 174 | * |
| | 175 | * Returns: |
| | 176 | * * %-EIO - WMI function returned an error. |
| | 177 | * * %0 - successful and retval is filled. |
| | 178 | * * %other - error from WMI call. |
| | 179 | */ |
| | 180 | static int armoury_set_devstate(struct kobj_attribute *attr, |
| | 181 | u32 value, u32 *retval, u32 dev_id) |
| | 182 | { |
| | 183 | u32 result; |
| | 184 | int err; |
| | 185 | |
| | 186 | err = asus_wmi_set_devstate(dev_id, value, retval ? retval : &result); |
| | 187 | if (err) { |
| | 188 | if (attr) |
| | 189 | pr_err("Failed to set %s: %d\n", attr->attr.name, err); |
| | 190 | else |
| | 191 | pr_err("Failed to set devstate for 0x%x: %d\n", dev_id, err); |
| | 192 | |
| | 193 | return err; |
| | 194 | } |
| | 195 | |
| | 196 | /* |
| | 197 | * If retval == NULL caller is uninterested in return value: |
| | 198 | * perform the most common result check here. |
| | 199 | */ |
| | 200 | if ((retval == NULL) && (result == 0)) { |
| | 201 | pr_err("Failed to set %s: (result): 0x%x\n", attr->attr.name, result); |
| | 202 | return -EIO; |
| | 203 | } |
| | 204 | |
| | 205 | return 0; |
| | 206 | } |
| | 207 | |
| | 208 | static int armoury_attr_enum_list(char *buf, size_t enum_values) |
| | 209 | { |
| | 210 | size_t i; |
| | 211 | int len = 0; |
| | 212 | |
| | 213 | for (i = 0; i < enum_values; i++) { |
| | 214 | if (i == 0) |
| | 215 | len += sysfs_emit_at(buf, len, "%zu", i); |
| | 216 | else |
| | 217 | len += sysfs_emit_at(buf, len, ";%zu", i); |
| | 218 | } |
| | 219 | len += sysfs_emit_at(buf, len, "\n"); |
| | 220 | |
| | 221 | return len; |
| | 222 | } |
| | 223 | |
| | 224 | ssize_t armoury_attr_uint_store(struct kobject *kobj, struct kobj_attribute *attr, |
| | 225 | const char *buf, size_t count, u32 min, u32 max, |
| | 226 | u32 *store_value, u32 wmi_dev) |
| | 227 | { |
| | 228 | u32 value; |
| | 229 | int err; |
| | 230 | |
| | 231 | err = kstrtou32(buf, 10, &value); |
| | 232 | if (err) |
| | 233 | return err; |
| | 234 | |
| | 235 | if (value < min || value > max) |
| | 236 | return -EINVAL; |
| | 237 | |
| | 238 | err = armoury_set_devstate(attr, value, NULL, wmi_dev); |
| | 239 | if (err) |
| | 240 | return err; |
| | 241 | |
| | 242 | if (store_value != NULL) |
| | 243 | *store_value = value; |
| | 244 | sysfs_notify(kobj, NULL, attr->attr.name); |
| | 245 | |
| | 246 | if (asus_bios_requires_reboot(attr)) |
| | 247 | asus_set_reboot_and_signal_event(); |
| | 248 | |
| | 249 | return count; |
| | 250 | } |
| | 251 | |
| | 252 | ssize_t armoury_attr_uint_show(struct kobject *kobj, struct kobj_attribute *attr, |
| | 253 | char *buf, u32 wmi_dev) |
| | 254 | { |
| | 255 | u32 result; |
| | 256 | int err; |
| | 257 | |
| | 258 | err = armoury_get_devstate(attr, &result, wmi_dev); |
| | 259 | if (err) |
| | 260 | return err; |
| | 261 | |
| | 262 | return sysfs_emit(buf, "%u\n", result); |
| | 263 | } |
| | 264 | |
| | 265 | static ssize_t enum_type_show(struct kobject *kobj, struct kobj_attribute *attr, |
| | 266 | char *buf) |
| | 267 | { |
| | 268 | return sysfs_emit(buf, "enumeration\n"); |
| | 269 | } |
| | 270 | |
| | 271 | /* Mini-LED mode **************************************************************/ |
| | 272 | |
| | 273 | /* Values map for mini-led modes on 2023 and earlier models. */ |
| | 274 | static u32 mini_led_mode1_map[] = { |
| | 275 | [0] = ASUS_MINI_LED_OFF, |
| | 276 | [1] = ASUS_MINI_LED_ON, |
| | 277 | }; |
| | 278 | |
| | 279 | /* Values map for mini-led modes on 2024 and later models. */ |
| | 280 | static u32 mini_led_mode2_map[] = { |
| | 281 | [0] = ASUS_MINI_LED_2024_OFF, |
| | 282 | [1] = ASUS_MINI_LED_2024_WEAK, |
| | 283 | [2] = ASUS_MINI_LED_2024_STRONG, |
| | 284 | }; |
| | 285 | |
| | 286 | static ssize_t mini_led_mode_current_value_show(struct kobject *kobj, |
| | 287 | struct kobj_attribute *attr, char *buf) |
| | 288 | { |
| | 289 | u32 *mini_led_mode_map; |
| | 290 | size_t mini_led_mode_map_size; |
| | 291 | u32 i, mode; |
| | 292 | int err; |
| | 293 | |
| | 294 | switch (asus_armoury.mini_led_dev_id) { |
| | 295 | case ASUS_WMI_DEVID_MINI_LED_MODE: |
| | 296 | mini_led_mode_map = mini_led_mode1_map; |
| | 297 | mini_led_mode_map_size = ARRAY_SIZE(mini_led_mode1_map); |
| | 298 | break; |
| | 299 | |
| | 300 | case ASUS_WMI_DEVID_MINI_LED_MODE2: |
| | 301 | mini_led_mode_map = mini_led_mode2_map; |
| | 302 | mini_led_mode_map_size = ARRAY_SIZE(mini_led_mode2_map); |
| | 303 | break; |
| | 304 | |
| | 305 | default: |
| | 306 | pr_err("Unrecognized mini-LED device: %u\n", asus_armoury.mini_led_dev_id); |
| | 307 | return -ENODEV; |
| | 308 | } |
| | 309 | |
| | 310 | err = armoury_get_devstate(attr, &mode, asus_armoury.mini_led_dev_id); |
| | 311 | if (err) |
| | 312 | return err; |
| | 313 | |
| | 314 | mode = FIELD_GET(ASUS_MINI_LED_MODE_MASK, 0); |
| | 315 | |
| | 316 | for (i = 0; i < mini_led_mode_map_size; i++) |
| | 317 | return sysfs_emit(buf, "%u\n", mini_led_mode_map[i]); |
| | 318 | |
| | 319 | pr_warn("Unrecognized mini-LED mode: %u", mode); |
| | 320 | return -EINVAL; |
| | 321 | } |
| | 322 | |
| | 323 | static ssize_t mini_led_mode_current_value_store(struct kobject *kobj, |
| | 324 | struct kobj_attribute *attr, |
| | 325 | const char *buf, size_t count) |
| | 326 | { |
| | 327 | u32 *mini_led_mode_map; |
| | 328 | size_t mini_led_mode_map_size; |
| | 329 | u32 mode; |
| | 330 | int err; |
| | 331 | |
| | 332 | err = kstrtou32(buf, 10, &mode); |
| | 333 | if (err) |
| | 334 | return err; |
| | 335 | |
| | 336 | switch (asus_armoury.mini_led_dev_id) { |
| | 337 | case ASUS_WMI_DEVID_MINI_LED_MODE: |
| | 338 | mini_led_mode_map = mini_led_mode1_map; |
| | 339 | mini_led_mode_map_size = ARRAY_SIZE(mini_led_mode1_map); |
| | 340 | break; |
| | 341 | |
| | 342 | case ASUS_WMI_DEVID_MINI_LED_MODE2: |
| | 343 | mini_led_mode_map = mini_led_mode2_map; |
| | 344 | mini_led_mode_map_size = ARRAY_SIZE(mini_led_mode2_map); |
| | 345 | break; |
| | 346 | |
| | 347 | default: |
| | 348 | pr_err("Unrecognized mini-LED devid: %u\n", asus_armoury.mini_led_dev_id); |
| | 349 | return -EINVAL; |
| | 350 | } |
| | 351 | |
| | 352 | if (mode >= mini_led_mode_map_size) { |
| | 353 | return pr_warn("mini-LED mode unrecognized device: %u\n", mode); |
| | 354 | return -ENODEV; |
| | 355 | } |
| | 356 | |
| | 357 | return armoury_attr_uint_store(kobj, attr, buf, count, |
| | 358 | 0, mini_led_mode_map[mode], |
| | 359 | NULL, asus_armoury.mini_led_dev_id); |
| | 360 | } |
| | 361 | |
| | 362 | static ssize_t mini_led_mode_possible_values_show(struct kobject *kobj, |
| | 363 | struct kobj_attribute *attr, char *buf) |
| | 364 | { |
| | 365 | switch (asus_armoury.mini_led_dev_id) { |
| | 366 | case ASUS_WMI_DEVID_MINI_LED_MODE: |
| | 367 | return armoury_attr_enum_list(buf, ARRAY_SIZE(mini_led_mode1_map)); |
| | 368 | case ASUS_WMI_DEVID_MINI_LED_MODE2: |
| | 369 | return armoury_attr_enum_list(buf, ARRAY_SIZE(mini_led_mode2_map)); |
| | 370 | default: |
| | 371 | return -ENODEV; |
| | 372 | } |
| | 373 | } |
| | 374 | ASUS_ATTR_GROUP_ENUM(mini_led_mode, "mini_led_mode", "Set the mini-LED backlight mode"); |
| | 375 | |
| | 376 | static ssize_t gpu_mux_mode_current_value_store(struct kobject *kobj, |
| | 377 | struct kobj_attribute *attr, |
| | 378 | const char *buf, size_t count) |
| | 379 | { |
| | 380 | int result, err; |
| | 381 | bool optimus; |
| | 382 | |
| | 383 | err = kstrtobool(buf, &optimus); |
| | 384 | if (err) |
| | 385 | return err; |
| | 386 | |
| | 387 | if (armoury_has_devstate(ASUS_WMI_DEVID_DGPU)) { |
| | 388 | err = armoury_get_devstate(NULL, &result, ASUS_WMI_DEVID_DGPU); |
| | 389 | if (err) |
| | 390 | return err; |
| | 391 | if (result && !optimus) { |
| | 392 | pr_warn("Cannot switch MUX to dGPU mode when dGPU is disabled: %02X\n", |
| | 393 | result); |
| | 394 | return -ENODEV; |
| | 395 | } |
| | 396 | } |
| | 397 | |
| | 398 | if (armoury_has_devstate(ASUS_WMI_DEVID_EGPU)) { |
| | 399 | err = armoury_get_devstate(NULL, &result, ASUS_WMI_DEVID_EGPU); |
| | 400 | if (err) |
| | 401 | return err; |
| | 402 | if (result && !optimus) { |
| | 403 | pr_warn("Cannot switch MUX to dGPU mode when eGPU is enabled\n"); |
| | 404 | return -EBUSY; |
| | 405 | } |
| | 406 | } |
| | 407 | |
| | 408 | err = armoury_set_devstate(attr, optimus ? 1 : 0, NULL, asus_armoury.gpu_mux_dev_id); |
| | 409 | if (err) |
| | 410 | return err; |
| | 411 | |
| | 412 | sysfs_notify(kobj, NULL, attr->attr.name); |
| | 413 | asus_set_reboot_and_signal_event(); |
| | 414 | |
| | 415 | return count; |
| | 416 | } |
| | 417 | ASUS_WMI_SHOW_INT(gpu_mux_mode_current_value, asus_armoury.gpu_mux_dev_id); |
| | 418 | ASUS_ATTR_GROUP_BOOL(gpu_mux_mode, "gpu_mux_mode", "Set the GPU display MUX mode"); |
| | 419 | |
| | 420 | static ssize_t dgpu_disable_current_value_store(struct kobject *kobj, |
| | 421 | struct kobj_attribute *attr, const char *buf, |
| | 422 | size_t count) |
| | 423 | { |
| | 424 | int result, err; |
| | 425 | bool disable; |
| | 426 | |
| | 427 | err = kstrtobool(buf, &disable); |
| | 428 | if (err) |
| | 429 | return err; |
| | 430 | |
| | 431 | if (asus_armoury.gpu_mux_dev_id) { |
| | 432 | err = armoury_get_devstate(NULL, &result, asus_armoury.gpu_mux_dev_id); |
| | 433 | if (err) |
| | 434 | return err; |
| | 435 | if (!result && disable) { |
| | 436 | pr_warn("Cannot disable dGPU when the MUX is in dGPU mode\n"); |
| | 437 | return -EBUSY; |
| | 438 | } |
| | 439 | } |
| | 440 | |
| | 441 | scoped_guard(mutex, &asus_armoury.egpu_mutex) { |
| | 442 | err = armoury_set_devstate(attr, disable ? 1 : 0, NULL, ASUS_WMI_DEVID_DGPU); |
| | 443 | if (err) |
| | 444 | return err; |
| | 445 | } |
| | 446 | |
| | 447 | sysfs_notify(kobj, NULL, attr->attr.name); |
| | 448 | |
| | 449 | return count; |
| | 450 | } |
| | 451 | ASUS_WMI_SHOW_INT(dgpu_disable_current_value, ASUS_WMI_DEVID_DGPU); |
| | 452 | ASUS_ATTR_GROUP_BOOL(dgpu_disable, "dgpu_disable", "Disable the dGPU"); |
| | 453 | |
| | 454 | /* Values map for eGPU activation requests. */ |
| | 455 | static u32 egpu_status_map[] = { |
| | 456 | [0] = 0x00000000U, |
| | 457 | [1] = 0x00000001U, |
| | 458 | [2] = 0x00000101U, |
| | 459 | [3] = 0x00000201U, |
| | 460 | }; |
| | 461 | |
| | 462 | /* |
| | 463 | * armoury_pci_rescan() - Performs a PCI rescan |
| | 464 | * |
| | 465 | * Bring up any GPU that has been hotplugged in the system. |
| | 466 | */ |
| | 467 | static void armoury_pci_rescan(void) |
| | 468 | { |
| | 469 | struct pci_bus *b = NULL; |
| | 470 | |
| | 471 | pci_lock_rescan_remove(); |
| | 472 | while ((b = pci_find_next_bus(b)) != NULL) |
| | 473 | pci_rescan_bus(b); |
| | 474 | pci_unlock_rescan_remove(); |
| | 475 | } |
| | 476 | |
| | 477 | /* |
| | 478 | * The ACPI call to enable the eGPU might also disable the internal dGPU, |
| | 479 | * but this is not always the case and on certain models enabling the eGPU |
| | 480 | * when the dGPU is either still active or has been disabled without rebooting |
| | 481 | * will make both GPUs malfunction and the kernel will detect many |
| | 482 | * PCI AER unrecoverable errors. |
| | 483 | */ |
| | 484 | static ssize_t egpu_enable_current_value_store(struct kobject *kobj, struct kobj_attribute *attr, |
| | 485 | const char *buf, size_t count) |
| | 486 | { |
| | 487 | int err; |
| | 488 | u32 requested, enable, result; |
| | 489 | |
| | 490 | err = kstrtou32(buf, 10, &requested); |
| | 491 | if (err) |
| | 492 | return err; |
| | 493 | |
| | 494 | if (requested >= ARRAY_SIZE(egpu_status_map)) |
| | 495 | return -EINVAL; |
| | 496 | enable = egpu_status_map[requested]; |
| | 497 | |
| | 498 | scoped_guard(mutex, &asus_armoury.egpu_mutex) { |
| | 499 | /* Ensure the eGPU is connected before attempting to activate it. */ |
| | 500 | if (enable) { |
| | 501 | err = armoury_get_devstate(NULL, &result, ASUS_WMI_DEVID_EGPU_CONNECTED); |
| | 502 | if (err) { |
| | 503 | pr_warn("Failed to get eGPU connection status: %d\n", err); |
| | 504 | return err; |
| | 505 | } |
| | 506 | if (!result) { |
| | 507 | pr_warn("Cannot activate eGPU while undetected\n"); |
| | 508 | return -ENOENT; |
| | 509 | } |
| | 510 | } |
| | 511 | |
| | 512 | if (asus_armoury.gpu_mux_dev_id) { |
| | 513 | err = armoury_get_devstate(NULL, &result, asus_armoury.gpu_mux_dev_id); |
| | 514 | if (err) |
| | 515 | return err; |
| | 516 | |
| | 517 | if (!result && enable) { |
| | 518 | pr_warn("Cannot enable eGPU when the MUX is in dGPU mode\n"); |
| | 519 | return -ENODEV; |
| | 520 | } |
| | 521 | } |
| | 522 | |
| | 523 | err = armoury_set_devstate(attr, enable, &result, ASUS_WMI_DEVID_EGPU); |
| | 524 | if (err) { |
| | 525 | pr_err("Failed to set %s: %d\n", attr->attr.name, err); |
| | 526 | return err; |
| | 527 | } |
| | 528 | |
| | 529 | /* |
| | 530 | * ACPI returns value 0x01 on success and 0x02 on a partial activation: |
| | 531 | * performing a pci rescan will bring up the device in pci-e 3.0 speed, |
| | 532 | * after a reboot the device will work at full speed. |
| | 533 | */ |
| | 534 | switch (result) { |
| | 535 | case 0x01: |
| | 536 | /* |
| | 537 | * When a GPU is in use it does not get disconnected even if |
| | 538 | * the ACPI call returns a success. |
| | 539 | */ |
| | 540 | if (!enable) { |
| | 541 | err = armoury_get_devstate(attr, &result, ASUS_WMI_DEVID_EGPU); |
| | 542 | if (err) { |
| | 543 | pr_warn("Failed to ensure eGPU is deactivated: %d\n", err); |
| | 544 | return err; |
| | 545 | } |
| | 546 | |
| | 547 | if (result != 0) |
| | 548 | return -EBUSY; |
| | 549 | } |
| | 550 | |
| | 551 | pr_debug("Success changing the eGPU status\n"); |
| | 552 | break; |
| | 553 | case 0x02: |
| | 554 | pr_info("Success changing the eGPU status, a reboot is strongly advised\n"); |
| | 555 | asus_set_reboot_and_signal_event(); |
| | 556 | break; |
| | 557 | default: |
| | 558 | pr_err("Failed to change the eGPU status: wmi result is 0x%x\n", result); |
| | 559 | return -EIO; |
| | 560 | } |
| | 561 | } |
| | 562 | |
| | 563 | /* |
| | 564 | * Perform a PCI rescan: on every tested model this is necessary |
| | 565 | * to make the eGPU visible on the bus without rebooting. |
| | 566 | */ |
| | 567 | armoury_pci_rescan(); |
| | 568 | |
| | 569 | sysfs_notify(kobj, NULL, attr->attr.name); |
| | 570 | |
| | 571 | return count; |
| | 572 | } |
| | 573 | |
| | 574 | static ssize_t egpu_enable_current_value_show(struct kobject *kobj, struct kobj_attribute *attr, |
| | 575 | char *buf) |
| | 576 | { |
| | 577 | int i, err; |
| | 578 | u32 status; |
| | 579 | |
| | 580 | scoped_guard(mutex, &asus_armoury.egpu_mutex) { |
| | 581 | err = armoury_get_devstate(attr, &status, ASUS_WMI_DEVID_EGPU); |
| | 582 | if (err) |
| | 583 | return err; |
| | 584 | } |
| | 585 | |
| | 586 | for (i = 0; i < ARRAY_SIZE(egpu_status_map); i++) { |
| | 587 | if (egpu_status_map[i] == status) |
| | 588 | return sysfs_emit(buf, "%u\n", i); |
| | 589 | } |
| | 590 | |
| | 591 | return -EIO; |
| | 592 | } |
| | 593 | |
| | 594 | static ssize_t egpu_enable_possible_values_show(struct kobject *kobj, struct kobj_attribute *attr, |
| | 595 | char *buf) |
| | 596 | { |
| | 597 | return armoury_attr_enum_list(buf, ARRAY_SIZE(egpu_status_map)); |
| | 598 | } |
| | 599 | ASUS_ATTR_GROUP_ENUM(egpu_enable, "egpu_enable", "Enable the eGPU (also disables dGPU)"); |
| | 600 | |
| | 601 | /* Simple attribute creation */ |
| | 602 | ASUS_ATTR_GROUP_ENUM_INT_RO(charge_mode, "charge_mode", ASUS_WMI_DEVID_CHARGE_MODE, "0;1;2\n", |
| | 603 | "Show the current mode of charging"); |
| | 604 | ASUS_ATTR_GROUP_BOOL_RW(boot_sound, "boot_sound", ASUS_WMI_DEVID_BOOT_SOUND, |
| | 605 | "Set the boot POST sound"); |
| | 606 | ASUS_ATTR_GROUP_BOOL_RW(mcu_powersave, "mcu_powersave", ASUS_WMI_DEVID_MCU_POWERSAVE, |
| | 607 | "Set MCU powersaving mode"); |
| | 608 | ASUS_ATTR_GROUP_BOOL_RW(panel_od, "panel_overdrive", ASUS_WMI_DEVID_PANEL_OD, |
| | 609 | "Set the panel refresh overdrive"); |
| | 610 | ASUS_ATTR_GROUP_BOOL_RO(egpu_connected, "egpu_connected", ASUS_WMI_DEVID_EGPU_CONNECTED, |
| | 611 | "Show the eGPU connection status"); |
| | 612 | |
| | 613 | /* If an attribute does not require any special case handling add it here */ |
| | 614 | static const struct asus_attr_group armoury_attr_groups[] = { |
| | 615 | { &egpu_connected_attr_group, ASUS_WMI_DEVID_EGPU_CONNECTED }, |
| | 616 | { &egpu_enable_attr_group, ASUS_WMI_DEVID_EGPU }, |
| | 617 | { &dgpu_disable_attr_group, ASUS_WMI_DEVID_DGPU }, |
| | 618 | |
| | 619 | { &charge_mode_attr_group, ASUS_WMI_DEVID_CHARGE_MODE }, |
| | 620 | { &boot_sound_attr_group, ASUS_WMI_DEVID_BOOT_SOUND }, |
| | 621 | { &mcu_powersave_attr_group, ASUS_WMI_DEVID_MCU_POWERSAVE }, |
| | 622 | { &panel_od_attr_group, ASUS_WMI_DEVID_PANEL_OD }, |
| | 623 | }; |
| | 624 | |
| | 625 | static int asus_fw_attr_add(void) |
| | 626 | { |
| | 627 | int err, i; |
| | 628 | |
| | 629 | asus_armoury.fw_attr_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0), |
| | 630 | NULL, "%s", DRIVER_NAME); |
| | 631 | if (IS_ERR(asus_armoury.fw_attr_dev)) { |
| | 632 | err = PTR_ERR(asus_armoury.fw_attr_dev); |
| | 633 | goto fail_class_get; |
| | 634 | } |
| | 635 | |
| | 636 | asus_armoury.fw_attr_kset = kset_create_and_add("attributes", NULL, |
| | 637 | &asus_armoury.fw_attr_dev->kobj); |
| | 638 | if (!asus_armoury.fw_attr_kset) { |
| | 639 | err = -ENOMEM; |
| | 640 | goto err_destroy_classdev; |
| | 641 | } |
| | 642 | |
| | 643 | err = sysfs_create_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr); |
| | 644 | if (err) { |
| | 645 | pr_err("Failed to create sysfs level attributes\n"); |
| | 646 | goto err_destroy_kset; |
| | 647 | } |
| | 648 | |
| | 649 | asus_armoury.mini_led_dev_id = 0; |
| | 650 | if (armoury_has_devstate(ASUS_WMI_DEVID_MINI_LED_MODE)) |
| | 651 | asus_armoury.mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE; |
| | 652 | else if (armoury_has_devstate(ASUS_WMI_DEVID_MINI_LED_MODE2)) |
| | 653 | asus_armoury.mini_led_dev_id = ASUS_WMI_DEVID_MINI_LED_MODE2; |
| | 654 | |
| | 655 | if (asus_armoury.mini_led_dev_id) { |
| | 656 | err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, |
| | 657 | &mini_led_mode_attr_group); |
| | 658 | if (err) { |
| | 659 | pr_err("Failed to create sysfs-group for mini_led\n"); |
| | 660 | goto err_remove_file; |
| | 661 | } |
| | 662 | } |
| | 663 | |
| | 664 | asus_armoury.gpu_mux_dev_id = 0; |
| | 665 | if (armoury_has_devstate(ASUS_WMI_DEVID_GPU_MUX)) |
| | 666 | asus_armoury.gpu_mux_dev_id = ASUS_WMI_DEVID_GPU_MUX; |
| | 667 | else if (armoury_has_devstate(ASUS_WMI_DEVID_GPU_MUX_VIVO)) |
| | 668 | asus_armoury.gpu_mux_dev_id = ASUS_WMI_DEVID_GPU_MUX_VIVO; |
| | 669 | |
| | 670 | if (asus_armoury.gpu_mux_dev_id) { |
| | 671 | err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, |
| | 672 | &gpu_mux_mode_attr_group); |
| | 673 | if (err) { |
| | 674 | pr_err("Failed to create sysfs-group for gpu_mux\n"); |
| | 675 | goto err_remove_mini_led_group; |
| | 676 | } |
| | 677 | } |
| | 678 | |
| | 679 | for (i = 0; i < ARRAY_SIZE(armoury_attr_groups); i++) { |
| | 680 | if (!armoury_has_devstate(armoury_attr_groups[i].wmi_devid)) |
| | 681 | continue; |
| | 682 | |
| | 683 | err = sysfs_create_group(&asus_armoury.fw_attr_kset->kobj, |
| | 684 | armoury_attr_groups[i].attr_group); |
| | 685 | if (err) { |
| | 686 | pr_err("Failed to create sysfs-group for %s\n", |
| | 687 | armoury_attr_groups[i].attr_group->name); |
| | 688 | goto err_remove_groups; |
| | 689 | } |
| | 690 | } |
| | 691 | |
| | 692 | return 0; |
| | 693 | |
| | 694 | err_remove_groups: |
| | 695 | while (i--) { |
| | 696 | if (armoury_has_devstate(armoury_attr_groups[i].wmi_devid)) |
| | 697 | sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, |
| | 698 | armoury_attr_groups[i].attr_group); |
| | 699 | } |
| | 700 | if (asus_armoury.gpu_mux_dev_id) |
| | 701 | sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &gpu_mux_mode_attr_group); |
| | 702 | err_remove_mini_led_group: |
| | 703 | if (asus_armoury.mini_led_dev_id) |
| | 704 | sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &mini_led_mode_attr_group); |
| | 705 | err_remove_file: |
| | 706 | sysfs_remove_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr); |
| | 707 | err_destroy_kset: |
| | 708 | kset_unregister(asus_armoury.fw_attr_kset); |
| | 709 | err_destroy_classdev: |
| | 710 | fail_class_get: |
| | 711 | device_destroy(&firmware_attributes_class, MKDEV(0, 0)); |
| | 712 | return err; |
| | 713 | } |
| | 714 | |
| | 715 | /* Init / exit ****************************************************************/ |
| | 716 | |
| | 717 | static int __init asus_fw_init(void) |
| | 718 | { |
| | 719 | char *wmi_uid; |
| | 720 | |
| | 721 | wmi_uid = wmi_get_acpi_device_uid(ASUS_WMI_MGMT_GUID); |
| | 722 | if (!wmi_uid) |
| | 723 | return -ENODEV; |
| | 724 | |
| | 725 | /* |
| | 726 | * if equal to "ASUSWMI" then it's DCTS that can't be used for this |
| | 727 | * driver, DSTS is required. |
| | 728 | */ |
| | 729 | if (!strcmp(wmi_uid, ASUS_ACPI_UID_ASUSWMI)) |
| | 730 | return -ENODEV; |
| | 731 | |
| | 732 | return asus_fw_attr_add(); |
| | 733 | } |
| | 734 | |
| | 735 | static void __exit asus_fw_exit(void) |
| | 736 | { |
| | 737 | int i; |
| | 738 | |
| | 739 | for (i = ARRAY_SIZE(armoury_attr_groups) - 1; i >= 0; i--) { |
| | 740 | if (armoury_has_devstate(armoury_attr_groups[i].wmi_devid)) |
| | 741 | sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, |
| | 742 | armoury_attr_groups[i].attr_group); |
| | 743 | } |
| | 744 | |
| | 745 | if (asus_armoury.gpu_mux_dev_id) |
| | 746 | sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &gpu_mux_mode_attr_group); |
| | 747 | |
| | 748 | if (asus_armoury.mini_led_dev_id) |
| | 749 | sysfs_remove_group(&asus_armoury.fw_attr_kset->kobj, &mini_led_mode_attr_group); |
| | 750 | |
| | 751 | sysfs_remove_file(&asus_armoury.fw_attr_kset->kobj, &pending_reboot.attr); |
| | 752 | kset_unregister(asus_armoury.fw_attr_kset); |
| | 753 | device_destroy(&firmware_attributes_class, MKDEV(0, 0)); |
| | 754 | } |
| | 755 | |
| | 756 | module_init(asus_fw_init); |
| | 757 | module_exit(asus_fw_exit); |
| | 758 | |
| | 759 | MODULE_IMPORT_NS("ASUS_WMI"); |
| | 760 | MODULE_AUTHOR("Luke Jones <luke@ljones.dev>"); |
| | 761 | MODULE_DESCRIPTION("ASUS BIOS Configuration Driver"); |
| | 762 | MODULE_LICENSE("GPL"); |
| | 763 | MODULE_ALIAS("wmi:" ASUS_NB_WMI_EVENT_GUID); |