void usb_conn_packet_send(struct usb_conn_t *conn, struct http_packet_t *pkt) { int size_sent = 0; const int timeout = 6 * 60 * 60 * 1000; // 6 hours in milliseconds int num_timeouts = 0; size_t sent = 0; size_t pending = pkt->filled_size; while (pending > 0) { int to_send = (int)pending; NOTE("USB: want to send %d bytes", to_send); int status = libusb_bulk_transfer(conn->parent->printer, conn->interface->endpoint_out, pkt->buffer + sent, to_send, &size_sent, timeout); if (status == LIBUSB_ERROR_TIMEOUT) { NOTE("USB: send timed out, retrying"); if (num_timeouts++ > PRINTER_CRASH_TIMEOUT) ERR_AND_EXIT("Usb send fully timed out"); // Sleep for tenth of a second struct timespec sleep_dur; sleep_dur.tv_sec = 0; sleep_dur.tv_nsec = 100000000; nanosleep(&sleep_dur, NULL); continue; } if (status == LIBUSB_ERROR_NO_DEVICE) ERR_AND_EXIT("Printer has been disconnected"); if (status < 0) ERR_AND_EXIT("USB: send failed with status %s", libusb_error_name(status)); if (size_sent < 0) ERR_AND_EXIT("Unexpected negative size_sent"); pending -= (size_t) size_sent; sent += (size_t) size_sent; NOTE("USB: sent %d bytes", size_sent); } NOTE("USB: sent %d bytes in total", sent); }
struct usb_sock_t *usb_open() { struct usb_sock_t *usb = calloc(1, sizeof *usb); int status = 1; status = libusb_init(&usb->context); if (status < 0) { ERR("libusb init failed with error: %s", libusb_error_name(status)); goto error_usbinit; } libusb_device **device_list = NULL; ssize_t device_count = libusb_get_device_list(usb->context, &device_list); if (device_count < 0) { ERR("failed to get list of usb devices"); goto error; } // Discover device and count interfaces ==---------------------------== int selected_config = -1; unsigned int selected_ipp_interface_count = 0; int auto_pick = !(g_options.vendor_id || g_options.product_id || g_options.serial_num); libusb_device *printer_device = NULL; for (ssize_t i = 0; i < device_count; i++) { libusb_device *candidate = device_list[i]; struct libusb_device_descriptor desc; libusb_get_device_descriptor(candidate, &desc); if (!is_our_device(candidate, desc)) continue; for (uint8_t config_num = 0; config_num < desc.bNumConfigurations; config_num++) { struct libusb_config_descriptor *config = NULL; status = libusb_get_config_descriptor(candidate, config_num, &config); if (status < 0) ERR_AND_EXIT("USB: didn't get config desc %s", libusb_error_name(status)); int interface_count = count_ippoverusb_interfaces(config); libusb_free_config_descriptor(config); if (interface_count >= 2) { selected_config = config_num; selected_ipp_interface_count = (unsigned) interface_count; printer_device = candidate; goto found_device; } // CONFTEST: Two or more interfaces are required if (interface_count == 1) { CONF("usb device has only one ipp interface " "in violation of standard"); goto error; } if (!auto_pick) { ERR_AND_EXIT("No ipp-usb interfaces found"); } } } found_device: if (printer_device == NULL) { if (!auto_pick) { ERR("No printer found by that vid, pid, serial"); } else { ERR("No IPP over USB printer found"); } goto error; } // Open the printer ==-----------------------------------------------== status = libusb_open(printer_device, &usb->printer); if (status != 0) { ERR("failed to open device"); goto error; } // Open every IPP-USB interface ==-----------------------------------== usb->num_interfaces = selected_ipp_interface_count; usb->interfaces = calloc(usb->num_interfaces, sizeof(*usb->interfaces)); if (usb->interfaces == NULL) { ERR("Failed to alloc interfaces"); goto error; } struct libusb_config_descriptor *config = NULL; status = libusb_get_config_descriptor(printer_device, (uint8_t)selected_config, &config); if (status != 0 || config == NULL) { ERR("Failed to aquire config descriptor"); goto error; } unsigned int interfs = selected_ipp_interface_count; for (uint8_t interf_num = 0; interf_num < config->bNumInterfaces; interf_num++) { const struct libusb_interface *interf = NULL; interf = &config->interface[interf_num]; for (int alt_num = 0; alt_num < interf->num_altsetting; alt_num++) { const struct libusb_interface_descriptor *alt = NULL; alt = &interf->altsetting[alt_num]; // Skip non-IPP-USB interfaces if (!is_ippusb_interface(alt)) continue; interfs--; struct usb_interface *uf = usb->interfaces + interfs; uf->interface_number = interf_num; uf->libusb_interface_index = alt->bInterfaceNumber; uf->interface_alt = alt_num; // Store interface's two endpoints for (int end_i = 0; end_i < alt->bNumEndpoints; end_i++) { const struct libusb_endpoint_descriptor *end; end = &alt->endpoint[end_i]; usb->max_packet_size = end->wMaxPacketSize; // High bit set means endpoint // is an INPUT or IN endpoint. uint8_t address = end->bEndpointAddress; if (address & 0x80) uf->endpoint_in = address; else uf->endpoint_out = address; } break; } } libusb_free_config_descriptor(config); libusb_free_device_list(device_list, 1); // Pour interfaces into pool ==--------------------------------------== usb->num_avail = usb->num_interfaces; usb->interface_pool = calloc(usb->num_avail, sizeof(*usb->interface_pool)); if (usb->interface_pool == NULL) { ERR("Failed to alloc interface pool"); goto error; } for (uint32_t i = 0; i < usb->num_avail; i++) { usb->interface_pool[i] = i; } // Stale lock int status_lock = sem_init(&usb->num_staled_lock, 0, 1); if (status_lock != 0) { ERR("Failed to create num_staled lock"); goto error; } // Pool management lock status_lock = sem_init(&usb->pool_manage_lock, 0, 1); if (status_lock != 0) { ERR("Failed to create high priority pool lock"); goto error; } // High priority lock status_lock = sem_init(&usb->pool_high_priority_lock, 0, 1); if (status_lock != 0) { ERR("Failed to create low priority pool lock"); goto error; } // Low priority lock status_lock = sem_init(&usb->pool_low_priority_lock, 0, usb->num_avail - 1); if (status_lock != 0) { ERR("Failed to create high priority pool lock"); goto error; } return usb; error: if (device_list != NULL) libusb_free_device_list(device_list, 1); error_usbinit: if (usb != NULL) { if (usb->context != NULL) libusb_exit(usb->context); if (usb->interfaces != NULL) free(usb->interfaces); if (usb->interface_pool != NULL) free(usb->interface_pool); free(usb); } return NULL; }
struct http_packet_t *usb_conn_packet_get(struct usb_conn_t *conn, struct http_message_t *msg) { if (msg->is_completed) return NULL; struct http_packet_t *pkt = packet_new(msg); if (pkt == NULL) { ERR("failed to create packet for incoming usb message"); goto cleanup; } // File packet const int timeout = 6 * 60 * 60 * 1000; // 6 hours in milliseconds size_t read_size_ulong = packet_pending_bytes(pkt); if (read_size_ulong == 0) return pkt; int times_staled = 0; while (read_size_ulong > 0 && !msg->is_completed) { if (read_size_ulong >= INT_MAX) goto cleanup; int read_size = (int)read_size_ulong; // Ensure read_size is multiple of usb packets read_size += (512 - (read_size % 512)) % 512; // Expand buffer if needed if (pkt->buffer_capacity < pkt->filled_size + read_size_ulong) if (packet_expand(pkt) < 0) ERR_AND_EXIT("Failed to ensure room for usb pkt"); NOTE("USB: Getting %d bytes of %d", read_size, pkt->expected_size); int gotten_size = 0; int status = libusb_bulk_transfer( conn->parent->printer, conn->interface->endpoint_in, pkt->buffer + pkt->filled_size, read_size, &gotten_size, timeout); if (status == LIBUSB_ERROR_NO_DEVICE) ERR_AND_EXIT("Printer has been disconnected"); if (status != 0 && status != LIBUSB_ERROR_TIMEOUT) { ERR("bulk xfer failed with error code %d", status); ERR("tried reading %d bytes", read_size); goto cleanup; } if (gotten_size < 0) ERR_AND_EXIT("Negative read size unexpected"); if (gotten_size > 0) { times_staled = 0; usb_conn_mark_moving(conn); } else { times_staled++; if (times_staled > CONN_STALE_THRESHHOLD) { usb_conn_mark_staled(conn); if (usb_all_conns_staled(conn->parent) && times_staled > PRINTER_CRASH_TIMEOUT) { ERR("USB timedout, dropping data"); goto cleanup; } if (pkt->filled_size > 0) NOTE("Packet so far \n%.*s\n", pkt->filled_size, pkt->buffer); } // Sleep for tenth of a second struct timespec sleep_dur; sleep_dur.tv_sec = 0; sleep_dur.tv_nsec = 100000000; nanosleep(&sleep_dur, NULL); } NOTE("USB: Got %d bytes", gotten_size); packet_mark_received(pkt, (size_t)gotten_size); read_size_ulong = packet_pending_bytes(pkt); } NOTE("USB: Received %d bytes of %d with type %d", pkt->filled_size, pkt->expected_size, msg->type); return pkt; cleanup: if (pkt != NULL) packet_free(pkt); return NULL; }
struct usb_conn_t *usb_conn_aquire(struct usb_sock_t *usb, int is_high_priority) { int used_high_priority = 0; if (is_high_priority) { // We first take the high priority lock. sem_wait(&usb->pool_high_priority_lock); used_high_priority = 1; // We can then check if a low priority // interface is available. if (!sem_trywait(&usb->pool_low_priority_lock)) { // If a low priority is avail we take that one // then release the high priority interface. // Otherwise we use the high priority interface. used_high_priority = 0; sem_post(&usb->pool_high_priority_lock); } } else { sem_wait(&usb->pool_low_priority_lock); } struct usb_conn_t *conn = calloc(1, sizeof(*conn)); if (conn == NULL) { ERR("failed to aloc space for usb connection"); return NULL; } sem_wait(&usb->pool_manage_lock); { conn->parent = usb; conn->is_high_priority = used_high_priority; usb->num_taken++; uint32_t slot = --usb->num_avail; conn->interface_index = usb->interface_pool[slot]; conn->interface = usb->interfaces + conn->interface_index; struct usb_interface *uf = conn->interface; // Make kernel release interface if (libusb_kernel_driver_active(usb->printer, uf->libusb_interface_index) == 1) { // Only linux supports this // other platforms will fail // thus we ignore the error code // it either works or it does not libusb_detach_kernel_driver(usb->printer, uf->libusb_interface_index); } // Claim the whole interface int status = 0; do { // Spinlock-like // Libusb does not offer a blocking call // so we're left with a spinlock status = libusb_claim_interface( usb->printer, uf->libusb_interface_index); switch (status) { case LIBUSB_ERROR_NOT_FOUND: ERR_AND_EXIT("USB Interface did not exist"); case LIBUSB_ERROR_NO_DEVICE: ERR_AND_EXIT("Printer was removed"); default: break; } } while (status != 0); // Select the IPP-USB alt setting of the interface libusb_set_interface_alt_setting(usb->printer, uf->libusb_interface_index, uf->interface_alt); } sem_post(&usb->pool_manage_lock); return conn; }
static void *service_connection(void *arg_void) { struct service_thread_param *arg = (struct service_thread_param *)arg_void; // clasify priority while (!arg->tcp->is_closed) { struct usb_conn_t *usb = NULL; struct http_message_t *server_msg = NULL; struct http_message_t *client_msg = NULL; // Client's request NOTE("Client msg starting"); client_msg = http_message_new(); if (client_msg == NULL) { ERR("Failed to create message"); break; } while (!client_msg->is_completed) { struct http_packet_t *pkt; pkt = tcp_packet_get(arg->tcp, client_msg); if (pkt == NULL) { if (arg->tcp->is_closed) { NOTE("Client closed connection\n"); goto cleanup_subconn; } ERR_AND_EXIT("Got null packet from tcp"); } if (usb == NULL) { usb = usb_conn_aquire(arg->usb_sock, 1); if (usb == NULL) { ERR("Failed to aquire usb interface"); packet_free(pkt); goto cleanup_subconn; } NOTE("Interface #%d: aquired usb conn", usb->interface_index); } //NOTE("%.*s", (int)pkt->filled_size, pkt->buffer); usb_conn_packet_send(usb, pkt); packet_free(pkt); } message_free(client_msg); client_msg = NULL; NOTE("Interface #%d: Client msg completed\n", usb->interface_index); // Server's responce NOTE("Interface #%d: Server msg starting", usb->interface_index); server_msg = http_message_new(); if (server_msg == NULL) { ERR("Failed to create message"); goto cleanup_subconn; } while (!server_msg->is_completed) { struct http_packet_t *pkt; pkt = usb_conn_packet_get(usb, server_msg); if (pkt == NULL) break; NOTE("Pkt from usb: \n%.*s", (int)pkt->filled_size, pkt->buffer); tcp_packet_send(arg->tcp, pkt); packet_free(pkt); NOTE("Interface #%d: Server pkt done", usb->interface_index); } NOTE("Interface #%d: Server msg completed\n", usb->interface_index); cleanup_subconn: if (client_msg != NULL) message_free(client_msg); if (server_msg != NULL) message_free(server_msg); if (usb != NULL) usb_conn_release(usb); } tcp_conn_close(arg->tcp); free(arg); return NULL; }