1
0
forked from drath/libratbag

hidpp10: implement full write of profiles

The complexity when writing the profile to ROM at first allows:
- to prevent the user to temper with a non full profile
- in case of a USB failure (disconnect or loss of power for the batteries)
  we don't use partially overwrite the profile.

Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
This commit is contained in:
Benjamin Tissoires
2015-12-11 15:49:22 +01:00
parent 390a82bce9
commit eb6e312bf3
2 changed files with 325 additions and 4 deletions

View File

@@ -1208,6 +1208,7 @@ hidpp10_set_profile(struct hidpp10_device *dev, int8_t number, struct hidpp10_pr
int count = 0;
unsigned i;
uint16_t crc;
uint8_t page;
hidpp_log_raw(&dev->base, "Fetching profile %d\n", number);
@@ -1287,11 +1288,63 @@ hidpp10_set_profile(struct hidpp10_device *dev, int8_t number, struct hidpp10_pr
crc = hidpp_crc_ccitt(page_data, HIDPP10_PAGE_SIZE - 2);
hidpp_set_unaligned_be_u16(&page_data[HIDPP10_PAGE_SIZE - 2], crc);
for (i = 0; i < HIDPP10_PAGE_SIZE / 16; i++) {
hidpp_log_buf_error(&dev->base, "new profile: ", page_data + i * 16, 16);
}
/*
* writing the data in several steps to prevent shroedinger state
* if the device is unplugged while uploading the data:
* - first disable the current profile by using the factory one
* (this prevents the user to change the current profile by pressing
* a button)
* - then upload in RAM half of the data
* - erase the portion of the flash we are overwriting
* - write the uploaded data to the flash
* - upload the rest
* - write the uploaded data to the flash
* - switch to the new profile
*/
res = hidpp10_set_internal_current_profile(dev, 0, PROFILE_TYPE_FACTORY);
if (res < 0)
return res;
return 0;
res = hidpp10_send_hot_payload(dev,
0x00, 0x0000, /* destination: RAM */
page_data,
HIDPP10_PAGE_SIZE / 2);
if (res < 0)
return res;
page = directory[number].page;
/* according to the spec, a profile can have an offset.
* For all the devices we know, they all start at 0x0000 */
res = hidpp10_erase_memory(dev, page);
if (res < 0)
return res;
res = hidpp10_write_flash(dev,
0x00, 0x0000,
page, 0x0000,
HIDPP10_PAGE_SIZE / 2);
if (res < 0)
return res;
res = hidpp10_send_hot_payload(dev,
0x00, 0x0000, /* destination: RAM */
page_data + HIDPP10_PAGE_SIZE / 2,
HIDPP10_PAGE_SIZE / 2);
if (res < 0)
return res;
res = hidpp10_write_flash(dev,
0x00, 0x0000,
page, HIDPP10_PAGE_SIZE / 2,
HIDPP10_PAGE_SIZE / 2);
if (res < 0)
return res;
res = hidpp10_set_internal_current_profile(dev, number, PROFILE_TYPE_INDEX);
if (res < 0)
return res;
return res;
}
/* -------------------------------------------------------------------------- */
@@ -1628,6 +1681,247 @@ hidpp10_set_usb_refresh_rate(struct hidpp10_device *dev,
return hidpp10_request_command(dev, &refresh);
}
/* -------------------------------------------------------------------------- */
/* 0xA0: Generic Memory Management */
/* -------------------------------------------------------------------------- */
#define __CMD_GENERIC_MEMORY_MANAGEMENT 0xA0
#define CMD_ERASE_MEMORY(idx, page) { \
.msg = { \
.report_id = REPORT_ID_LONG, \
.device_idx = idx, \
.sub_id = SET_LONG_REGISTER_REQ, \
.address = __CMD_GENERIC_MEMORY_MANAGEMENT, \
.string = {0x02, 0x00, \
0x00, 0x00, 0x00, 0x00, \
page, 0x00, 0x00, 0x00,\
0x00, 0x00, 0x00, 0x00}, \
} \
}
#define CMD_WRITE_FLASH(idx, src_page, src_woffset, dst_page, dst_woffset, size) { \
.msg = { \
.report_id = REPORT_ID_LONG, \
.device_idx = idx, \
.sub_id = SET_LONG_REGISTER_REQ, \
.address = __CMD_GENERIC_MEMORY_MANAGEMENT, \
.string = {0x03, 0x00, \
src_page, src_woffset, 0x00, 0x00, \
dst_page, dst_woffset, 0x00, 0x00,\
size >> 8, size & 0xFF}, \
} \
}
int
hidpp10_erase_memory(struct hidpp10_device *dev, uint8_t page)
{
unsigned idx = dev->index;
union hidpp10_message erase = CMD_ERASE_MEMORY(idx, page);
hidpp_log_raw(&dev->base, "Erasing page 0x%02x\n", page);
return hidpp10_request_command(dev, &erase);
}
int
hidpp10_write_flash(struct hidpp10_device *dev,
uint8_t src_page,
uint16_t src_offset,
uint8_t dst_page,
uint16_t dst_offset,
uint16_t size)
{
unsigned idx = dev->index;
union hidpp10_message copy = CMD_WRITE_FLASH(idx,
src_page, src_offset / 2,
dst_page, dst_offset / 2,
size);
if ((src_offset % 2 != 0) || (dst_offset % 2 != 0)) {
hidpp_log_error(&dev->base, "Accessing memory with odd offset is not supported.\n");
return -EINVAL;
}
hidpp_log_raw(&dev->base, "Copying %d bytes from (0x%02x,0x%04x) to (0x%02x,0x%04x)\n",
size,
src_page, src_offset,
dst_page, dst_offset);
return hidpp10_request_command(dev, &copy);
}
/* -------------------------------------------------------------------------- */
/* 0x9x: HOT payload */
/* 0xA1: HOT Control Register */
/* -------------------------------------------------------------------------- */
#define __CMD_HOT_CONTROL 0xA1
#define CMD_HOT_RESET(idx) { \
.msg = { \
.report_id = REPORT_ID_SHORT, \
.device_idx = idx, \
.sub_id = SET_REGISTER_REQ, \
.address = __CMD_HOT_CONTROL, \
.parameters = {0x01, 0x00, 0x00 }, \
} \
}
#define HOT_NOTIFICATION 0x50
#define HOT_WRITE 0x92
#define HOT_CONTINUE 0x93
static int
hidpp10_hot_ctrl_reset(struct hidpp10_device *dev)
{
unsigned idx = dev->index;
union hidpp10_message ctrl_reset = CMD_HOT_RESET(idx);
return hidpp10_request_command(dev, &ctrl_reset);
}
static int
hidpp10_hot_request_command(struct hidpp10_device *dev, uint8_t data[LONG_MESSAGE_LENGTH])
{
uint8_t read_buffer[LONG_MESSAGE_LENGTH] = {0};
int ret;
uint8_t id = data[3];
if ((data[0] != REPORT_ID_LONG) ||
((data[2] != HOT_WRITE) && (data[2] != HOT_CONTINUE)))
return -EINVAL;
/* Send the message to the Device */
ret = hidpp_write_command(&dev->base, data, LONG_MESSAGE_LENGTH);
if (ret)
goto out_err;
/*
* Now read the answers from the device:
* loop until we get the actual answer or an error code.
*/
do {
ret = hidpp_read_response(&dev->base, read_buffer, LONG_MESSAGE_LENGTH);
/* Wait and retry if the USB timed out */
if (ret == -ETIMEDOUT) {
msleep(10);
ret = hidpp_read_response(&dev->base, read_buffer, LONG_MESSAGE_LENGTH);
}
/* actual answer */
if (read_buffer[2] == HOT_NOTIFICATION)
break;
} while (ret > 0);
if (ret < 0) {
hidpp_log_error(&dev->base, " USB error: %s (%d)\n", strerror(-ret), -ret);
perror("write");
goto out_err;
}
if (read_buffer[4] != id) {
ret = -EPROTO;
hidpp_log_error(&dev->base, " Protocol error: ids do not match.\n");
perror("write");
goto out_err;
}
ret = 0;
out_err:
return ret;
}
struct hot_header {
uint8_t id;
uint8_t page;
uint8_t offset;
uint16_t zero;
uint16_t size;
uint16_t zero1;
} __attribute__ ((__packed__));
static int
hidpp10_send_hot_chunk(struct hidpp10_device *dev,
uint8_t index,
bool first,
uint8_t dst_page,
uint16_t dst_offset,
uint8_t *data,
unsigned size)
{
struct hot_header header = {0};
uint8_t buffer[LONG_MESSAGE_LENGTH] = {0};
unsigned offset = 0;
unsigned count;
int res;
buffer[offset++] = REPORT_ID_LONG;
buffer[offset++] = dev->index;
if (first) {
if (dst_offset % 2 != 0) {
hidpp_log_error(&dev->base, "Writing memory with odd offset is not supported.\n");
return -EINVAL;
}
buffer[offset++] = HOT_WRITE;
buffer[offset++] = index;
header.id = 0x01;
header.page = dst_page;
header.offset = dst_offset / 2;
header.size = hidpp_cpu_to_be_u16(size);
memcpy(&buffer[offset], &header, sizeof(header));
offset += sizeof(header);
} else {
buffer[offset++] = HOT_CONTINUE;
buffer[offset++] = index;
}
count = min(LONG_MESSAGE_LENGTH - offset, size);
if (count <= 0)
return -EINVAL;
memcpy(&buffer[offset], data, count);
res = hidpp10_hot_request_command(dev, buffer);
if (res < 0)
return res;
return count;
}
int
hidpp10_send_hot_payload(struct hidpp10_device *dev,
uint8_t dst_page,
uint16_t dst_offset,
uint8_t *data,
unsigned size)
{
bool first = true;
unsigned int count = 0;
unsigned int index = 0;
int res;
res = hidpp10_hot_ctrl_reset(dev);
if (res < 0)
return res;
do {
res = hidpp10_send_hot_chunk(dev, index, first,
dst_page, dst_offset,
data + count,
size - count);
if (res < 0)
return res;
first = false;
count += res;
index++;
} while (size > count);
return 0;
}
/* -------------------------------------------------------------------------- */
/* 0xA2: Read Sector */
/* -------------------------------------------------------------------------- */

View File

@@ -532,6 +532,33 @@ int
hidpp10_set_usb_refresh_rate(struct hidpp10_device *dev,
uint16_t rate);
/* -------------------------------------------------------------------------- */
/* 0xA0: Generic Memory Management */
/* -------------------------------------------------------------------------- */
int
hidpp10_erase_memory(struct hidpp10_device *dev, uint8_t page);
int
hidpp10_write_flash(struct hidpp10_device *dev,
uint8_t src_page,
uint16_t src_offset,
uint8_t dst_page,
uint16_t dst_offset,
uint16_t size);
/* -------------------------------------------------------------------------- */
/* 0x9x: HOT payload */
/* 0xA1: HOT Control Register */
/* -------------------------------------------------------------------------- */
int
hidpp10_send_hot_payload(struct hidpp10_device *dev,
uint8_t dst_page,
uint16_t dst_offset,
uint8_t *data,
unsigned size);
/* -------------------------------------------------------------------------- */
/* 0xA2: Read Sector */
/* -------------------------------------------------------------------------- */