1 // SPDX-License-Identifier: GPL-2.0-only OR MIT 2 /* 3 * Apple SMC Reboot/Poweroff Handler 4 * Copyright The Asahi Linux Contributors 5 */ 6 7 #include <linux/delay.h> 8 #include <linux/mfd/core.h> 9 #include <linux/mfd/macsmc.h> 10 #include <linux/mod_devicetable.h> 11 #include <linux/module.h> 12 #include <linux/nvmem-consumer.h> 13 #include <linux/platform_device.h> 14 #include <linux/reboot.h> 15 #include <linux/slab.h> 16 17 struct macsmc_reboot_nvmem { 18 struct nvmem_cell *shutdown_flag; 19 struct nvmem_cell *boot_stage; 20 struct nvmem_cell *boot_error_count; 21 struct nvmem_cell *panic_count; 22 }; 23 24 static const char * const nvmem_names[] = { 25 "shutdown_flag", 26 "boot_stage", 27 "boot_error_count", 28 "panic_count", 29 }; 30 31 enum boot_stage { 32 BOOT_STAGE_SHUTDOWN = 0x00, /* Clean shutdown */ 33 BOOT_STAGE_IBOOT_DONE = 0x2f, /* Last stage of bootloader */ 34 BOOT_STAGE_KERNEL_STARTED = 0x30, /* Normal OS booting */ 35 }; 36 37 struct macsmc_reboot { 38 struct device *dev; 39 struct apple_smc *smc; 40 struct notifier_block reboot_notify; 41 42 union { 43 struct macsmc_reboot_nvmem nvm; 44 struct nvmem_cell *nvm_cells[ARRAY_SIZE(nvmem_names)]; 45 }; 46 }; 47 48 /* Helpers to read/write a u8 given a struct nvmem_cell */ 49 static int nvmem_cell_get_u8(struct nvmem_cell *cell) 50 { 51 size_t len; 52 void *bfr; 53 u8 val; 54 55 bfr = nvmem_cell_read(cell, &len); 56 if (IS_ERR(bfr)) 57 return PTR_ERR(bfr); 58 59 if (len < 1) { 60 kfree(bfr); 61 return -EINVAL; 62 } 63 64 val = *(u8 *)bfr; 65 kfree(bfr); 66 return val; 67 } 68 69 static int nvmem_cell_set_u8(struct nvmem_cell *cell, u8 val) 70 { 71 return nvmem_cell_write(cell, &val, sizeof(val)); 72 } 73 74 /* 75 * SMC 'MBSE' key actions: 76 * 77 * 'offw' - shutdown warning 78 * 'slpw' - sleep warning 79 * 'rest' - restart warning 80 * 'off1' - shutdown (needs PMU bit set to stay on) 81 * 'susp' - suspend 82 * 'phra' - restart ("PE Halt Restart Action"?) 83 * 'panb' - panic beginning 84 * 'pane' - panic end 85 */ 86 87 static int macsmc_prepare_atomic(struct sys_off_data *data) 88 { 89 struct macsmc_reboot *reboot = data->cb_data; 90 91 dev_info(reboot->dev, "Preparing SMC for atomic mode\n"); 92 93 apple_smc_enter_atomic(reboot->smc); 94 return NOTIFY_OK; 95 } 96 97 static int macsmc_power_off(struct sys_off_data *data) 98 { 99 struct macsmc_reboot *reboot = data->cb_data; 100 101 dev_info(reboot->dev, "Issuing power off (off1)\n"); 102 103 if (apple_smc_write_u32_atomic(reboot->smc, SMC_KEY(MBSE), SMC_KEY(off1)) < 0) { 104 dev_err(reboot->dev, "Failed to issue MBSE = off1 (power_off)\n"); 105 } else { 106 mdelay(100); 107 WARN_ONCE(1, "Unable to power off system\n"); 108 } 109 110 return NOTIFY_OK; 111 } 112 113 static int macsmc_restart(struct sys_off_data *data) 114 { 115 struct macsmc_reboot *reboot = data->cb_data; 116 117 dev_info(reboot->dev, "Issuing restart (phra)\n"); 118 119 if (apple_smc_write_u32_atomic(reboot->smc, SMC_KEY(MBSE), SMC_KEY(phra)) < 0) { 120 dev_err(reboot->dev, "Failed to issue MBSE = phra (restart)\n"); 121 } else { 122 mdelay(100); 123 WARN_ONCE(1, "Unable to restart system\n"); 124 } 125 126 return NOTIFY_OK; 127 } 128 129 static int macsmc_reboot_notify(struct notifier_block *this, unsigned long action, void *data) 130 { 131 struct macsmc_reboot *reboot = container_of(this, struct macsmc_reboot, reboot_notify); 132 u8 shutdown_flag; 133 u32 val; 134 135 switch (action) { 136 case SYS_RESTART: 137 val = SMC_KEY(rest); 138 shutdown_flag = 0; 139 break; 140 case SYS_POWER_OFF: 141 val = SMC_KEY(offw); 142 shutdown_flag = 1; 143 break; 144 default: 145 return NOTIFY_DONE; 146 } 147 148 dev_info(reboot->dev, "Preparing for reboot (%p4ch)\n", &val); 149 150 /* On the Mac Mini, this will turn off the LED for power off */ 151 if (apple_smc_write_u32(reboot->smc, SMC_KEY(MBSE), val) < 0) 152 dev_err(reboot->dev, "Failed to issue MBSE = %p4ch (reboot_prepare)\n", &val); 153 154 /* Set the boot_stage to 0, which means we're doing a clean shutdown/reboot. */ 155 if (reboot->nvm.boot_stage && 156 nvmem_cell_set_u8(reboot->nvm.boot_stage, BOOT_STAGE_SHUTDOWN) < 0) 157 dev_err(reboot->dev, "Failed to write boot_stage\n"); 158 159 /* 160 * Set the PMU flag to actually reboot into the off state. 161 * Without this, the device will just reboot. We make it optional in case it is no longer 162 * necessary on newer hardware. 163 */ 164 if (reboot->nvm.shutdown_flag && 165 nvmem_cell_set_u8(reboot->nvm.shutdown_flag, shutdown_flag) < 0) 166 dev_err(reboot->dev, "Failed to write shutdown_flag\n"); 167 168 return NOTIFY_OK; 169 } 170 171 static void macsmc_power_init_error_counts(struct macsmc_reboot *reboot) 172 { 173 int boot_error_count, panic_count; 174 175 if (!reboot->nvm.boot_error_count || !reboot->nvm.panic_count) 176 return; 177 178 boot_error_count = nvmem_cell_get_u8(reboot->nvm.boot_error_count); 179 if (boot_error_count < 0) { 180 dev_err(reboot->dev, "Failed to read boot_error_count (%d)\n", boot_error_count); 181 return; 182 } 183 184 panic_count = nvmem_cell_get_u8(reboot->nvm.panic_count); 185 if (panic_count < 0) { 186 dev_err(reboot->dev, "Failed to read panic_count (%d)\n", panic_count); 187 return; 188 } 189 190 if (!boot_error_count && !panic_count) 191 return; 192 193 dev_warn(reboot->dev, "PMU logged %d boot error(s) and %d panic(s)\n", 194 boot_error_count, panic_count); 195 196 if (nvmem_cell_set_u8(reboot->nvm.panic_count, 0) < 0) 197 dev_err(reboot->dev, "Failed to reset panic_count\n"); 198 if (nvmem_cell_set_u8(reboot->nvm.boot_error_count, 0) < 0) 199 dev_err(reboot->dev, "Failed to reset boot_error_count\n"); 200 } 201 202 static int macsmc_reboot_probe(struct platform_device *pdev) 203 { 204 struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); 205 struct macsmc_reboot *reboot; 206 int ret, i; 207 208 reboot = devm_kzalloc(&pdev->dev, sizeof(*reboot), GFP_KERNEL); 209 if (!reboot) 210 return -ENOMEM; 211 212 reboot->dev = &pdev->dev; 213 reboot->smc = smc; 214 215 platform_set_drvdata(pdev, reboot); 216 217 for (i = 0; i < ARRAY_SIZE(nvmem_names); i++) { 218 struct nvmem_cell *cell; 219 220 cell = devm_nvmem_cell_get(&pdev->dev, 221 nvmem_names[i]); 222 if (IS_ERR(cell)) { 223 if (PTR_ERR(cell) == -EPROBE_DEFER) 224 return -EPROBE_DEFER; 225 dev_warn(&pdev->dev, "Missing NVMEM cell %s (%ld)\n", 226 nvmem_names[i], PTR_ERR(cell)); 227 /* Non fatal, we'll deal with it */ 228 cell = NULL; 229 } 230 reboot->nvm_cells[i] = cell; 231 } 232 233 /* Set the boot_stage to indicate we're running the OS kernel */ 234 if (reboot->nvm.boot_stage && 235 nvmem_cell_set_u8(reboot->nvm.boot_stage, BOOT_STAGE_KERNEL_STARTED) < 0) 236 dev_err(reboot->dev, "Failed to write boot_stage\n"); 237 238 /* Display and clear the error counts */ 239 macsmc_power_init_error_counts(reboot); 240 241 reboot->reboot_notify.notifier_call = macsmc_reboot_notify; 242 243 ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF_PREPARE, 244 SYS_OFF_PRIO_HIGH, macsmc_prepare_atomic, reboot); 245 if (ret) 246 return dev_err_probe(&pdev->dev, ret, 247 "Failed to register power-off prepare handler\n"); 248 ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_HIGH, 249 macsmc_power_off, reboot); 250 if (ret) 251 return dev_err_probe(&pdev->dev, ret, 252 "Failed to register power-off handler\n"); 253 254 ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART_PREPARE, 255 SYS_OFF_PRIO_HIGH, macsmc_prepare_atomic, reboot); 256 if (ret) 257 return dev_err_probe(&pdev->dev, ret, 258 "Failed to register restart prepare handler\n"); 259 ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART, SYS_OFF_PRIO_HIGH, 260 macsmc_restart, reboot); 261 if (ret) 262 return dev_err_probe(&pdev->dev, ret, "Failed to register restart handler\n"); 263 264 ret = devm_register_reboot_notifier(&pdev->dev, &reboot->reboot_notify); 265 if (ret) 266 return dev_err_probe(&pdev->dev, ret, "Failed to register reboot notifier\n"); 267 268 dev_info(&pdev->dev, "Handling reboot and poweroff requests via SMC\n"); 269 270 return 0; 271 } 272 273 static const struct of_device_id macsmc_reboot_of_table[] = { 274 { .compatible = "apple,smc-reboot", }, 275 {} 276 }; 277 MODULE_DEVICE_TABLE(of, macsmc_reboot_of_table); 278 279 static struct platform_driver macsmc_reboot_driver = { 280 .driver = { 281 .name = "macsmc-reboot", 282 .of_match_table = macsmc_reboot_of_table, 283 }, 284 .probe = macsmc_reboot_probe, 285 }; 286 module_platform_driver(macsmc_reboot_driver); 287 288 MODULE_LICENSE("Dual MIT/GPL"); 289 MODULE_DESCRIPTION("Apple SMC reboot/poweroff driver"); 290 MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); 291