diff options
Diffstat (limited to 'kernel/drivers/misc/mei/hbm.c')
-rw-r--r-- | kernel/drivers/misc/mei/hbm.c | 352 |
1 files changed, 325 insertions, 27 deletions
diff --git a/kernel/drivers/misc/mei/hbm.c b/kernel/drivers/misc/mei/hbm.c index 58da92565..e7b7aad09 100644 --- a/kernel/drivers/misc/mei/hbm.c +++ b/kernel/drivers/misc/mei/hbm.c @@ -52,6 +52,7 @@ static const char *mei_cl_conn_status_str(enum mei_cl_connect_status status) MEI_CL_CS(ALREADY_STARTED); MEI_CL_CS(OUT_OF_RESOURCES); MEI_CL_CS(MESSAGE_SMALL); + MEI_CL_CS(NOT_ALLOWED); default: return "unknown"; } #undef MEI_CL_CCS @@ -89,6 +90,7 @@ static int mei_cl_conn_status_to_errno(enum mei_cl_connect_status status) case MEI_CL_CONN_ALREADY_STARTED: return -EBUSY; case MEI_CL_CONN_OUT_OF_RESOURCES: return -EBUSY; case MEI_CL_CONN_MESSAGE_SMALL: return -EINVAL; + case MEI_CL_CONN_NOT_ALLOWED: return -EBUSY; default: return -EINVAL; } } @@ -150,8 +152,8 @@ void mei_hbm_cl_hdr(struct mei_cl *cl, u8 hbm_cmd, void *buf, size_t len) memset(cmd, 0, len); cmd->hbm_cmd = hbm_cmd; - cmd->host_addr = cl->host_client_id; - cmd->me_addr = cl->me_client_id; + cmd->host_addr = mei_cl_host_addr(cl); + cmd->me_addr = mei_cl_me_id(cl); } /** @@ -188,8 +190,8 @@ int mei_hbm_cl_write(struct mei_device *dev, static inline bool mei_hbm_cl_addr_equal(struct mei_cl *cl, struct mei_hbm_cl_cmd *cmd) { - return cl->host_client_id == cmd->host_addr && - cl->me_client_id == cmd->me_addr; + return mei_cl_host_addr(cl) == cmd->host_addr && + mei_cl_me_id(cl) == cmd->me_addr; } /** @@ -279,7 +281,7 @@ int mei_hbm_start_req(struct mei_device *dev) return 0; } -/* +/** * mei_hbm_enum_clients_req - sends enumeration client request message. * * @dev: the device structure @@ -299,6 +301,7 @@ static int mei_hbm_enum_clients_req(struct mei_device *dev) enum_req = (struct hbm_host_enum_request *)dev->wr_msg.data; memset(enum_req, 0, len); enum_req->hbm_cmd = HOST_ENUM_REQ_CMD; + enum_req->allow_add = dev->hbm_f_dc_supported; ret = mei_write_message(dev, mei_hdr, dev->wr_msg.data); if (ret) { @@ -311,7 +314,7 @@ static int mei_hbm_enum_clients_req(struct mei_device *dev) return 0; } -/* +/** * mei_hbm_me_cl_add - add new me client to the list * * @dev: the device structure @@ -344,6 +347,180 @@ static int mei_hbm_me_cl_add(struct mei_device *dev, } /** + * mei_hbm_add_cl_resp - send response to fw on client add request + * + * @dev: the device structure + * @addr: me address + * @status: response status + * + * Return: 0 on success and < 0 on failure + */ +static int mei_hbm_add_cl_resp(struct mei_device *dev, u8 addr, u8 status) +{ + struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; + struct hbm_add_client_response *resp; + const size_t len = sizeof(struct hbm_add_client_response); + int ret; + + dev_dbg(dev->dev, "adding client response\n"); + + resp = (struct hbm_add_client_response *)dev->wr_msg.data; + + mei_hbm_hdr(mei_hdr, len); + memset(resp, 0, sizeof(struct hbm_add_client_response)); + + resp->hbm_cmd = MEI_HBM_ADD_CLIENT_RES_CMD; + resp->me_addr = addr; + resp->status = status; + + ret = mei_write_message(dev, mei_hdr, dev->wr_msg.data); + if (ret) + dev_err(dev->dev, "add client response write failed: ret = %d\n", + ret); + return ret; +} + +/** + * mei_hbm_fw_add_cl_req - request from the fw to add a client + * + * @dev: the device structure + * @req: add client request + * + * Return: 0 on success and < 0 on failure + */ +static int mei_hbm_fw_add_cl_req(struct mei_device *dev, + struct hbm_add_client_request *req) +{ + int ret; + u8 status = MEI_HBMS_SUCCESS; + + BUILD_BUG_ON(sizeof(struct hbm_add_client_request) != + sizeof(struct hbm_props_response)); + + ret = mei_hbm_me_cl_add(dev, (struct hbm_props_response *)req); + if (ret) + status = !MEI_HBMS_SUCCESS; + + return mei_hbm_add_cl_resp(dev, req->me_addr, status); +} + +/** + * mei_hbm_cl_notify_req - send notification request + * + * @dev: the device structure + * @cl: a client to disconnect from + * @start: true for start false for stop + * + * Return: 0 on success and -EIO on write failure + */ +int mei_hbm_cl_notify_req(struct mei_device *dev, + struct mei_cl *cl, u8 start) +{ + + struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; + struct hbm_notification_request *req; + const size_t len = sizeof(struct hbm_notification_request); + int ret; + + mei_hbm_hdr(mei_hdr, len); + mei_hbm_cl_hdr(cl, MEI_HBM_NOTIFY_REQ_CMD, dev->wr_msg.data, len); + + req = (struct hbm_notification_request *)dev->wr_msg.data; + req->start = start; + + ret = mei_write_message(dev, mei_hdr, dev->wr_msg.data); + if (ret) + dev_err(dev->dev, "notify request failed: ret = %d\n", ret); + + return ret; +} + +/** + * notify_res_to_fop - convert notification response to the proper + * notification FOP + * + * @cmd: client notification start response command + * + * Return: MEI_FOP_NOTIFY_START or MEI_FOP_NOTIFY_STOP; + */ +static inline enum mei_cb_file_ops notify_res_to_fop(struct mei_hbm_cl_cmd *cmd) +{ + struct hbm_notification_response *rs = + (struct hbm_notification_response *)cmd; + + return mei_cl_notify_req2fop(rs->start); +} + +/** + * mei_hbm_cl_notify_start_res - update the client state according + * notify start response + * + * @dev: the device structure + * @cl: mei host client + * @cmd: client notification start response command + */ +static void mei_hbm_cl_notify_start_res(struct mei_device *dev, + struct mei_cl *cl, + struct mei_hbm_cl_cmd *cmd) +{ + struct hbm_notification_response *rs = + (struct hbm_notification_response *)cmd; + + cl_dbg(dev, cl, "hbm: notify start response status=%d\n", rs->status); + + if (rs->status == MEI_HBMS_SUCCESS || + rs->status == MEI_HBMS_ALREADY_STARTED) { + cl->notify_en = true; + cl->status = 0; + } else { + cl->status = -EINVAL; + } +} + +/** + * mei_hbm_cl_notify_stop_res - update the client state according + * notify stop response + * + * @dev: the device structure + * @cl: mei host client + * @cmd: client notification stop response command + */ +static void mei_hbm_cl_notify_stop_res(struct mei_device *dev, + struct mei_cl *cl, + struct mei_hbm_cl_cmd *cmd) +{ + struct hbm_notification_response *rs = + (struct hbm_notification_response *)cmd; + + cl_dbg(dev, cl, "hbm: notify stop response status=%d\n", rs->status); + + if (rs->status == MEI_HBMS_SUCCESS || + rs->status == MEI_HBMS_NOT_STARTED) { + cl->notify_en = false; + cl->status = 0; + } else { + /* TODO: spec is not clear yet about other possible issues */ + cl->status = -EINVAL; + } +} + +/** + * mei_hbm_cl_notify - signal notification event + * + * @dev: the device structure + * @cmd: notification client message + */ +static void mei_hbm_cl_notify(struct mei_device *dev, + struct mei_hbm_cl_cmd *cmd) +{ + struct mei_cl *cl; + + cl = mei_hbm_cl_find_by_cmd(dev, cmd); + if (cl) + mei_cl_notify(cl); +} + +/** * mei_hbm_prop_req - request property for a single client * * @dev: the device structure @@ -392,7 +569,7 @@ static int mei_hbm_prop_req(struct mei_device *dev) return 0; } -/* +/** * mei_hbm_pg - sends pg command * * @dev: the device structure @@ -572,7 +749,7 @@ static void mei_hbm_cl_disconnect_res(struct mei_device *dev, struct mei_cl *cl, cl_dbg(dev, cl, "hbm: disconnect response status=%d\n", rs->status); if (rs->status == MEI_CL_DISCONN_SUCCESS) - cl->state = MEI_FILE_DISCONNECTED; + cl->state = MEI_FILE_DISCONNECT_REPLY; cl->status = 0; } @@ -610,8 +787,11 @@ static void mei_hbm_cl_connect_res(struct mei_device *dev, struct mei_cl *cl, if (rs->status == MEI_CL_CONN_SUCCESS) cl->state = MEI_FILE_CONNECTED; - else - cl->state = MEI_FILE_DISCONNECTED; + else { + cl->state = MEI_FILE_DISCONNECT_REPLY; + if (rs->status == MEI_CL_CONN_NOT_FOUND) + mei_me_cl_del(dev, cl->me_cl); + } cl->status = mei_cl_conn_status_to_errno(rs->status); } @@ -654,6 +834,12 @@ static void mei_hbm_cl_res(struct mei_device *dev, case MEI_FOP_DISCONNECT: mei_hbm_cl_disconnect_res(dev, cl, rs); break; + case MEI_FOP_NOTIFY_START: + mei_hbm_cl_notify_start_res(dev, cl, rs); + break; + case MEI_FOP_NOTIFY_STOP: + mei_hbm_cl_notify_stop_res(dev, cl, rs); + break; default: return; } @@ -680,8 +866,8 @@ static int mei_hbm_fw_disconnect_req(struct mei_device *dev, cl = mei_hbm_cl_find_by_cmd(dev, disconnect_req); if (cl) { - cl_dbg(dev, cl, "disconnect request received\n"); - cl->state = MEI_FILE_DISCONNECTED; + cl_dbg(dev, cl, "fw disconnect request received\n"); + cl->state = MEI_FILE_DISCONNECTING; cl->timer_count = 0; cb = mei_io_cb_init(cl, MEI_FOP_DISCONNECT_RSP, NULL); @@ -694,6 +880,79 @@ static int mei_hbm_fw_disconnect_req(struct mei_device *dev, } /** + * mei_hbm_pg_enter_res - PG enter response received + * + * @dev: the device structure. + * + * Return: 0 on success, -EPROTO on state mismatch + */ +static int mei_hbm_pg_enter_res(struct mei_device *dev) +{ + if (mei_pg_state(dev) != MEI_PG_OFF || + dev->pg_event != MEI_PG_EVENT_WAIT) { + dev_err(dev->dev, "hbm: pg entry response: state mismatch [%s, %d]\n", + mei_pg_state_str(mei_pg_state(dev)), dev->pg_event); + return -EPROTO; + } + + dev->pg_event = MEI_PG_EVENT_RECEIVED; + wake_up(&dev->wait_pg); + + return 0; +} + +/** + * mei_hbm_pg_resume - process with PG resume + * + * @dev: the device structure. + */ +void mei_hbm_pg_resume(struct mei_device *dev) +{ + pm_request_resume(dev->dev); +} +EXPORT_SYMBOL_GPL(mei_hbm_pg_resume); + +/** + * mei_hbm_pg_exit_res - PG exit response received + * + * @dev: the device structure. + * + * Return: 0 on success, -EPROTO on state mismatch + */ +static int mei_hbm_pg_exit_res(struct mei_device *dev) +{ + if (mei_pg_state(dev) != MEI_PG_ON || + (dev->pg_event != MEI_PG_EVENT_WAIT && + dev->pg_event != MEI_PG_EVENT_IDLE)) { + dev_err(dev->dev, "hbm: pg exit response: state mismatch [%s, %d]\n", + mei_pg_state_str(mei_pg_state(dev)), dev->pg_event); + return -EPROTO; + } + + switch (dev->pg_event) { + case MEI_PG_EVENT_WAIT: + dev->pg_event = MEI_PG_EVENT_RECEIVED; + wake_up(&dev->wait_pg); + break; + case MEI_PG_EVENT_IDLE: + /* + * If the driver is not waiting on this then + * this is HW initiated exit from PG. + * Start runtime pm resume sequence to exit from PG. + */ + dev->pg_event = MEI_PG_EVENT_RECEIVED; + mei_hbm_pg_resume(dev); + break; + default: + WARN(1, "hbm: pg exit response: unexpected pg event = %d\n", + dev->pg_event); + return -EPROTO; + } + + return 0; +} + +/** * mei_hbm_config_features - check what hbm features and commands * are supported by the fw * @@ -709,6 +968,17 @@ static void mei_hbm_config_features(struct mei_device *dev) if (dev->version.major_version == HBM_MAJOR_VERSION_PGI && dev->version.minor_version >= HBM_MINOR_VERSION_PGI) dev->hbm_f_pg_supported = 1; + + if (dev->version.major_version >= HBM_MAJOR_VERSION_DC) + dev->hbm_f_dc_supported = 1; + + /* disconnect on connect timeout instead of link reset */ + if (dev->version.major_version >= HBM_MAJOR_VERSION_DOT) + dev->hbm_f_dot_supported = 1; + + /* Notification Event Support */ + if (dev->version.major_version >= HBM_MAJOR_VERSION_EV) + dev->hbm_f_ev_supported = 1; } /** @@ -740,6 +1010,8 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) struct hbm_host_version_response *version_res; struct hbm_props_response *props_res; struct hbm_host_enum_response *enum_res; + struct hbm_add_client_request *add_cl_req; + int ret; struct mei_hbm_cl_cmd *cl_cmd; struct hbm_client_connect_request *disconnect_req; @@ -828,24 +1100,17 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) break; case MEI_PG_ISOLATION_ENTRY_RES_CMD: - dev_dbg(dev->dev, "power gate isolation entry response received\n"); - dev->pg_event = MEI_PG_EVENT_RECEIVED; - if (waitqueue_active(&dev->wait_pg)) - wake_up(&dev->wait_pg); + dev_dbg(dev->dev, "hbm: power gate isolation entry response received\n"); + ret = mei_hbm_pg_enter_res(dev); + if (ret) + return ret; break; case MEI_PG_ISOLATION_EXIT_REQ_CMD: - dev_dbg(dev->dev, "power gate isolation exit request received\n"); - dev->pg_event = MEI_PG_EVENT_RECEIVED; - if (waitqueue_active(&dev->wait_pg)) - wake_up(&dev->wait_pg); - else - /* - * If the driver is not waiting on this then - * this is HW initiated exit from PG. - * Start runtime pm resume sequence to exit from PG. - */ - pm_request_resume(dev->dev); + dev_dbg(dev->dev, "hbm: power gate isolation exit request received\n"); + ret = mei_hbm_pg_exit_res(dev); + if (ret) + return ret; break; case HOST_CLIENT_PROPERTIES_RES_CMD: @@ -937,6 +1202,39 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) return -EIO; } break; + + case MEI_HBM_ADD_CLIENT_REQ_CMD: + dev_dbg(dev->dev, "hbm: add client request received\n"); + /* + * after the host receives the enum_resp + * message clients may be added or removed + */ + if (dev->hbm_state <= MEI_HBM_ENUM_CLIENTS || + dev->hbm_state >= MEI_HBM_STOPPED) { + dev_err(dev->dev, "hbm: add client: state mismatch, [%d, %d]\n", + dev->dev_state, dev->hbm_state); + return -EPROTO; + } + add_cl_req = (struct hbm_add_client_request *)mei_msg; + ret = mei_hbm_fw_add_cl_req(dev, add_cl_req); + if (ret) { + dev_err(dev->dev, "hbm: add client: failed to send response %d\n", + ret); + return -EIO; + } + dev_dbg(dev->dev, "hbm: add client request processed\n"); + break; + + case MEI_HBM_NOTIFY_RES_CMD: + dev_dbg(dev->dev, "hbm: notify response received\n"); + mei_hbm_cl_res(dev, cl_cmd, notify_res_to_fop(cl_cmd)); + break; + + case MEI_HBM_NOTIFICATION_CMD: + dev_dbg(dev->dev, "hbm: notification\n"); + mei_hbm_cl_notify(dev, cl_cmd); + break; + default: BUG(); break; |