#include "hw/arm/ipod_touch_multitouch.h" #include "qemu/log.h" #include "qemu/bswap.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 { qemu_log_mask(LOG_GUEST_ERROR, "%s: rapport inconnu 0x%02x\n", __func__, report_id); s->out_buffer[2] = 1; } 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) { stl_le_p(&s->out_buffer[3], MT_SENSOR_SURFACE_WIDTH); stl_le_p(&s->out_buffer[7], MT_SENSOR_SURFACE_HEIGHT); } else { qemu_log_mask(LOG_GUEST_ERROR, "%s: rapport inconnu 0x%02x\n", __func__, report_id); s->out_buffer[2] = 1; } // 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 MTFrame *get_frame(IPodTouchMultitouchState *s, uint8_t event, float x, float y, uint16_t radius1, uint16_t radius2, uint16_t radius3, uint16_t contactDensity); static void finish_transfer(IPodTouchMultitouchState *s) { g_clear_pointer(&s->in_buffer, g_free); g_clear_pointer(&s->out_buffer, g_free); s->cur_cmd = 0; s->buf_size = s->buf_ind = s->in_buffer_ind = 0; s->in_buffer_capacity = 0; } static uint32_t ipod_touch_multitouch_transfer(SSIPeripheral *dev, uint32_t value) { IPodTouchMultitouchState *s = IPOD_TOUCH_MULTITOUCH(dev); //printf(" 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 = g_malloc0(0x100); s->out_buffer[0] = value; // the response header s->buf_ind = 0; s->in_buffer = g_malloc0(0x100); s->in_buffer_capacity = 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 || value == MT_CMD_FRAME_READ_ALT) { s->buf_size = sizeof(MTFrameLengthPacket); g_free(s->out_buffer); /* La trame doit rester stable entre sa longueur et ses données, même si un événement hôte arrive pendant les lectures SPI. */ s->out_buffer = g_memdup2(s->read_frame ? s->read_frame : s->next_frame, sizeof(MTFrame)); } else { qemu_log_mask(LOG_GUEST_ERROR, "%s: commande inconnue 0x%02x\n", __func__, value); finish_transfer(s); return 0xff; } } if (s->buf_ind >= s->buf_size || s->in_buffer_ind >= s->in_buffer_capacity) { finish_transfer(s); return 0xff; } s->in_buffer[s->in_buffer_ind] = value; s->in_buffer_ind++; if ((s->cur_cmd == MT_CMD_FRAME_READ || s->cur_cmd == MT_CMD_FRAME_READ_ALT) && s->in_buffer_ind == 3) { /* Le troisième octet distingue longueur (0) et données (1). Chaque requête est une transaction complète : une lecture de longueur abandonnée pendant la veille ne décale pas la suivante. */ if (value == 1) { MTFrame *frame = (MTFrame *)s->out_buffer; /* Le deuxième octet est déjà émis avant de connaître le sélecteur. Il est ignoré dans les données, mais participe au checksum. */ uint8_t second = s->out_buffer[1]; frame->frame_packet.unused1 = second; frame->frame_packet.checksum_pad = -(frame->frame_packet.cmd + second + frame->frame_packet.length1 + frame->frame_packet.length2); memmove(s->out_buffer, &frame->frame_packet, sizeof(MTFrame) - sizeof(MTFrameLengthPacket)); s->buf_size = sizeof(MTFrame) - sizeof(MTFrameLengthPacket); g_clear_pointer(&s->read_frame, g_free); } else if (value == 0) { g_free(s->read_frame); s->read_frame = g_memdup2(s->next_frame, sizeof(MTFrame)); memcpy(s->out_buffer, s->read_frame, sizeof(MTFrame)); } else { finish_transfer(s); return 0xff; } } else 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])) { qemu_log_mask(LOG_GUEST_ERROR, "%s: somme de contrôle HBPP invalide\n", __func__); finish_transfer(s); return 0xff; } uint32_t data_len = ((s->in_buffer[2] << 10) | (s->in_buffer[3] << 2)) + 5; if (data_len < s->in_buffer_ind) { finish_transfer(s); return 0xff; } /* Conserver l'en-tête reçu et dimensionner aussi les petites trames. */ s->in_buffer_capacity = MAX(data_len + 10, 0x100); s->in_buffer = g_realloc(s->in_buffer, s->in_buffer_capacity); g_free(s->out_buffer); s->out_buffer = g_malloc0(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(" 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 finish_transfer(s); } 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 = g_new0(MTFrame, 1); 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)((y - s->prev_touch_y) * MT_INTERNAL_SENSOR_SURFACE_HEIGHT); uint64_t dt = MAX(elapsed_ns - s->last_frame_timestamp, 1); frame->finger_data.velX = CLAMP((int64_t)diff_x * 1000 / (int64_t)dt, INT16_MIN, INT16_MAX); frame->finger_data.velY = CLAMP((int64_t)diff_y * 1000 / (int64_t)dt, INT16_MIN, INT16_MAX); s->prev_touch_x = x; s->prev_touch_y = y; frame->finger_data.x = (int)(x * MT_INTERNAL_SENSOR_SURFACE_WIDTH) + MT_INTERNAL_SENSOR_ORIGIN_X; frame->finger_data.y = (int)(y * MT_INTERNAL_SENSOR_SURFACE_HEIGHT) + MT_INTERNAL_SENSOR_ORIGIN_Y; 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; g_free(s->next_frame); 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) { g_free(s->next_frame); 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; g_free(s->next_frame); 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; g_free(s->next_frame); 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 multitouch_cs(void *opaque, int pin, int level) { IPodTouchMultitouchState *s = opaque; if (level) finish_transfer(s); } static void ipod_touch_multitouch_realize(SSIPeripheral *d, Error **errp) { IPodTouchMultitouchState *s = IPOD_TOUCH_MULTITOUCH(d); qdev_init_gpio_in_named(DEVICE(d), multitouch_cs, SSI_GPIO_CS, 1); 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; /* Une lecture sans toucher doit renvoyer une trame de fin valide. */ s->next_frame = get_frame(s, MT_EVENT_TOUCH_FULL_END, 0, 0, 0, 0, 0, 0); } static void ipod_touch_multitouch_reset(DeviceState *dev) { IPodTouchMultitouchState *s = IPOD_TOUCH_MULTITOUCH(dev); finish_transfer(s); timer_del(s->touch_timer); timer_del(s->touch_end_timer); g_clear_pointer(&s->read_frame, g_free); g_clear_pointer(&s->next_frame, g_free); memset(s->hbpp_atn_ack_response, 0, sizeof s->hbpp_atn_ack_response); s->touch_down = false; s->touch_x = s->touch_y = s->prev_touch_x = s->prev_touch_y = 0; s->frame_counter = 0; s->last_frame_timestamp = 0; if (s->sysic) { s->sysic->gpio_int_status[3] &= ~(1 << 13); if (!s->sysic->gpio_int_status[3]) { qemu_irq_lower(s->sysic->gpio_irqs[3]); } } s->next_frame = get_frame(s, MT_EVENT_TOUCH_FULL_END, 0, 0, 0, 0, 0, 0); } static void ipod_touch_multitouch_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); SSIPeripheralClass *k = SSI_PERIPHERAL_CLASS(klass); dc->reset = ipod_touch_multitouch_reset; 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)