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 3aa2b0dcc36..8461b08ab9f 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);  |