Model the A6 crypto, interrupt, USB, and platform blocks needed to boot SecureROM through iBSS into iBEC Recovery. Add local lab identity, IMG3, and APTicket tooling, patched macOS recovery utilities, UART and GDB access, and English end-user documentation.
701 lines
20 KiB
Diff
701 lines
20 KiB
Diff
--- a/src/libirecovery.c
|
|
+++ b/src/libirecovery.c
|
|
@@ -31,6 +31,12 @@
|
|
#include <ctype.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
+#ifndef _WIN32
|
|
+#include <errno.h>
|
|
+#include <netdb.h>
|
|
+#include <sys/socket.h>
|
|
+#include <sys/time.h>
|
|
+#endif
|
|
|
|
#include <libimobiledevice-glue/collection.h>
|
|
#include <libimobiledevice-glue/thread.h>
|
|
@@ -119,6 +125,11 @@
|
|
int usb_alt_interface;
|
|
unsigned int mode;
|
|
int isKIS;
|
|
+ int qemu_transport;
|
|
+ int qemu_fd;
|
|
+ uint32_t qemu_request_id;
|
|
+ uint32_t qemu_generation;
|
|
+ uint32_t qemu_poll_timeout_ms;
|
|
struct irecv_device_info device_info;
|
|
#ifndef USE_DUMMY
|
|
#ifndef _WIN32
|
|
@@ -163,6 +173,214 @@
|
|
#define debug(...) if (libirecovery_debug) fprintf(stderr, __VA_ARGS__)
|
|
|
|
static int libirecovery_debug = 0;
|
|
+
|
|
+#ifndef _WIN32
|
|
+#define QA6_USB_MAGIC 0x55364151u
|
|
+#define QA6_USB_PROTOCOL_VERSION 1u
|
|
+#define QA6_USB_MESSAGE_REQUEST 1u
|
|
+#define QA6_USB_MESSAGE_RESPONSE 2u
|
|
+#define QA6_USB_MESSAGE_INFO_REQUEST 3u
|
|
+#define QA6_USB_MESSAGE_INFO_RESPONSE 4u
|
|
+#define QA6_USB_MESSAGE_BULK_REQUEST 5u
|
|
+#define QA6_USB_MESSAGE_BULK_RESPONSE 6u
|
|
+#define QA6_USB_MESSAGE_RESET_REQUEST 7u
|
|
+#define QA6_USB_MESSAGE_RESET_RESPONSE 8u
|
|
+#define QA6_USB_STATUS_SUCCESS 0
|
|
+#define QA6_USB_STATUS_STALL -1
|
|
+#define QA6_USB_STATUS_DISCONNECTED -2
|
|
+#define QA6_USB_STATUS_PROTOCOL -3
|
|
+#define QA6_USB_STATUS_UNSUPPORTED -4
|
|
+#define QA6_USB_MAX_PAYLOAD 65535u
|
|
+#define QA6_USB_SERIAL_MAX 256u
|
|
+#define QA6_USB_DFU_STATE_ERROR 10u
|
|
+#define QA6_USB_DFU_STATE_WAIT_RESET 8u
|
|
+
|
|
+#pragma pack(push, 1)
|
|
+typedef struct qemu_a6_usb_frame_header {
|
|
+ uint32_t magic;
|
|
+ uint16_t version;
|
|
+ uint16_t type;
|
|
+ uint32_t request_id;
|
|
+ int32_t status;
|
|
+ uint32_t payload_length;
|
|
+ uint32_t transfer_length;
|
|
+ uint8_t setup[8];
|
|
+} qemu_a6_usb_frame_header;
|
|
+
|
|
+typedef struct qemu_a6_usb_device_info {
|
|
+ uint32_t generation;
|
|
+ uint16_t vendor_id;
|
|
+ uint16_t product_id;
|
|
+ uint8_t device_class;
|
|
+ uint8_t device_subclass;
|
|
+ uint8_t device_protocol;
|
|
+ uint8_t is_dfu;
|
|
+ char serial[QA6_USB_SERIAL_MAX];
|
|
+} qemu_a6_usb_device_info;
|
|
+#pragma pack(pop)
|
|
+
|
|
+static const char *qemu_a6_endpoint(void)
|
|
+{
|
|
+ const char *endpoint = getenv("LIBIRECOVERY_QEMU");
|
|
+
|
|
+ return endpoint && endpoint[0] ? endpoint : NULL;
|
|
+}
|
|
+
|
|
+static int qemu_a6_send_all(int fd, const void *data, size_t length)
|
|
+{
|
|
+ const uint8_t *cursor = data;
|
|
+
|
|
+ while (length) {
|
|
+ ssize_t sent = send(fd, cursor, length, 0);
|
|
+ if (sent < 0 && errno == EINTR) {
|
|
+ continue;
|
|
+ }
|
|
+ if (sent <= 0) {
|
|
+ return -1;
|
|
+ }
|
|
+ cursor += sent;
|
|
+ length -= sent;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int qemu_a6_recv_all(int fd, void *data, size_t length)
|
|
+{
|
|
+ uint8_t *cursor = data;
|
|
+
|
|
+ while (length) {
|
|
+ ssize_t received = recv(fd, cursor, length, 0);
|
|
+ if (received < 0 && errno == EINTR) {
|
|
+ continue;
|
|
+ }
|
|
+ if (received < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
|
|
+ return -2;
|
|
+ }
|
|
+ if (received <= 0) {
|
|
+ return -1;
|
|
+ }
|
|
+ cursor += received;
|
|
+ length -= received;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int qemu_a6_connect(void)
|
|
+{
|
|
+ const char *endpoint = qemu_a6_endpoint();
|
|
+ char host[256];
|
|
+ char port[16];
|
|
+ const char *separator;
|
|
+ struct addrinfo hints;
|
|
+ struct addrinfo *addresses = NULL;
|
|
+ struct addrinfo *address;
|
|
+ struct timeval timeout = { 1, 0 };
|
|
+ int fd = -1;
|
|
+
|
|
+ if (!endpoint || !(separator = strrchr(endpoint, ':')) ||
|
|
+ separator == endpoint || !separator[1] ||
|
|
+ (size_t)(separator - endpoint) >= sizeof(host) ||
|
|
+ strlen(separator + 1) >= sizeof(port)) {
|
|
+ return -1;
|
|
+ }
|
|
+ memcpy(host, endpoint, separator - endpoint);
|
|
+ host[separator - endpoint] = '\0';
|
|
+ strcpy(port, separator + 1);
|
|
+
|
|
+ memset(&hints, 0, sizeof(hints));
|
|
+ hints.ai_family = AF_UNSPEC;
|
|
+ hints.ai_socktype = SOCK_STREAM;
|
|
+ if (getaddrinfo(host, port, &hints, &addresses) != 0) {
|
|
+ return -1;
|
|
+ }
|
|
+ for (address = addresses; address; address = address->ai_next) {
|
|
+ fd = socket(address->ai_family, address->ai_socktype,
|
|
+ address->ai_protocol);
|
|
+ if (fd < 0) {
|
|
+ continue;
|
|
+ }
|
|
+ setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
|
+ setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
|
|
+ if (connect(fd, address->ai_addr, address->ai_addrlen) == 0) {
|
|
+ break;
|
|
+ }
|
|
+ close(fd);
|
|
+ fd = -1;
|
|
+ }
|
|
+ freeaddrinfo(addresses);
|
|
+ return fd;
|
|
+}
|
|
+
|
|
+static int qemu_a6_exchange(int fd, qemu_a6_usb_frame_header *request,
|
|
+ const void *out_data,
|
|
+ qemu_a6_usb_frame_header *response,
|
|
+ void *in_data, size_t in_capacity)
|
|
+{
|
|
+ int receive_result;
|
|
+
|
|
+ if (qemu_a6_send_all(fd, request, sizeof(*request)) < 0 ||
|
|
+ (request->payload_length &&
|
|
+ qemu_a6_send_all(fd, out_data, request->payload_length) < 0)) {
|
|
+ return IRECV_E_NO_DEVICE;
|
|
+ }
|
|
+ receive_result = qemu_a6_recv_all(fd, response, sizeof(*response));
|
|
+ if (receive_result == -2) {
|
|
+ return IRECV_E_TIMEOUT;
|
|
+ }
|
|
+ if (receive_result < 0) {
|
|
+ return IRECV_E_NO_DEVICE;
|
|
+ }
|
|
+ if (response->magic != QA6_USB_MAGIC ||
|
|
+ response->version != QA6_USB_PROTOCOL_VERSION ||
|
|
+ response->request_id != request->request_id ||
|
|
+ response->payload_length > QA6_USB_MAX_PAYLOAD ||
|
|
+ response->payload_length > in_capacity) {
|
|
+ return IRECV_E_UNKNOWN_ERROR;
|
|
+ }
|
|
+ if (response->payload_length &&
|
|
+ qemu_a6_recv_all(fd, in_data, response->payload_length) < 0) {
|
|
+ return IRECV_E_NO_DEVICE;
|
|
+ }
|
|
+ switch (response->status) {
|
|
+ case QA6_USB_STATUS_SUCCESS:
|
|
+ return IRECV_E_SUCCESS;
|
|
+ case QA6_USB_STATUS_STALL:
|
|
+ return IRECV_E_PIPE;
|
|
+ case QA6_USB_STATUS_DISCONNECTED:
|
|
+ return IRECV_E_NO_DEVICE;
|
|
+ case QA6_USB_STATUS_UNSUPPORTED:
|
|
+ return IRECV_E_UNSUPPORTED;
|
|
+ case QA6_USB_STATUS_PROTOCOL:
|
|
+ default:
|
|
+ return IRECV_E_UNKNOWN_ERROR;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int qemu_a6_read_info_fd(int fd, qemu_a6_usb_device_info *info)
|
|
+{
|
|
+ qemu_a6_usb_frame_header request;
|
|
+ qemu_a6_usb_frame_header response;
|
|
+ int error;
|
|
+
|
|
+ memset(&request, 0, sizeof(request));
|
|
+ request.magic = QA6_USB_MAGIC;
|
|
+ request.version = QA6_USB_PROTOCOL_VERSION;
|
|
+ request.type = QA6_USB_MESSAGE_INFO_REQUEST;
|
|
+ request.request_id = 1;
|
|
+ error = qemu_a6_exchange(fd, &request, NULL, &response,
|
|
+ info, sizeof(*info));
|
|
+ if (error != IRECV_E_SUCCESS) {
|
|
+ return error;
|
|
+ }
|
|
+ if (response.type != QA6_USB_MESSAGE_INFO_RESPONSE ||
|
|
+ response.payload_length != sizeof(*info) ||
|
|
+ response.transfer_length != sizeof(*info)) {
|
|
+ return IRECV_E_UNKNOWN_ERROR;
|
|
+ }
|
|
+ info->serial[sizeof(info->serial) - 1] = '\0';
|
|
+ return IRECV_E_SUCCESS;
|
|
+}
|
|
+#endif
|
|
#ifndef USE_DUMMY
|
|
#ifndef _WIN32
|
|
#ifndef HAVE_IOKIT
|
|
@@ -930,6 +1149,199 @@
|
|
}
|
|
}
|
|
|
|
+#ifndef _WIN32
|
|
+static irecv_error_t qemu_a6_open_with_ecid(irecv_client_t *pclient,
|
|
+ uint64_t ecid)
|
|
+{
|
|
+ qemu_a6_usb_device_info info;
|
|
+ irecv_client_t client;
|
|
+ int fd;
|
|
+ int error;
|
|
+
|
|
+ *pclient = NULL;
|
|
+ fd = qemu_a6_connect();
|
|
+ if (fd < 0) {
|
|
+ return IRECV_E_UNABLE_TO_CONNECT;
|
|
+ }
|
|
+ memset(&info, 0, sizeof(info));
|
|
+ error = qemu_a6_read_info_fd(fd, &info);
|
|
+ if (error != IRECV_E_SUCCESS) {
|
|
+ close(fd);
|
|
+ return error;
|
|
+ }
|
|
+ if (info.vendor_id != APPLE_VENDOR_ID ||
|
|
+ (info.product_id != IRECV_K_DFU_MODE &&
|
|
+ info.product_id != IRECV_K_WTF_MODE &&
|
|
+ info.product_id != IRECV_K_PORT_DFU_MODE &&
|
|
+ (info.product_id < IRECV_K_RECOVERY_MODE_1 ||
|
|
+ info.product_id > IRECV_K_RECOVERY_MODE_4))) {
|
|
+ close(fd);
|
|
+ return IRECV_E_NO_DEVICE;
|
|
+ }
|
|
+
|
|
+ client = calloc(1, sizeof(*client));
|
|
+ if (!client) {
|
|
+ close(fd);
|
|
+ return IRECV_E_OUT_OF_MEMORY;
|
|
+ }
|
|
+ client->qemu_transport = 1;
|
|
+ client->qemu_fd = fd;
|
|
+ client->qemu_request_id = 1;
|
|
+ client->qemu_generation = info.generation;
|
|
+ client->mode = info.product_id;
|
|
+ client->usb_interface = 0;
|
|
+ irecv_load_device_info_from_iboot_string(client, info.serial);
|
|
+ if (ecid && ecid != IRECV_K_WTF_MODE &&
|
|
+ client->device_info.ecid != ecid) {
|
|
+ irecv_close(client);
|
|
+ return IRECV_E_NO_DEVICE;
|
|
+ }
|
|
+ debug("opening QEMU A6 device %04x:%04x generation %u...\n",
|
|
+ info.vendor_id, info.product_id, info.generation);
|
|
+ *pclient = client;
|
|
+ return IRECV_E_SUCCESS;
|
|
+}
|
|
+
|
|
+static int qemu_a6_control_transfer(irecv_client_t client,
|
|
+ uint8_t bm_request_type,
|
|
+ uint8_t b_request, uint16_t w_value,
|
|
+ uint16_t w_index, unsigned char *data,
|
|
+ uint16_t w_length)
|
|
+{
|
|
+ qemu_a6_usb_frame_header request;
|
|
+ qemu_a6_usb_frame_header response;
|
|
+ int direction_in = (bm_request_type & 0x80) != 0;
|
|
+ int error;
|
|
+
|
|
+ memset(&request, 0, sizeof(request));
|
|
+ request.magic = QA6_USB_MAGIC;
|
|
+ request.version = QA6_USB_PROTOCOL_VERSION;
|
|
+ request.type = QA6_USB_MESSAGE_REQUEST;
|
|
+ request.request_id = ++client->qemu_request_id;
|
|
+ request.payload_length = direction_in ? 0 : w_length;
|
|
+ request.transfer_length = w_length;
|
|
+ request.setup[0] = bm_request_type;
|
|
+ request.setup[1] = b_request;
|
|
+ request.setup[2] = w_value & 0xff;
|
|
+ request.setup[3] = w_value >> 8;
|
|
+ request.setup[4] = w_index & 0xff;
|
|
+ request.setup[5] = w_index >> 8;
|
|
+ request.setup[6] = w_length & 0xff;
|
|
+ request.setup[7] = w_length >> 8;
|
|
+ error = qemu_a6_exchange(client->qemu_fd, &request,
|
|
+ direction_in ? NULL : data, &response,
|
|
+ direction_in ? data : NULL,
|
|
+ direction_in ? w_length : 0);
|
|
+ if (error != IRECV_E_SUCCESS) {
|
|
+ return error;
|
|
+ }
|
|
+ if (response.type != QA6_USB_MESSAGE_RESPONSE ||
|
|
+ response.transfer_length > w_length) {
|
|
+ return IRECV_E_UNKNOWN_ERROR;
|
|
+ }
|
|
+ if (bm_request_type == 0xa1 && b_request == 3 && data &&
|
|
+ response.transfer_length >= 4) {
|
|
+ client->qemu_poll_timeout_ms = data[1] |
|
|
+ (data[2] << 8) | (data[3] << 16);
|
|
+ }
|
|
+ return response.transfer_length;
|
|
+}
|
|
+
|
|
+static int qemu_a6_bulk_transfer(irecv_client_t client,
|
|
+ unsigned char endpoint,
|
|
+ unsigned char *data, int length,
|
|
+ int *transferred, unsigned int timeout)
|
|
+{
|
|
+ qemu_a6_usb_frame_header request;
|
|
+ qemu_a6_usb_frame_header response;
|
|
+ int direction_in = (endpoint & 0x80) != 0;
|
|
+ int error;
|
|
+
|
|
+ if (length < 0 || (unsigned int)length > QA6_USB_MAX_PAYLOAD) {
|
|
+ return IRECV_E_INVALID_INPUT;
|
|
+ }
|
|
+ memset(&request, 0, sizeof(request));
|
|
+ request.magic = QA6_USB_MAGIC;
|
|
+ request.version = QA6_USB_PROTOCOL_VERSION;
|
|
+ request.type = QA6_USB_MESSAGE_BULK_REQUEST;
|
|
+ request.request_id = ++client->qemu_request_id;
|
|
+ request.payload_length = direction_in ? 0 : length;
|
|
+ request.transfer_length = length;
|
|
+ request.setup[0] = endpoint;
|
|
+ request.setup[1] = timeout & 0xff;
|
|
+ request.setup[2] = (timeout >> 8) & 0xff;
|
|
+ request.setup[3] = (timeout >> 16) & 0xff;
|
|
+ request.setup[4] = (timeout >> 24) & 0xff;
|
|
+ error = qemu_a6_exchange(client->qemu_fd, &request,
|
|
+ direction_in ? NULL : data, &response,
|
|
+ direction_in ? data : NULL,
|
|
+ direction_in ? length : 0);
|
|
+ if (error != IRECV_E_SUCCESS) {
|
|
+ return error;
|
|
+ }
|
|
+ if (response.type != QA6_USB_MESSAGE_BULK_RESPONSE ||
|
|
+ response.transfer_length > (unsigned int)length) {
|
|
+ return IRECV_E_UNKNOWN_ERROR;
|
|
+ }
|
|
+ *transferred = response.transfer_length;
|
|
+ return IRECV_E_SUCCESS;
|
|
+}
|
|
+
|
|
+static irecv_error_t qemu_a6_reset(irecv_client_t client)
|
|
+{
|
|
+ qemu_a6_usb_frame_header request;
|
|
+ qemu_a6_usb_frame_header response;
|
|
+ uint8_t dfu_status[6];
|
|
+ int retry = 0;
|
|
+ int error;
|
|
+ while (client->qemu_poll_timeout_ms && retry++ < 5) {
|
|
+ uint32_t delay_ms = client->qemu_poll_timeout_ms > 10000 ?
|
|
+ 10000 : client->qemu_poll_timeout_ms;
|
|
+ debug("honoring QEMU DFU poll timeout: %u ms\n", delay_ms);
|
|
+ usleep((useconds_t)delay_ms * 1000);
|
|
+ client->qemu_poll_timeout_ms = 0;
|
|
+ memset(dfu_status, 0, sizeof(dfu_status));
|
|
+ error = qemu_a6_control_transfer(client, 0xa1, 3, 0, 0,
|
|
+ dfu_status, sizeof(dfu_status));
|
|
+ if (error != sizeof(dfu_status)) {
|
|
+ return error < 0 ? error : IRECV_E_USB_STATUS;
|
|
+ }
|
|
+ debug("QEMU DFU manifestation state=%u status=%u\n",
|
|
+ dfu_status[4], dfu_status[0]);
|
|
+ if (dfu_status[0] || dfu_status[4] == QA6_USB_DFU_STATE_ERROR) {
|
|
+ return IRECV_E_USB_UPLOAD;
|
|
+ }
|
|
+ if (dfu_status[4] == QA6_USB_DFU_STATE_WAIT_RESET) {
|
|
+ client->qemu_poll_timeout_ms = 0;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ if (client->qemu_poll_timeout_ms) {
|
|
+ return IRECV_E_TIMEOUT;
|
|
+ }
|
|
+ if (getenv("LIBIRECOVERY_QEMU_NO_RESET")) {
|
|
+ debug("leaving QEMU in DFU-WAIT-RESET for debugging\n");
|
|
+ return IRECV_E_SUCCESS;
|
|
+ }
|
|
+
|
|
+ memset(&request, 0, sizeof(request));
|
|
+ request.magic = QA6_USB_MAGIC;
|
|
+ request.version = QA6_USB_PROTOCOL_VERSION;
|
|
+ request.type = QA6_USB_MESSAGE_RESET_REQUEST;
|
|
+ request.request_id = ++client->qemu_request_id;
|
|
+ error = qemu_a6_exchange(client->qemu_fd, &request, NULL, &response,
|
|
+ NULL, 0);
|
|
+ if (error != IRECV_E_SUCCESS) {
|
|
+ return error;
|
|
+ }
|
|
+ if (response.type != QA6_USB_MESSAGE_RESET_RESPONSE ||
|
|
+ response.payload_length || response.transfer_length) {
|
|
+ return IRECV_E_UNKNOWN_ERROR;
|
|
+ }
|
|
+ return IRECV_E_SUCCESS;
|
|
+}
|
|
+#endif
|
|
+
|
|
static void irecv_copy_nonce_with_tag_from_buffer(const char* tag, unsigned char** nonce, unsigned int* nonce_size, const char *buf)
|
|
{
|
|
int taglen = strlen(tag);
|
|
@@ -1006,6 +1350,11 @@
|
|
|
|
*nonce = NULL;
|
|
*nonce_size = 0;
|
|
+ if (client->qemu_transport) {
|
|
+ irecv_copy_nonce_with_tag_from_buffer(
|
|
+ tag, nonce, nonce_size, client->device_info.serial_string);
|
|
+ return;
|
|
+ }
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
len = irecv_get_string_descriptor_ascii(client, 1, (unsigned char*)buf, sizeof(buf)-1);
|
|
@@ -1406,7 +1755,8 @@
|
|
|
|
static int check_context(irecv_client_t client)
|
|
{
|
|
- if (client == NULL || client->handle == NULL) {
|
|
+ if (client == NULL ||
|
|
+ (!client->qemu_transport && client->handle == NULL)) {
|
|
return IRECV_E_NO_DEVICE;
|
|
}
|
|
|
|
@@ -1456,6 +1806,12 @@
|
|
return IRECV_E_UNSUPPORTED;
|
|
#else
|
|
#ifndef _WIN32
|
|
+ if (client->qemu_transport) {
|
|
+ return qemu_a6_control_transfer(client, bm_request_type, b_request,
|
|
+ w_value, w_index, data, w_length);
|
|
+ }
|
|
+#endif
|
|
+#ifndef _WIN32
|
|
#ifdef HAVE_IOKIT
|
|
return iokit_usb_control_transfer(client, bm_request_type, b_request, w_value, w_index, data, w_length, timeout);
|
|
#else
|
|
@@ -1813,6 +2169,10 @@
|
|
int ret;
|
|
|
|
#ifndef _WIN32
|
|
+ if (client->qemu_transport) {
|
|
+ return qemu_a6_bulk_transfer(client, endpoint, data, length,
|
|
+ transferred, timeout);
|
|
+ }
|
|
#ifdef HAVE_IOKIT
|
|
return iokit_usb_bulk_transfer(client, endpoint, data, length, transferred, timeout);
|
|
#else
|
|
@@ -2144,11 +2504,15 @@
|
|
irecv_set_debug_level(libirecovery_debug);
|
|
}
|
|
#ifndef _WIN32
|
|
+ if (qemu_a6_endpoint()) {
|
|
+ error = qemu_a6_open_with_ecid(pclient, ecid);
|
|
+ } else {
|
|
#ifdef HAVE_IOKIT
|
|
- error = iokit_open_with_ecid(pclient, ecid);
|
|
+ error = iokit_open_with_ecid(pclient, ecid);
|
|
#else
|
|
- error = libusb_open_with_ecid(pclient, ecid);
|
|
+ error = libusb_open_with_ecid(pclient, ecid);
|
|
#endif
|
|
+ }
|
|
#else
|
|
error = win32_open_with_ecid(pclient, ecid);
|
|
#endif
|
|
@@ -2166,12 +2530,14 @@
|
|
}
|
|
|
|
#ifdef HAVE_IOKIT
|
|
- error = (*client->handle)->CreateDeviceAsyncEventSource(client->handle, &client->async_event_source);
|
|
- if (error != IRECV_E_SUCCESS) {
|
|
- free(client);
|
|
- return error;
|
|
+ if (!client->qemu_transport) {
|
|
+ error = (*client->handle)->CreateDeviceAsyncEventSource(client->handle, &client->async_event_source);
|
|
+ if (error != IRECV_E_SUCCESS) {
|
|
+ free(client);
|
|
+ return error;
|
|
+ }
|
|
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), client->async_event_source, kCFRunLoopDefaultMode);
|
|
}
|
|
- CFRunLoopAddSource(CFRunLoopGetCurrent(), client->async_event_source, kCFRunLoopDefaultMode);
|
|
#endif
|
|
|
|
if (client->mode == IRECV_K_DFU_MODE || client->mode == IRECV_K_PORT_DFU_MODE || client->mode == IRECV_K_WTF_MODE || client->mode == KIS_PRODUCT_ID) {
|
|
@@ -2237,6 +2603,10 @@
|
|
|
|
#ifndef _WIN32
|
|
debug("Setting to configuration %d\n", configuration);
|
|
+ if (client->qemu_transport) {
|
|
+ client->usb_config = configuration;
|
|
+ return IRECV_E_SUCCESS;
|
|
+ }
|
|
|
|
#ifdef HAVE_IOKIT
|
|
IOReturn result;
|
|
@@ -2355,6 +2725,17 @@
|
|
|
|
debug("Setting to interface %d:%d\n", usb_interface, usb_alt_interface);
|
|
#ifndef _WIN32
|
|
+ if (client->qemu_transport) {
|
|
+ if (usb_interface == 1 &&
|
|
+ qemu_a6_control_transfer(client, 0x01, 0x0B,
|
|
+ usb_alt_interface, usb_interface,
|
|
+ NULL, 0) < 0) {
|
|
+ return IRECV_E_USB_INTERFACE;
|
|
+ }
|
|
+ client->usb_interface = usb_interface;
|
|
+ client->usb_alt_interface = usb_alt_interface;
|
|
+ return IRECV_E_SUCCESS;
|
|
+ }
|
|
#ifdef HAVE_IOKIT
|
|
if (iokit_usb_set_interface(client, usb_interface, usb_alt_interface) < 0) {
|
|
return IRECV_E_USB_INTERFACE;
|
|
@@ -2393,6 +2768,9 @@
|
|
return IRECV_E_NO_DEVICE;
|
|
|
|
#ifndef _WIN32
|
|
+ if (client->qemu_transport) {
|
|
+ return qemu_a6_reset(client);
|
|
+ }
|
|
#ifdef HAVE_IOKIT
|
|
IOReturn result;
|
|
|
|
@@ -2875,6 +3253,44 @@
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
+static struct irecv_usb_device_info *qemu_a6_handle_device_add(
|
|
+ const qemu_a6_usb_device_info *info)
|
|
+{
|
|
+ struct irecv_client_private client_loc;
|
|
+ struct irecv_usb_device_info *usb_dev_info;
|
|
+ irecv_device_event_t dev_event;
|
|
+
|
|
+ memset(&client_loc, 0, sizeof(client_loc));
|
|
+ client_loc.mode = info->product_id;
|
|
+ irecv_load_device_info_from_iboot_string(&client_loc, info->serial);
|
|
+ usb_dev_info = calloc(1, sizeof(*usb_dev_info));
|
|
+ if (!usb_dev_info) {
|
|
+ free(client_loc.device_info.srnm);
|
|
+ free(client_loc.device_info.imei);
|
|
+ free(client_loc.device_info.srtg);
|
|
+ free(client_loc.device_info.serial_string);
|
|
+ return NULL;
|
|
+ }
|
|
+ memcpy(&usb_dev_info->device_info, &client_loc.device_info,
|
|
+ sizeof(client_loc.device_info));
|
|
+ usb_dev_info->location = info->generation;
|
|
+ usb_dev_info->alive = 1;
|
|
+ usb_dev_info->mode = client_loc.mode;
|
|
+ collection_add(&devices, usb_dev_info);
|
|
+
|
|
+ dev_event.type = IRECV_DEVICE_ADD;
|
|
+ dev_event.mode = client_loc.mode;
|
|
+ dev_event.device_info = &usb_dev_info->device_info;
|
|
+ mutex_lock(&listener_mutex);
|
|
+ FOREACH(struct irecv_device_event_context* context, &listeners) {
|
|
+ context->callback(&dev_event, context->user_data);
|
|
+ } ENDFOREACH
|
|
+ mutex_unlock(&listener_mutex);
|
|
+ return usb_dev_info;
|
|
+}
|
|
+#endif
|
|
+
|
|
+#ifndef _WIN32
|
|
#ifdef HAVE_IOKIT
|
|
static void iokit_device_added(void *refcon, io_iterator_t iterator)
|
|
{
|
|
@@ -2977,10 +3393,70 @@
|
|
cond_t startup_cond;
|
|
mutex_t startup_mutex;
|
|
};
|
|
+
|
|
+#ifndef _WIN32
|
|
+static void *qemu_a6_event_handler(struct _irecv_event_handler_info *startup)
|
|
+{
|
|
+ struct irecv_usb_device_info *current = NULL;
|
|
+
|
|
+ mutex_lock(&startup->startup_mutex);
|
|
+ cond_signal(&startup->startup_cond);
|
|
+ mutex_unlock(&startup->startup_mutex);
|
|
+
|
|
+ for (;;) {
|
|
+ qemu_a6_usb_device_info info;
|
|
+ int listeners_active;
|
|
+ int fd;
|
|
+ int error = IRECV_E_UNABLE_TO_CONNECT;
|
|
+
|
|
+ mutex_lock(&listener_mutex);
|
|
+ listeners_active = collection_count(&listeners) != 0;
|
|
+ mutex_unlock(&listener_mutex);
|
|
+ if (!listeners_active) {
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ fd = qemu_a6_connect();
|
|
+ if (fd >= 0) {
|
|
+ memset(&info, 0, sizeof(info));
|
|
+ error = qemu_a6_read_info_fd(fd, &info);
|
|
+ close(fd);
|
|
+ }
|
|
+ if (error == IRECV_E_SUCCESS) {
|
|
+ if (current &&
|
|
+ (current->location != info.generation ||
|
|
+ current->mode != info.product_id)) {
|
|
+ /*
|
|
+ * Publish removal and arrival on separate polling turns.
|
|
+ * idevicerestore deliberately waits for MODE_UNKNOWN before
|
|
+ * it accepts the next DFU/recovery attachment.
|
|
+ */
|
|
+ _irecv_handle_device_remove(current);
|
|
+ current = NULL;
|
|
+ } else if (!current) {
|
|
+ current = qemu_a6_handle_device_add(&info);
|
|
+ }
|
|
+ } else if (error == IRECV_E_NO_DEVICE && current) {
|
|
+ _irecv_handle_device_remove(current);
|
|
+ current = NULL;
|
|
+ }
|
|
+ usleep(250000);
|
|
+ }
|
|
+ if (current) {
|
|
+ _irecv_handle_device_remove(current);
|
|
+ }
|
|
+ return NULL;
|
|
+}
|
|
+#endif
|
|
|
|
static void *_irecv_event_handler(void* data)
|
|
{
|
|
struct _irecv_event_handler_info* info = (struct _irecv_event_handler_info*)data;
|
|
+#ifndef _WIN32
|
|
+ if (qemu_a6_endpoint()) {
|
|
+ return qemu_a6_event_handler(info);
|
|
+ }
|
|
+#endif
|
|
#ifdef _WIN32
|
|
struct collection newDevices;
|
|
const GUID *guids[] = { &GUID_DEVINTERFACE_KIS, &GUID_DEVINTERFACE_PORTDFU, &GUID_DEVINTERFACE_DFU, &GUID_DEVINTERFACE_IBOOT, NULL };
|
|
@@ -3381,6 +3858,10 @@
|
|
client->disconnected_callback(client, &event);
|
|
}
|
|
#ifndef _WIN32
|
|
+ if (client->qemu_transport) {
|
|
+ close(client->qemu_fd);
|
|
+ client->qemu_fd = -1;
|
|
+ } else {
|
|
#ifdef HAVE_IOKIT
|
|
if (client->usbInterface) {
|
|
(*client->usbInterface)->USBInterfaceClose(client->usbInterface);
|
|
@@ -3405,6 +3886,7 @@
|
|
client->handle = NULL;
|
|
}
|
|
#endif
|
|
+ }
|
|
#else
|
|
CloseHandle(client->handle);
|
|
#endif
|