used code from https://github.com/devos50/qemu-ios/commit/8acdcfc48ad19ee6dfb30d7be3abcb6110cb2f25 it was on ipod_touch_1g branch but not on 2g
415 lines
14 KiB
C
415 lines
14 KiB
C
#include "hw/arm/ipod_touch_multitouch.h"
|
|
|
|
static void prepare_interface_version_response(IPodTouchMultitouchState *s) {
|
|
memset(s->out_buffer + 1, 0, 15);
|
|
|
|
// set the interface version
|
|
s->out_buffer[2] = MT_INTERFACE_VERSION;
|
|
|
|
// set the max packet size
|
|
s->out_buffer[3] = (MT_MAX_PACKET_SIZE & 0xFF);
|
|
s->out_buffer[4] = (MT_MAX_PACKET_SIZE >> 8) & 0xFF;
|
|
|
|
// compute and set the checksum
|
|
uint32_t checksum = 0;
|
|
for(int i = 0; i < 14; i++) {
|
|
checksum += s->out_buffer[i];
|
|
}
|
|
|
|
s->out_buffer[14] = (checksum & 0xFF);
|
|
s->out_buffer[15] = (checksum >> 8) & 0xFF;
|
|
}
|
|
|
|
static void prepare_cmd_status_response(IPodTouchMultitouchState *s) {
|
|
memset(s->out_buffer + 1, 0, 15);
|
|
|
|
// TODO we should probably set some CMD status here
|
|
|
|
// compute and set the checksum
|
|
uint32_t checksum = 0;
|
|
for(int i = 0; i < 14; i++) {
|
|
checksum += s->out_buffer[i];
|
|
}
|
|
|
|
s->out_buffer[14] = (checksum & 0xFF);
|
|
s->out_buffer[15] = (checksum >> 8) & 0xFF;
|
|
}
|
|
|
|
static void prepare_report_info_response(IPodTouchMultitouchState *s, uint8_t report_id) {
|
|
memset(s->out_buffer + 1, 0, 15);
|
|
|
|
// set the error
|
|
s->out_buffer[2] = 0;
|
|
|
|
// set the report length
|
|
uint32_t report_length = 0;
|
|
if(report_id == MT_REPORT_UNKNOWN1) {
|
|
report_length = MT_REPORT_UNKNOWN1_SIZE;
|
|
}
|
|
else if(report_id == MT_REPORT_FAMILY_ID) {
|
|
report_length = MT_REPORT_FAMILY_ID_SIZE;
|
|
}
|
|
else if(report_id == MT_REPORT_SENSOR_INFO) {
|
|
report_length = MT_REPORT_SENSOR_INFO_SIZE;
|
|
}
|
|
else if(report_id == MT_REPORT_SENSOR_REGION_DESC) {
|
|
report_length = MT_REPORT_SENSOR_REGION_DESC_SIZE;
|
|
}
|
|
else if(report_id == MT_REPORT_SENSOR_REGION_PARAM) {
|
|
report_length = MT_REPORT_SENSOR_REGION_PARAM_SIZE;
|
|
}
|
|
else if(report_id == MT_REPORT_SENSOR_DIMENSIONS) {
|
|
report_length = MT_REPORT_SENSOR_DIMENSIONS_SIZE;
|
|
}
|
|
else {
|
|
hw_error("Unknown report ID 0x%02x\n", report_id);
|
|
}
|
|
|
|
s->out_buffer[3] = (report_length & 0xFF);
|
|
s->out_buffer[4] = (report_length >> 8) & 0xFF;
|
|
|
|
// compute and set the checksum
|
|
uint32_t checksum = 0;
|
|
for(int i = 0; i < 14; i++) {
|
|
checksum += s->out_buffer[i];
|
|
}
|
|
|
|
s->out_buffer[14] = (checksum & 0xFF);
|
|
s->out_buffer[15] = (checksum >> 8) & 0xFF;
|
|
}
|
|
|
|
static void prepare_short_control_response(IPodTouchMultitouchState *s, uint8_t report_id) {
|
|
memset(s->out_buffer + 1, 0, 15);
|
|
|
|
if(report_id == MT_REPORT_FAMILY_ID) {
|
|
s->out_buffer[3] = MT_FAMILY_ID;
|
|
}
|
|
else if(report_id == MT_REPORT_SENSOR_INFO) {
|
|
s->out_buffer[3] = MT_ENDIANNESS;
|
|
s->out_buffer[4] = MT_SENSOR_ROWS;
|
|
s->out_buffer[5] = MT_SENSOR_COLUMNS;
|
|
s->out_buffer[6] = (MT_BCD_VERSION & 0xFF);
|
|
s->out_buffer[7] = (MT_BCD_VERSION >> 8) & 0xFF;
|
|
}
|
|
else if(report_id == MT_REPORT_SENSOR_REGION_DESC) {
|
|
s->out_buffer[3] = MT_SENSOR_REGION_DESC;
|
|
}
|
|
else if(report_id == MT_REPORT_SENSOR_REGION_PARAM) {
|
|
s->out_buffer[3] = MT_SENSOR_REGION_PARAM;
|
|
}
|
|
else if(report_id == MT_REPORT_SENSOR_DIMENSIONS) {
|
|
uint32_t *ob_int32 = (uint32_t *)&s->out_buffer[3];
|
|
ob_int32[0] = MT_SENSOR_SURFACE_WIDTH;
|
|
ob_int32[1] = MT_SENSOR_SURFACE_HEIGHT;
|
|
}
|
|
else {
|
|
hw_error("Unknown report ID 0x%02x\n", report_id);
|
|
}
|
|
|
|
// compute and set the checksum
|
|
uint32_t checksum = 0;
|
|
for(int i = 0; i < 14; i++) {
|
|
checksum += s->out_buffer[i];
|
|
}
|
|
|
|
s->out_buffer[14] = (checksum & 0xFF);
|
|
s->out_buffer[15] = (checksum >> 8) & 0xFF;
|
|
}
|
|
|
|
static uint32_t ipod_touch_multitouch_transfer(SSIPeripheral *dev, uint32_t value)
|
|
{
|
|
IPodTouchMultitouchState *s = IPOD_TOUCH_MULTITOUCH(dev);
|
|
|
|
//printf("<MULTITOUCH> Got value: 0x%02x\n", value);
|
|
|
|
if(s->cur_cmd == 0) {
|
|
//printf("Starting command 0x%02x\n", value);
|
|
// we're currently not in a command - start a new command
|
|
s->cur_cmd = value;
|
|
s->out_buffer = malloc(0x100);
|
|
s->out_buffer[0] = value; // the response header
|
|
s->buf_ind = 0;
|
|
s->in_buffer = malloc(0x100);
|
|
s->in_buffer_ind = 0;
|
|
|
|
if(value == 0x18) { // filler packet??
|
|
s->buf_size = 2;
|
|
s->out_buffer[1] = 0xE1;
|
|
}
|
|
else if(value == 0x1A) { // HBPP ACK
|
|
s->buf_size = 2;
|
|
if(s->hbpp_atn_ack_response[0] == 0 && s->hbpp_atn_ack_response[1] == 0) {
|
|
// return the default ACK response
|
|
s->out_buffer[0] = 0x4B;
|
|
s->out_buffer[1] = 0xC1;
|
|
}
|
|
else {
|
|
s->out_buffer[0] = s->hbpp_atn_ack_response[0];
|
|
s->out_buffer[1] = s->hbpp_atn_ack_response[1];
|
|
}
|
|
|
|
}
|
|
else if(value == 0x1C) { // read register
|
|
s->buf_size = 8;
|
|
memset(s->out_buffer, 0, 8); // just return zeros
|
|
}
|
|
else if(value == 0x1D) { // execute
|
|
s->buf_size = 12;
|
|
memset(s->out_buffer, 0, 12); // just return zeros
|
|
}
|
|
else if(value == 0x1F) { // calibration
|
|
s->buf_size = 2;
|
|
s->out_buffer[1] = 0x0;
|
|
}
|
|
else if(value == 0x1E) { // write register
|
|
s->buf_size = 16;
|
|
memset(s->out_buffer, 0, 16); // just return zeros
|
|
}
|
|
else if(value == 0x1F) { // calibration
|
|
s->buf_size = 2;
|
|
s->out_buffer[1] = 0x0;
|
|
}
|
|
else if(value == MT_CMD_HBPP_DATA_PACKET) {
|
|
s->buf_size = 20; // should be enough initially, until we get the packet length
|
|
memset(s->out_buffer + 1, 0, 20 - 1); // just return zeros
|
|
}
|
|
else if(value == 0x47) { // unknown command, probably used to clear the interrupt
|
|
s->buf_size = 2;
|
|
}
|
|
else if(value == MT_CMD_GET_CMD_STATUS) {
|
|
s->buf_size = 16;
|
|
prepare_cmd_status_response(s);
|
|
}
|
|
else if(value == MT_CMD_GET_INTERFACE_VERSION) {
|
|
s->buf_size = 16;
|
|
prepare_interface_version_response(s);
|
|
}
|
|
else if(value == MT_CMD_GET_REPORT_INFO) {
|
|
s->buf_size = 16;
|
|
}
|
|
else if(value == MT_CMD_SHORT_CONTROL_WRITE) {
|
|
s->buf_size = 16;
|
|
}
|
|
else if(value == MT_CMD_SHORT_CONTROL_READ) {
|
|
s->buf_size = 16;
|
|
}
|
|
else if(value == MT_CMD_FRAME_READ) {
|
|
s->buf_size = sizeof(MTFrame);
|
|
free(s->out_buffer);
|
|
s->out_buffer = (uint8_t *) s->next_frame;
|
|
}
|
|
else {
|
|
hw_error("Unknown command 0x%02x!", value);
|
|
}
|
|
}
|
|
|
|
s->in_buffer[s->in_buffer_ind] = value;
|
|
s->in_buffer_ind++;
|
|
|
|
if(s->cur_cmd == MT_CMD_HBPP_DATA_PACKET && s->in_buffer_ind == 10) {
|
|
// verify the header checksum
|
|
uint32_t checksum = 0;
|
|
for(int i = 2; i < 8; i++) {
|
|
checksum += s->in_buffer[i];
|
|
}
|
|
|
|
if(checksum != (s->in_buffer[8] << 8 | s->in_buffer[9])) {
|
|
hw_error("HBPP data header checksum doesn't match!");
|
|
}
|
|
|
|
uint32_t data_len = (s->in_buffer[2] << 10) | (s->in_buffer[3] << 2) + 5;
|
|
// extend the lengths of the in/out buffers
|
|
free(s->in_buffer);
|
|
s->in_buffer = malloc(data_len + 0x10);
|
|
|
|
free(s->out_buffer);
|
|
s->out_buffer = malloc(data_len);
|
|
memset(s->out_buffer, 0, data_len);
|
|
s->buf_size = data_len;
|
|
s->buf_ind = 0;
|
|
}
|
|
else if(s->cur_cmd == MT_CMD_GET_REPORT_INFO && s->in_buffer_ind == 2) {
|
|
prepare_report_info_response(s, s->in_buffer[1]);
|
|
}
|
|
else if(s->cur_cmd == MT_CMD_SHORT_CONTROL_WRITE && s->in_buffer_ind == 16) {
|
|
// TODO we should persist the report here!
|
|
}
|
|
else if(s->cur_cmd == MT_CMD_SHORT_CONTROL_READ && s->in_buffer_ind == 2) {
|
|
prepare_short_control_response(s, s->in_buffer[1]);
|
|
}
|
|
|
|
// TODO process register writes!
|
|
|
|
uint8_t ret_val = s->out_buffer[s->buf_ind];
|
|
s->buf_ind++;
|
|
|
|
//printf("<MULTITOUCH> Got value: 0x%02x, returning 0x%02x (index: %d, buffer length: %d)\n", value, ret_val, s->buf_ind, s->buf_size);
|
|
|
|
if(s->buf_ind == s->buf_size) {
|
|
//printf("Finished command 0x%02x\n", s->cur_cmd);
|
|
|
|
if(s->cur_cmd == 0x1E) {
|
|
// make sure we return a success status on the next HBPP ACK
|
|
s->hbpp_atn_ack_response[0] = 0x4A;
|
|
s->hbpp_atn_ack_response[1] = 0xD1;
|
|
}
|
|
|
|
// we're done with the command
|
|
s->cur_cmd = 0;
|
|
s->buf_size = 0;
|
|
//free(s->out_buffer);
|
|
//free(s->in_buffer);
|
|
}
|
|
|
|
return ret_val;
|
|
}
|
|
|
|
static MTFrame *get_frame(IPodTouchMultitouchState *s, uint8_t event, float x, float y, uint16_t radius1, uint16_t radius2, uint16_t radius3, uint16_t contactDensity) {
|
|
MTFrame *frame = calloc(sizeof(MTFrame), sizeof(uint8_t *));
|
|
|
|
uint16_t data_len = sizeof(MTFrameHeader) + sizeof(FingerData) + 2;
|
|
|
|
/// create the frame length packet
|
|
frame->frame_length.cmd = MT_CMD_FRAME_READ;
|
|
frame->frame_length.length1 = (data_len & 0xFF);
|
|
frame->frame_length.length2 = (data_len >> 8) & 0xFF;
|
|
|
|
uint16_t checksum = 0;
|
|
for(int i = 0; i < 14; i++) {
|
|
checksum += ((uint8_t *) &frame->frame_length)[i];
|
|
}
|
|
frame->frame_length.checksum1 = (checksum & 0xFF);
|
|
frame->frame_length.checksum2 = (checksum >> 8) & 0xFF;
|
|
|
|
// create the frame packet
|
|
frame->frame_packet.cmd = MT_CMD_FRAME_READ;
|
|
frame->frame_packet.length1 = (data_len & 0xFF);
|
|
frame->frame_packet.length2 = (data_len >> 8) & 0xFF;
|
|
|
|
checksum = 0;
|
|
for(int i = 0; i < 4; i++) {
|
|
checksum += ((uint8_t *) &frame->frame_length)[i];
|
|
}
|
|
|
|
// the first five bytes have to sum up to 0.
|
|
frame->frame_packet.checksum_pad = 0xFF - (checksum & 0xFF) + 1;
|
|
|
|
frame->frame_packet.header.type = MT_FRAME_TYPE_PATH;
|
|
frame->frame_packet.header.frameNum = s->frame_counter;
|
|
frame->frame_packet.header.headerLen = sizeof(MTFrameHeader);
|
|
uint64_t elapsed_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / 1000000;
|
|
frame->frame_packet.header.timestamp = elapsed_ns;
|
|
frame->frame_packet.header.numFingers = 1;
|
|
frame->frame_packet.header.fingerDataLen = sizeof(FingerData);
|
|
|
|
// create the finger data
|
|
frame->finger_data.id = 1;
|
|
frame->finger_data.event = event;
|
|
frame->finger_data.unk_2 = 2;
|
|
frame->finger_data.unk_3 = 1;
|
|
|
|
// compute the velocity
|
|
int diff_x = (int)((x - s->prev_touch_x) * MT_INTERNAL_SENSOR_SURFACE_WIDTH);
|
|
int diff_y = (int)((x - s->prev_touch_y) * MT_INTERNAL_SENSOR_SURFACE_HEIGHT);
|
|
frame->finger_data.velX = diff_x / (elapsed_ns + 1 - s->last_frame_timestamp) * 1000;
|
|
frame->finger_data.velY = diff_y / (elapsed_ns + 1 - s->last_frame_timestamp) * 1000;
|
|
|
|
frame->finger_data.x = (int)(x * MT_INTERNAL_SENSOR_SURFACE_WIDTH);
|
|
frame->finger_data.y = (int)(y * MT_INTERNAL_SENSOR_SURFACE_HEIGHT);
|
|
frame->finger_data.radius1 = radius1;
|
|
frame->finger_data.radius2 = radius2;
|
|
frame->finger_data.radius3 = radius3;
|
|
frame->finger_data.angle = 19317;
|
|
frame->finger_data.contactDensity = contactDensity; // seems to be a medium press
|
|
|
|
// compute the checksum over the frame data.
|
|
checksum = 0;
|
|
for(int i = 0; i < data_len - 2; i++) {
|
|
checksum += ((uint8_t *) &frame->frame_packet.header)[i];
|
|
}
|
|
frame->checksum1 = (checksum & 0xFF);
|
|
frame->checksum2 = (checksum >> 8) & 0xFF;
|
|
|
|
s->last_frame_timestamp = elapsed_ns;
|
|
s->frame_counter += 1;
|
|
|
|
return frame;
|
|
}
|
|
|
|
static void ipod_touch_multitouch_inform_frame_ready(IPodTouchMultitouchState *s) {
|
|
s->sysic->gpio_int_status[3] |= (1 << 13); // the multitouch interrupt bit is in group 3 (32 interrupts per group), and the 13th of the 3th group
|
|
qemu_irq_raise(s->sysic->gpio_irqs[3]);
|
|
}
|
|
|
|
void ipod_touch_multitouch_on_touch(IPodTouchMultitouchState *s) {
|
|
s->touch_down = true;
|
|
|
|
s->next_frame = get_frame(s, MT_EVENT_TOUCH_START, s->touch_x, s->touch_y, 100, 660, 580, 150);
|
|
ipod_touch_multitouch_inform_frame_ready(s);
|
|
|
|
timer_mod(s->touch_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + NANOSECONDS_PER_SECOND / 10);
|
|
}
|
|
|
|
void ipod_touch_multitouch_on_release(IPodTouchMultitouchState *s) {
|
|
s->next_frame = get_frame(s, MT_EVENT_TOUCH_ENDED, s->touch_x, s->touch_y, 0, 0, 0, 0);
|
|
s->touch_down = false;
|
|
ipod_touch_multitouch_inform_frame_ready(s);
|
|
|
|
timer_del(s->touch_timer);
|
|
timer_mod(s->touch_end_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + NANOSECONDS_PER_SECOND / 10);
|
|
}
|
|
|
|
static void touch_timer_tick(void *opaque)
|
|
{
|
|
IPodTouchMultitouchState *s = (IPodTouchMultitouchState *)opaque;
|
|
|
|
s->next_frame = get_frame(s, MT_EVENT_TOUCH_MOVED, s->touch_x, s->touch_y, 100, 660, 580, 150);
|
|
ipod_touch_multitouch_inform_frame_ready(s);
|
|
|
|
if(s->touch_down) {
|
|
// reschedule the timer
|
|
timer_mod(s->touch_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + NANOSECONDS_PER_SECOND / 10);
|
|
}
|
|
}
|
|
|
|
static void touch_end_timer_tick(void *opaque)
|
|
{
|
|
IPodTouchMultitouchState *s = (IPodTouchMultitouchState *)opaque;
|
|
s->next_frame = get_frame(s, MT_EVENT_TOUCH_FULL_END, s->touch_x, s->touch_y, 0, 0, 0, 0);
|
|
s->touch_down = false;
|
|
ipod_touch_multitouch_inform_frame_ready(s);
|
|
}
|
|
|
|
static void ipod_touch_multitouch_realize(SSIPeripheral *d, Error **errp)
|
|
{
|
|
IPodTouchMultitouchState *s = IPOD_TOUCH_MULTITOUCH(d);
|
|
memset(s->hbpp_atn_ack_response, 0, 2);
|
|
s->touch_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, touch_timer_tick, s);
|
|
s->touch_end_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, touch_end_timer_tick, s);
|
|
|
|
s->prev_touch_x = 0;
|
|
s->prev_touch_y = 0;
|
|
s->last_frame_timestamp = 0;
|
|
}
|
|
|
|
static void ipod_touch_multitouch_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
SSIPeripheralClass *k = SSI_PERIPHERAL_CLASS(klass);
|
|
k->realize = ipod_touch_multitouch_realize;
|
|
k->transfer = ipod_touch_multitouch_transfer;
|
|
}
|
|
|
|
static const TypeInfo ipod_touch_multitouch_type_info = {
|
|
.name = TYPE_IPOD_TOUCH_MULTITOUCH,
|
|
.parent = TYPE_SSI_PERIPHERAL,
|
|
.instance_size = sizeof(IPodTouchMultitouchState),
|
|
.class_init = ipod_touch_multitouch_class_init,
|
|
};
|
|
|
|
static void ipod_touch_multitouch_register_types(void)
|
|
{
|
|
type_register_static(&ipod_touch_multitouch_type_info);
|
|
}
|
|
|
|
type_init(ipod_touch_multitouch_register_types)
|