diff options
-rw-r--r-- | drivers/char/ipmi/ipmi_ssif.c | 209 |
1 files changed, 159 insertions, 50 deletions
diff --git a/drivers/char/ipmi/ipmi_ssif.c b/drivers/char/ipmi/ipmi_ssif.c index 764d305a264d..137b03892806 100644 --- a/drivers/char/ipmi/ipmi_ssif.c +++ b/drivers/char/ipmi/ipmi_ssif.c @@ -61,6 +61,7 @@ #define SSIF_IPMI_REQUEST 2 #define SSIF_IPMI_MULTI_PART_REQUEST_START 6 #define SSIF_IPMI_MULTI_PART_REQUEST_MIDDLE 7 +#define SSIF_IPMI_MULTI_PART_REQUEST_END 8 #define SSIF_IPMI_RESPONSE 3 #define SSIF_IPMI_MULTI_PART_RESPONSE_MIDDLE 9 @@ -272,6 +273,7 @@ struct ssif_info { /* Info from SSIF cmd */ unsigned char max_xmit_msg_size; unsigned char max_recv_msg_size; + bool cmd8_works; /* See test_multipart_messages() for details. */ unsigned int multi_support; int supports_pec; @@ -885,32 +887,33 @@ static void msg_written_handler(struct ssif_info *ssif_info, int result, * in the SSIF_MULTI_n_PART case in the probe function * for details on the intricacies of this. */ - int left; + int left, to_write; unsigned char *data_to_send; + unsigned char cmd; ssif_inc_stat(ssif_info, sent_messages_parts); left = ssif_info->multi_len - ssif_info->multi_pos; - if (left > 32) - left = 32; + to_write = left; + if (to_write > 32) + to_write = 32; /* Length byte. */ - ssif_info->multi_data[ssif_info->multi_pos] = left; + ssif_info->multi_data[ssif_info->multi_pos] = to_write; data_to_send = ssif_info->multi_data + ssif_info->multi_pos; - ssif_info->multi_pos += left; - if (left < 32) - /* - * Write is finished. Note that we must end - * with a write of less than 32 bytes to - * complete the transaction, even if it is - * zero bytes. - */ + ssif_info->multi_pos += to_write; + cmd = SSIF_IPMI_MULTI_PART_REQUEST_MIDDLE; + if (ssif_info->cmd8_works) { + if (left == to_write) { + cmd = SSIF_IPMI_MULTI_PART_REQUEST_END; + ssif_info->multi_data = NULL; + } + } else if (to_write < 32) { ssif_info->multi_data = NULL; + } rv = ssif_i2c_send(ssif_info, msg_written_handler, - I2C_SMBUS_WRITE, - SSIF_IPMI_MULTI_PART_REQUEST_MIDDLE, - data_to_send, - I2C_SMBUS_BLOCK_DATA); + I2C_SMBUS_WRITE, cmd, + data_to_send, I2C_SMBUS_BLOCK_DATA); if (rv < 0) { /* request failed, just return the error. */ ssif_inc_stat(ssif_info, send_errors); @@ -1242,6 +1245,24 @@ static int ssif_remove(struct i2c_client *client) return 0; } +static int read_response(struct i2c_client *client, unsigned char *resp) +{ + int ret = -ENODEV, retry_cnt = SSIF_RECV_RETRIES; + + while (retry_cnt > 0) { + ret = i2c_smbus_read_block_data(client, SSIF_IPMI_RESPONSE, + resp); + if (ret > 0) + break; + msleep(SSIF_MSG_MSEC); + retry_cnt--; + if (retry_cnt <= 0) + break; + } + + return ret; +} + static int do_cmd(struct i2c_client *client, int len, unsigned char *msg, int *resp_len, unsigned char *resp) { @@ -1258,26 +1279,16 @@ static int do_cmd(struct i2c_client *client, int len, unsigned char *msg, return -ENODEV; } - ret = -ENODEV; - retry_cnt = SSIF_RECV_RETRIES; - while (retry_cnt > 0) { - ret = i2c_smbus_read_block_data(client, SSIF_IPMI_RESPONSE, - resp); - if (ret > 0) - break; - msleep(SSIF_MSG_MSEC); - retry_cnt--; - if (retry_cnt <= 0) - break; - } - + ret = read_response(client, resp); if (ret > 0) { /* Validate that the response is correct. */ if (ret < 3 || (resp[0] != (msg[0] | (1 << 2))) || (resp[1] != msg[1])) ret = -EINVAL; - else { + else if (ret > IPMI_MAX_MSG_LENGTH) { + ret = -E2BIG; + } else { *resp_len = ret; ret = 0; } @@ -1389,6 +1400,121 @@ static int find_slave_address(struct i2c_client *client, int slave_addr) return slave_addr; } +static int start_multipart_test(struct i2c_client *client, + unsigned char *msg, bool do_middle) +{ + int retry_cnt = SSIF_SEND_RETRIES, ret; + +retry_write: + ret = i2c_smbus_write_block_data(client, + SSIF_IPMI_MULTI_PART_REQUEST_START, + 32, msg); + if (ret) { + retry_cnt--; + if (retry_cnt > 0) + goto retry_write; + dev_err(&client->dev, "Could not write multi-part start, though the BMC said it could handle it. Just limit sends to one part.\n"); + return ret; + } + + if (!do_middle) + return 0; + + ret = i2c_smbus_write_block_data(client, + SSIF_IPMI_MULTI_PART_REQUEST_MIDDLE, + 32, msg + 32); + if (ret) { + dev_err(&client->dev, "Could not write multi-part middle, though the BMC said it could handle it. Just limit sends to one part.\n"); + return ret; + } + + return 0; +} + +static void test_multipart_messages(struct i2c_client *client, + struct ssif_info *ssif_info, + unsigned char *resp) +{ + unsigned char msg[65]; + int ret; + bool do_middle; + + if (ssif_info->max_xmit_msg_size <= 32) + return; + + do_middle = ssif_info->max_xmit_msg_size > 63; + + memset(msg, 0, sizeof(msg)); + msg[0] = IPMI_NETFN_APP_REQUEST << 2; + msg[1] = IPMI_GET_DEVICE_ID_CMD; + + /* + * The specification is all messed up dealing with sending + * multi-part messages. Per what the specification says, it + * is impossible to send a message that is a multiple of 32 + * bytes, except for 32 itself. It talks about a "start" + * transaction (cmd=6) that must be 32 bytes, "middle" + * transaction (cmd=7) that must be 32 bytes, and an "end" + * transaction. The "end" transaction is shown as cmd=7 in + * the text, but if that's the case there is no way to + * differentiate between a middle and end part except the + * length being less than 32. But there is a table at the far + * end of the section (that I had never noticed until someone + * pointed it out to me) that mentions it as cmd=8. + * + * After some thought, I think the example is wrong and the + * end transaction should be cmd=8. But some systems don't + * implement cmd=8, they use a zero-length end transaction, + * even though that violates the SMBus specification. + * + * So, to work around this, this code tests if cmd=8 works. + * If it does, then we use that. If not, it tests zero- + * byte end transactions. If that works, good. If not, + * we only allow 63-byte transactions max. + */ + + ret = start_multipart_test(client, msg, do_middle); + if (ret) + goto out_no_multi_part; + + ret = i2c_smbus_write_block_data(client, + SSIF_IPMI_MULTI_PART_REQUEST_END, + 1, msg + 64); + + if (!ret) + ret = read_response(client, resp); + + if (ret > 0) { + /* End transactions work, we are good. */ + ssif_info->cmd8_works = true; + return; + } + + ret = start_multipart_test(client, msg, do_middle); + if (ret) { + dev_err(&client->dev, "Second multipart test failed.\n"); + goto out_no_multi_part; + } + + ret = i2c_smbus_write_block_data(client, + SSIF_IPMI_MULTI_PART_REQUEST_MIDDLE, + 0, msg + 64); + if (!ret) + ret = read_response(client, resp); + if (ret > 0) + /* Zero-size end parts work, use those. */ + return; + + /* Limit to 63 bytes and use a short middle command to mark the end. */ + if (ssif_info->max_xmit_msg_size > 63) + ssif_info->max_xmit_msg_size = 63; + return; + +out_no_multi_part: + ssif_info->max_xmit_msg_size = 32; + return; +} + /* * Global enables we care about. */ @@ -1475,26 +1601,7 @@ static int ssif_probe(struct i2c_client *client, const struct i2c_device_id *id) break; case SSIF_MULTI_n_PART: - /* - * The specification is rather confusing at - * this point, but I think I understand what - * is meant. At least I have a workable - * solution. With multi-part messages, you - * cannot send a message that is a multiple of - * 32-bytes in length, because the start and - * middle messages are 32-bytes and the end - * message must be at least one byte. You - * can't fudge on an extra byte, that would - * screw up things like fru data writes. So - * we limit the length to 63 bytes. That way - * a 32-byte message gets sent as a single - * part. A larger message will be a 32-byte - * start and the next message is always going - * to be 1-31 bytes in length. Not ideal, but - * it should work. - */ - if (ssif_info->max_xmit_msg_size > 63) - ssif_info->max_xmit_msg_size = 63; + /* We take whatever size given, but do some testing. */ break; default: @@ -1513,6 +1620,8 @@ static int ssif_probe(struct i2c_client *client, const struct i2c_device_id *id) ssif_info->supports_pec = 0; } + test_multipart_messages(client, ssif_info, resp); + /* Make sure the NMI timeout is cleared. */ msg[0] = IPMI_NETFN_APP_REQUEST << 2; msg[1] = IPMI_CLEAR_MSG_FLAGS_CMD; |