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