diff options
Diffstat (limited to 'drivers/ps3/sys-manager.c')
-rw-r--r-- | drivers/ps3/sys-manager.c | 290 |
1 files changed, 191 insertions, 99 deletions
diff --git a/drivers/ps3/sys-manager.c b/drivers/ps3/sys-manager.c index 3aa2b0dcc369..8461b08ab9fb 100644 --- a/drivers/ps3/sys-manager.c +++ b/drivers/ps3/sys-manager.c @@ -35,7 +35,7 @@ MODULE_DESCRIPTION("PS3 System Manager"); /** * ps3_sys_manager - PS3 system manager driver. * - * The system manager provides an asyncronous system event notification + * The system manager provides an asynchronous system event notification * mechanism for reporting events like thermal alert and button presses to * guests. It also provides support to control system shutdown and startup. * @@ -52,6 +52,7 @@ MODULE_DESCRIPTION("PS3 System Manager"); * @size: Header size in bytes, curently 16. * @payload_size: Message payload size in bytes. * @service_id: Message type, one of enum ps3_sys_manager_service_id. + * @request_tag: Unique number to identify reply. */ struct ps3_sys_manager_header { @@ -61,29 +62,49 @@ struct ps3_sys_manager_header { u16 reserved_1; u32 payload_size; u16 service_id; - u16 reserved_2[3]; + u16 reserved_2; + u32 request_tag; }; +#define dump_sm_header(_h) _dump_sm_header(_h, __func__, __LINE__) +static void __maybe_unused _dump_sm_header( + const struct ps3_sys_manager_header *h, const char *func, int line) +{ + pr_debug("%s:%d: version: %xh\n", func, line, h->version); + pr_debug("%s:%d: size: %xh\n", func, line, h->size); + pr_debug("%s:%d: payload_size: %xh\n", func, line, h->payload_size); + pr_debug("%s:%d: service_id: %xh\n", func, line, h->service_id); + pr_debug("%s:%d: request_tag: %xh\n", func, line, h->request_tag); +} + /** - * @PS3_SM_RX_MSG_LEN - System manager received message length. + * @PS3_SM_RX_MSG_LEN_MIN - Shortest received message length. + * @PS3_SM_RX_MSG_LEN_MAX - Longest received message length. * - * Currently all messages received from the system manager are the same length - * (16 bytes header + 16 bytes payload = 32 bytes). This knowlege is used to - * simplify the logic. + * Currently all messages received from the system manager are either + * (16 bytes header + 8 bytes payload = 24 bytes) or (16 bytes header + * + 16 bytes payload = 32 bytes). This knowlege is used to simplify + * the logic. */ enum { - PS3_SM_RX_MSG_LEN = 32, + PS3_SM_RX_MSG_LEN_MIN = 24, + PS3_SM_RX_MSG_LEN_MAX = 32, }; /** * enum ps3_sys_manager_service_id - Message header service_id. - * @PS3_SM_SERVICE_ID_REQUEST: guest --> sys_manager. - * @PS3_SM_SERVICE_ID_COMMAND: guest <-- sys_manager. - * @PS3_SM_SERVICE_ID_RESPONSE: guest --> sys_manager. - * @PS3_SM_SERVICE_ID_SET_ATTR: guest --> sys_manager. - * @PS3_SM_SERVICE_ID_EXTERN_EVENT: guest <-- sys_manager. - * @PS3_SM_SERVICE_ID_SET_NEXT_OP: guest --> sys_manager. + * @PS3_SM_SERVICE_ID_REQUEST: guest --> sys_manager. + * @PS3_SM_SERVICE_ID_REQUEST_ERROR: guest <-- sys_manager. + * @PS3_SM_SERVICE_ID_COMMAND: guest <-- sys_manager. + * @PS3_SM_SERVICE_ID_RESPONSE: guest --> sys_manager. + * @PS3_SM_SERVICE_ID_SET_ATTR: guest --> sys_manager. + * @PS3_SM_SERVICE_ID_EXTERN_EVENT: guest <-- sys_manager. + * @PS3_SM_SERVICE_ID_SET_NEXT_OP: guest --> sys_manager. + * + * PS3_SM_SERVICE_ID_REQUEST_ERROR is returned for invalid data values in a + * a PS3_SM_SERVICE_ID_REQUEST message. It also seems to be returned when + * a REQUEST message is sent at the wrong time. */ enum ps3_sys_manager_service_id { @@ -93,6 +114,7 @@ enum ps3_sys_manager_service_id { PS3_SM_SERVICE_ID_COMMAND = 3, PS3_SM_SERVICE_ID_EXTERN_EVENT = 4, PS3_SM_SERVICE_ID_SET_NEXT_OP = 5, + PS3_SM_SERVICE_ID_REQUEST_ERROR = 6, PS3_SM_SERVICE_ID_SET_ATTR = 8, }; @@ -185,11 +207,21 @@ enum ps3_sys_manager_cmd { }; /** + * ps3_sm_force_power_off - Poweroff helper. + * + * A global variable used to force a poweroff when the power button has + * been pressed irrespective of how init handles the ctrl_alt_del signal. + * + */ + +static unsigned int ps3_sm_force_power_off; + +/** * ps3_sys_manager_write - Helper to write a two part message to the vuart. * */ -static int ps3_sys_manager_write(struct ps3_vuart_port_device *dev, +static int ps3_sys_manager_write(struct ps3_system_bus_device *dev, const struct ps3_sys_manager_header *header, const void *payload) { int result; @@ -213,15 +245,10 @@ static int ps3_sys_manager_write(struct ps3_vuart_port_device *dev, * */ -static int ps3_sys_manager_send_attr(struct ps3_vuart_port_device *dev, +static int ps3_sys_manager_send_attr(struct ps3_system_bus_device *dev, enum ps3_sys_manager_attr attr) { - static const struct ps3_sys_manager_header header = { - .version = 1, - .size = 16, - .payload_size = 16, - .service_id = PS3_SM_SERVICE_ID_SET_ATTR, - }; + struct ps3_sys_manager_header header; struct { u8 version; u8 reserved_1[3]; @@ -232,6 +259,12 @@ static int ps3_sys_manager_send_attr(struct ps3_vuart_port_device *dev, dev_dbg(&dev->core, "%s:%d: %xh\n", __func__, __LINE__, attr); + memset(&header, 0, sizeof(header)); + header.version = 1; + header.size = 16; + header.payload_size = 16; + header.service_id = PS3_SM_SERVICE_ID_SET_ATTR; + memset(&payload, 0, sizeof(payload)); payload.version = 1; payload.attribute = attr; @@ -245,16 +278,11 @@ static int ps3_sys_manager_send_attr(struct ps3_vuart_port_device *dev, * Tell the system manager what to do after this lpar is destroyed. */ -static int ps3_sys_manager_send_next_op(struct ps3_vuart_port_device *dev, +static int ps3_sys_manager_send_next_op(struct ps3_system_bus_device *dev, enum ps3_sys_manager_next_op op, enum ps3_sys_manager_wake_source wake_source) { - static const struct ps3_sys_manager_header header = { - .version = 1, - .size = 16, - .payload_size = 16, - .service_id = PS3_SM_SERVICE_ID_SET_NEXT_OP, - }; + struct ps3_sys_manager_header header; struct { u8 version; u8 type; @@ -268,6 +296,12 @@ static int ps3_sys_manager_send_next_op(struct ps3_vuart_port_device *dev, dev_dbg(&dev->core, "%s:%d: (%xh)\n", __func__, __LINE__, op); + memset(&header, 0, sizeof(header)); + header.version = 1; + header.size = 16; + header.payload_size = 16; + header.service_id = PS3_SM_SERVICE_ID_SET_NEXT_OP; + memset(&payload, 0, sizeof(payload)); payload.version = 3; payload.type = op; @@ -286,32 +320,35 @@ static int ps3_sys_manager_send_next_op(struct ps3_vuart_port_device *dev, * the command is then communicated back to the system manager with a response * message. * - * Currently, the only supported request it the 'shutdown self' request. + * Currently, the only supported request is the 'shutdown self' request. */ -static int ps3_sys_manager_send_request_shutdown(struct ps3_vuart_port_device *dev) +static int ps3_sys_manager_send_request_shutdown( + struct ps3_system_bus_device *dev) { - static const struct ps3_sys_manager_header header = { - .version = 1, - .size = 16, - .payload_size = 16, - .service_id = PS3_SM_SERVICE_ID_REQUEST, - }; + struct ps3_sys_manager_header header; struct { u8 version; u8 type; u8 gos_id; u8 reserved_1[13]; - } static const payload = { - .version = 1, - .type = 1, /* shutdown */ - .gos_id = 0, /* self */ - }; + } payload; BUILD_BUG_ON(sizeof(payload) != 16); dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__); + memset(&header, 0, sizeof(header)); + header.version = 1; + header.size = 16; + header.payload_size = 16; + header.service_id = PS3_SM_SERVICE_ID_REQUEST; + + memset(&payload, 0, sizeof(payload)); + payload.version = 1; + payload.type = 1; /* shutdown */ + payload.gos_id = 0; /* self */ + return ps3_sys_manager_write(dev, &header, &payload); } @@ -323,15 +360,10 @@ static int ps3_sys_manager_send_request_shutdown(struct ps3_vuart_port_device *d * failure of a command sent by the system manager. */ -static int ps3_sys_manager_send_response(struct ps3_vuart_port_device *dev, +static int ps3_sys_manager_send_response(struct ps3_system_bus_device *dev, u64 status) { - static const struct ps3_sys_manager_header header = { - .version = 1, - .size = 16, - .payload_size = 16, - .service_id = PS3_SM_SERVICE_ID_RESPONSE, - }; + struct ps3_sys_manager_header header; struct { u8 version; u8 reserved_1[3]; @@ -344,6 +376,12 @@ static int ps3_sys_manager_send_response(struct ps3_vuart_port_device *dev, dev_dbg(&dev->core, "%s:%d: (%s)\n", __func__, __LINE__, (status ? "nak" : "ack")); + memset(&header, 0, sizeof(header)); + header.version = 1; + header.size = 16; + header.payload_size = 16; + header.service_id = PS3_SM_SERVICE_ID_RESPONSE; + memset(&payload, 0, sizeof(payload)); payload.version = 1; payload.status = status; @@ -356,7 +394,7 @@ static int ps3_sys_manager_send_response(struct ps3_vuart_port_device *dev, * */ -static int ps3_sys_manager_handle_event(struct ps3_vuart_port_device *dev) +static int ps3_sys_manager_handle_event(struct ps3_system_bus_device *dev) { int result; struct { @@ -370,7 +408,7 @@ static int ps3_sys_manager_handle_event(struct ps3_vuart_port_device *dev) BUILD_BUG_ON(sizeof(event) != 16); result = ps3_vuart_read(dev, &event, sizeof(event)); - BUG_ON(result); + BUG_ON(result && "need to retry here"); if (event.version != 1) { dev_dbg(&dev->core, "%s:%d: unsupported event version (%u)\n", @@ -382,11 +420,34 @@ static int ps3_sys_manager_handle_event(struct ps3_vuart_port_device *dev) case PS3_SM_EVENT_POWER_PRESSED: dev_dbg(&dev->core, "%s:%d: POWER_PRESSED\n", __func__, __LINE__); + ps3_sm_force_power_off = 1; + /* + * A memory barrier is use here to sync memory since + * ps3_sys_manager_final_restart() could be called on + * another cpu. + */ + wmb(); + kill_cad_pid(SIGINT, 1); /* ctrl_alt_del */ break; case PS3_SM_EVENT_POWER_RELEASED: dev_dbg(&dev->core, "%s:%d: POWER_RELEASED (%u ms)\n", __func__, __LINE__, event.value); - kill_cad_pid(SIGINT, 1); + break; + case PS3_SM_EVENT_RESET_PRESSED: + dev_dbg(&dev->core, "%s:%d: RESET_PRESSED\n", + __func__, __LINE__); + ps3_sm_force_power_off = 0; + /* + * A memory barrier is use here to sync memory since + * ps3_sys_manager_final_restart() could be called on + * another cpu. + */ + wmb(); + kill_cad_pid(SIGINT, 1); /* ctrl_alt_del */ + break; + case PS3_SM_EVENT_RESET_RELEASED: + dev_dbg(&dev->core, "%s:%d: RESET_RELEASED (%u ms)\n", + __func__, __LINE__, event.value); break; case PS3_SM_EVENT_THERMAL_ALERT: dev_dbg(&dev->core, "%s:%d: THERMAL_ALERT (zone %u)\n", @@ -411,7 +472,7 @@ static int ps3_sys_manager_handle_event(struct ps3_vuart_port_device *dev) * The system manager sends this in reply to a 'request' message from the guest. */ -static int ps3_sys_manager_handle_cmd(struct ps3_vuart_port_device *dev) +static int ps3_sys_manager_handle_cmd(struct ps3_system_bus_device *dev) { int result; struct { @@ -425,6 +486,7 @@ static int ps3_sys_manager_handle_cmd(struct ps3_vuart_port_device *dev) dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__); result = ps3_vuart_read(dev, &cmd, sizeof(cmd)); + BUG_ON(result && "need to retry here"); if(result) return result; @@ -448,9 +510,10 @@ static int ps3_sys_manager_handle_cmd(struct ps3_vuart_port_device *dev) /** * ps3_sys_manager_handle_msg - First stage msg handler. * + * Can be called directly to manually poll vuart and pump message handler. */ -static int ps3_sys_manager_handle_msg(struct ps3_vuart_port_device *dev) +static int ps3_sys_manager_handle_msg(struct ps3_system_bus_device *dev) { int result; struct ps3_sys_manager_header header; @@ -464,12 +527,17 @@ static int ps3_sys_manager_handle_msg(struct ps3_vuart_port_device *dev) if (header.version != 1) { dev_dbg(&dev->core, "%s:%d: unsupported header version (%u)\n", __func__, __LINE__, header.version); + dump_sm_header(&header); goto fail_header; } BUILD_BUG_ON(sizeof(header) != 16); - BUG_ON(header.size != 16); - BUG_ON(header.payload_size != 16); + + if (header.size != 16 || (header.payload_size != 8 + && header.payload_size != 16)) { + dump_sm_header(&header); + BUG(); + } switch (header.service_id) { case PS3_SM_SERVICE_ID_EXTERN_EVENT: @@ -478,6 +546,11 @@ static int ps3_sys_manager_handle_msg(struct ps3_vuart_port_device *dev) case PS3_SM_SERVICE_ID_COMMAND: dev_dbg(&dev->core, "%s:%d: COMMAND\n", __func__, __LINE__); return ps3_sys_manager_handle_cmd(dev); + case PS3_SM_SERVICE_ID_REQUEST_ERROR: + dev_dbg(&dev->core, "%s:%d: REQUEST_ERROR\n", __func__, + __LINE__); + dump_sm_header(&header); + break; default: dev_dbg(&dev->core, "%s:%d: unknown service_id (%u)\n", __func__, __LINE__, header.service_id); @@ -494,45 +567,25 @@ fail_id: } /** - * ps3_sys_manager_work - Asyncronous read handler. - * - * Signaled when a complete message arrives at the vuart port. - */ - -static void ps3_sys_manager_work(struct work_struct *work) -{ - struct ps3_vuart_port_device *dev = ps3_vuart_work_to_port_device(work); - - ps3_sys_manager_handle_msg(dev); - ps3_vuart_read_async(dev, ps3_sys_manager_work, PS3_SM_RX_MSG_LEN); -} - -struct { - struct ps3_vuart_port_device *dev; -} static drv_priv; - -/** - * ps3_sys_manager_restart - The final platform machine_restart routine. + * ps3_sys_manager_final_power_off - The final platform machine_power_off routine. * - * This routine never returns. The routine disables asyncronous vuart reads + * This routine never returns. The routine disables asynchronous vuart reads * then spins calling ps3_sys_manager_handle_msg() to receive and acknowledge * the shutdown command sent from the system manager. Soon after the * acknowledgement is sent the lpar is destroyed by the HV. This routine - * should only be called from ps3_restart(). + * should only be called from ps3_power_off() through + * ps3_sys_manager_ops.power_off. */ -void ps3_sys_manager_restart(void) +static void ps3_sys_manager_final_power_off(struct ps3_system_bus_device *dev) { - struct ps3_vuart_port_device *dev = drv_priv.dev; - - BUG_ON(!drv_priv.dev); + BUG_ON(!dev); dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__); ps3_vuart_cancel_async(dev); - ps3_sys_manager_send_attr(dev, 0); - ps3_sys_manager_send_next_op(dev, PS3_SM_NEXT_OP_LPAR_REBOOT, + ps3_sys_manager_send_next_op(dev, PS3_SM_NEXT_OP_SYS_SHUTDOWN, PS3_SM_WAKE_DEFAULT); ps3_sys_manager_send_request_shutdown(dev); @@ -543,26 +596,33 @@ void ps3_sys_manager_restart(void) } /** - * ps3_sys_manager_power_off - The final platform machine_power_off routine. + * ps3_sys_manager_final_restart - The final platform machine_restart routine. * - * This routine never returns. The routine disables asyncronous vuart reads + * This routine never returns. The routine disables asynchronous vuart reads * then spins calling ps3_sys_manager_handle_msg() to receive and acknowledge * the shutdown command sent from the system manager. Soon after the * acknowledgement is sent the lpar is destroyed by the HV. This routine - * should only be called from ps3_power_off(). + * should only be called from ps3_restart() through ps3_sys_manager_ops.restart. */ -void ps3_sys_manager_power_off(void) +static void ps3_sys_manager_final_restart(struct ps3_system_bus_device *dev) { - struct ps3_vuart_port_device *dev = drv_priv.dev; - - BUG_ON(!drv_priv.dev); + BUG_ON(!dev); dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__); + /* Check if we got here via a power button event. */ + + if (ps3_sm_force_power_off) { + dev_dbg(&dev->core, "%s:%d: forcing poweroff\n", + __func__, __LINE__); + ps3_sys_manager_final_power_off(dev); + } + ps3_vuart_cancel_async(dev); - ps3_sys_manager_send_next_op(dev, PS3_SM_NEXT_OP_SYS_SHUTDOWN, + ps3_sys_manager_send_attr(dev, 0); + ps3_sys_manager_send_next_op(dev, PS3_SM_NEXT_OP_LPAR_REBOOT, PS3_SM_WAKE_DEFAULT); ps3_sys_manager_send_request_shutdown(dev); @@ -572,31 +632,60 @@ void ps3_sys_manager_power_off(void) ps3_sys_manager_handle_msg(dev); } -static int ps3_sys_manager_probe(struct ps3_vuart_port_device *dev) +/** + * ps3_sys_manager_work - Asynchronous read handler. + * + * Signaled when PS3_SM_RX_MSG_LEN_MIN bytes arrive at the vuart port. + */ + +static void ps3_sys_manager_work(struct ps3_system_bus_device *dev) +{ + ps3_sys_manager_handle_msg(dev); + ps3_vuart_read_async(dev, PS3_SM_RX_MSG_LEN_MIN); +} + +static int ps3_sys_manager_probe(struct ps3_system_bus_device *dev) { int result; + struct ps3_sys_manager_ops ops; dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__); - BUG_ON(drv_priv.dev); - drv_priv.dev = dev; + ops.power_off = ps3_sys_manager_final_power_off; + ops.restart = ps3_sys_manager_final_restart; + ops.dev = dev; + + /* ps3_sys_manager_register_ops copies ops. */ + + ps3_sys_manager_register_ops(&ops); result = ps3_sys_manager_send_attr(dev, PS3_SM_ATTR_ALL); BUG_ON(result); - result = ps3_vuart_read_async(dev, ps3_sys_manager_work, - PS3_SM_RX_MSG_LEN); + result = ps3_vuart_read_async(dev, PS3_SM_RX_MSG_LEN_MIN); BUG_ON(result); return result; } +static int ps3_sys_manager_remove(struct ps3_system_bus_device *dev) +{ + dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__); + return 0; +} + +static void ps3_sys_manager_shutdown(struct ps3_system_bus_device *dev) +{ + dev_dbg(&dev->core, "%s:%d\n", __func__, __LINE__); +} + static struct ps3_vuart_port_driver ps3_sys_manager = { - .match_id = PS3_MATCH_ID_SYSTEM_MANAGER, - .core = { - .name = "ps3_sys_manager", - }, + .core.match_id = PS3_MATCH_ID_SYSTEM_MANAGER, + .core.core.name = "ps3_sys_manager", .probe = ps3_sys_manager_probe, + .remove = ps3_sys_manager_remove, + .shutdown = ps3_sys_manager_shutdown, + .work = ps3_sys_manager_work, }; static int __init ps3_sys_manager_init(void) @@ -608,3 +697,6 @@ static int __init ps3_sys_manager_init(void) } module_init(ps3_sys_manager_init); +/* Module remove not supported. */ + +MODULE_ALIAS(PS3_MODULE_ALIAS_SYSTEM_MANAGER); |