/** * \brief Send a SCSI command to a port (for usb scsi ports) * * \param port a #GPPort * \param to_dev data direction, set to 1 for a scsi cmd which sends * data to the device, set to 0 for cmds which read data from the dev. * \param cmd buffer holding the command to send * \param cmd_size sizeof cmd buffer * \param sense buffer for returning scsi sense information * \param sense_size sizeof sense buffer * \param data buffer containing informatino to write to the device * (to_dev is 1), or to store data read from the device (to_dev 0). * * Send a SCSI command to a usb scsi port attached device. * * \return a gphoto2 error code **/ int gp_port_send_scsi_cmd (GPPort *port, int to_dev, char *cmd, int cmd_size, char *sense, int sense_size, char *data, int data_size) { int retval; gp_log (GP_LOG_DEBUG, "gphoto2-port", "Sending scsi cmd:"); gp_log_data ("gphoto2-port", cmd, cmd_size); if (to_dev && data_size) { gp_log (GP_LOG_DEBUG, "gphoto2-port", "scsi cmd data:"); gp_log_data ("gphoto2-port", data, data_size); } CHECK_NULL (port); CHECK_INIT (port); memset (sense, 0, sense_size); CHECK_SUPP (port, "send_scsi_cmd", port->pc->ops->send_scsi_cmd); retval = port->pc->ops->send_scsi_cmd (port, to_dev, cmd, cmd_size, sense, sense_size, data, data_size); gp_log (GP_LOG_DEBUG, "gphoto2-port", "scsi cmd result: %d", retval); if (sense[0] != 0) { gp_log (GP_LOG_DEBUG, "gphoto2-port", "sense data:"); gp_log_data ("gphoto2-port", sense, sense_size); /* https://secure.wikimedia.org/wikipedia/en/wiki/Key_Code_Qualifier */ gp_log(GP_LOG_DEBUG, "gphoto2-port","sense decided:"); if ((sense[0]&0x7f)!=0x70) { gp_log(GP_LOG_DEBUG, "gphoto2-port","\tInvalid header."); } gp_log(GP_LOG_DEBUG, "gphoto2-port", "\tCurrent command read filemark: %s",(sense[2]&0x80)?"yes":"no"); gp_log(GP_LOG_DEBUG, "gphoto2-port", "\tEarly warning passed: %s",(sense[2]&0x40)?"yes":"no"); gp_log(GP_LOG_DEBUG, "gphoto2-port", "\tIncorrect blocklengt: %s",(sense[2]&0x20)?"yes":"no"); gp_log(GP_LOG_DEBUG, "gphoto2-port", "\tSense Key: %d",sense[2]&0xf); if (sense[0]&0x80) gp_log(GP_LOG_DEBUG, "gphoto2-port", "\tResidual Length: %d",sense[3]*0x1000000+sense[4]*0x10000+sense[5]*0x100+sense[6]); gp_log(GP_LOG_DEBUG, "gphoto2-port", "\tAdditional Sense Length: %d",sense[7]); gp_log(GP_LOG_DEBUG, "gphoto2-port", "\tAdditional Sense Code: %d",sense[12]); gp_log(GP_LOG_DEBUG, "gphoto2-port", "\tAdditional Sense Code Qualifier: %d",sense[13]); if (sense[15]&0x80) { gp_log(GP_LOG_DEBUG, "gphoto2-port", "\tIllegal Param is in %s",(sense[15]&0x40)?"the CDB":"the Data Out Phase"); if (sense[15]&0x8) { gp_log(GP_LOG_DEBUG, "gphoto2-port", "Pointer at %d, bit %d",sense[16]*256+sense[17],sense[15]&0x7); } } } if (!to_dev && data_size) { gp_log (GP_LOG_DEBUG, "gphoto2-port", "scsi cmd data:"); gp_log_data ("gphoto2-port", data, data_size); } return retval; }
static uint16_t ptp_ptpip_generic_read (PTPParams *params, int fd, PTPIPHeader *hdr, unsigned char**data) { int ret, len, curread; unsigned char *xhdr; xhdr = (unsigned char*)hdr; curread = 0; len = sizeof (PTPIPHeader); while (curread < len) { ret = read (fd, xhdr + curread, len - curread); if (ret == -1) { perror ("read PTPIPHeader"); return PTP_RC_GeneralError; } gp_log_data ( "ptpip/generic_read", (char*)xhdr+curread, ret); curread += ret; if (ret == 0) { gp_log (GP_LOG_ERROR, "ptpip", "End of stream after reading %d bytes of ptpipheader", ret); return PTP_RC_GeneralError; } } len = dtoh32 (hdr->length) - sizeof (PTPIPHeader); if (len < 0) { gp_log (GP_LOG_ERROR, "ptpip/generic_read", "len < 0, %d?", len); return PTP_RC_GeneralError; } *data = malloc (len); if (!*data) { gp_log (GP_LOG_ERROR, "ptpip/generic_read", "malloc failed."); return PTP_RC_GeneralError; } curread = 0; while (curread < len) { ret = read (fd, (*data)+curread, len-curread); if (ret == -1) { gp_log (GP_LOG_ERROR, "ptpip/generic_read", "error %d in reading PTPIP data", errno); free (*data);*data = NULL; return PTP_RC_GeneralError; } else { gp_log_data ( "ptpip/generic_read", (char*)((*data)+curread), ret); } if (ret == 0) break; curread += ret; } if (curread != len) { gp_log (GP_LOG_ERROR, "ptpip/generic_read", "read PTPIP data, ret %d vs len %d", ret, len); free (*data);*data = NULL; return PTP_RC_GeneralError; } return PTP_RC_OK; }
/** * \brief Send a USB interface control message with input data * * \param port a GPPort * \param request control request code * \param value control value * \param index control index * \param bytes pointer to data * \param size size of the data * * Sends a specific USB control command and read associated data. * * \return a gphoto2 error code */ int gp_port_usb_msg_interface_read (GPPort *port, int request, int value, int index, char *bytes, int size) { int retval; gp_log (GP_LOG_DEBUG, "gphoto2-port", "Reading message " "(request=0x%x value=0x%x index=0x%x size=%i=0x%x)...", request, value, index, size, size); CHECK_NULL (port); CHECK_INIT (port); CHECK_SUPP (port, "msg_read", port->pc->ops->msg_interface_read); retval = port->pc->ops->msg_interface_read (port, request, value, index, bytes, size); CHECK_RESULT (retval); if (retval != size) gp_log (GP_LOG_DEBUG, "gphoto2-port", "Could only read %i " "out of %i byte(s)", retval, size); gp_log_data ("gphoto2-port", bytes, retval); return (retval); }
/** * \brief Check for intterupt. * * \param port a GPPort * \param data a pointer to an allocated buffer * \param size the number of bytes that should be read * * Reads a specified number of bytes from the interrupt endpoint * into the supplied buffer. * Function waits port->timeout miliseconds for data on interrupt endpoint. * * \return a gphoto2 error code **/ int gp_port_check_int (GPPort *port, char *data, int size) { int retval; gp_log (GP_LOG_DEBUG, "gphoto2-port", ngettext( "Reading %i=0x%x byte from interrupt endpoint...", "Reading %i=0x%x bytes from interrupt endpoint...", size), size, size); CHECK_NULL (port); CHECK_INIT (port); /* Check if we read as many bytes as expected */ CHECK_SUPP (port, "check_int", port->pc->ops->check_int); retval = port->pc->ops->check_int (port, data, size, port->timeout); CHECK_RESULT (retval); if (retval != size) gp_log (GP_LOG_DEBUG, "gphoto2-port", _("Could only read %i " "out of %i byte(s)"), retval, size); gp_log_data ("gphoto2-port", data, retval); return (retval); }
uint16_t ptp_usb_control_get_device_status (PTPParams *params, char *buffer, int *size) { Camera *camera = ((PTPData *)params->data)->camera; int ret; ret = gp_port_usb_msg_class_read (camera->port, 0x67, 0x0000, 0x0000, buffer, *size); if (ret < GP_OK) return PTP_ERROR_IO; gp_log_data ("ptp2/get_device_status", buffer, ret); *size = ret; return PTP_RC_OK; }
uint16_t ptp_usb_control_get_extended_event_data (PTPParams *params, char *buffer, int *size) { Camera *camera = ((PTPData *)params->data)->camera; int ret; gp_log (GP_LOG_DEBUG, "ptp2/get_extended_event_data", "get event data"); ret = gp_port_usb_msg_class_read (camera->port, 0x65, 0x0000, 0x0000, buffer, *size); if (ret < GP_OK) return PTP_ERROR_IO; *size = ret; gp_log_data ("ptp2/get_extended_event_data", buffer, ret); return PTP_RC_OK; }
/** * \brief Check for interrupt without wait * \param port a GPPort * \param data a pointer to an allocated buffer * \param size the number of bytes that should be read * * Reads a specified number of bytes from the inerrupt endpoint * into the supplied buffer. * Function waits 50 miliseconds for data on interrupt endpoint. * * \return a gphoto2 error code **/ int gp_port_check_int_fast (GPPort *port, char *data, int size) { int retval; CHECK_NULL (port); CHECK_INIT (port); /* Check if we read as many bytes as expected */ CHECK_SUPP (port, "check_int", port->pc->ops->check_int); retval = port->pc->ops->check_int (port, data, size, FAST_TIMEOUT); CHECK_RESULT (retval); #ifdef IGNORE_EMPTY_INTR_READS if (retval != size && retval != 0 ) #else if (retval != size ) #endif gp_log (GP_LOG_DEBUG, "gphoto2-port", ngettext( "Could only read %i out of %i byte", "Could only read %i out of %i bytes", size ), retval, size); #ifdef IGNORE_EMPTY_INTR_READS if ( retval != 0 ) { #endif /* For Canon cameras, we will make lots of reads that will return zero length. Don't bother to log them as errors. */ gp_log (GP_LOG_DEBUG, "gphoto2-port", ngettext( "Reading %i=0x%x byte from interrupt endpoint (fast)...", "Reading %i=0x%x bytes from interrupt endpoint (fast)...", size ), size, size); gp_log_data ("gphoto2-port", data, retval); #ifdef IGNORE_EMPTY_INTR_READS } #endif return (retval); }
/** * \brief Send a USB control message with output data * * \param port a GPPort * \param request control request code * \param value control value * \param index control index * \param bytes pointer to data * \param size size of the data * * Sends a specific USB control command and write associated data. * * \return a gphoto2 error code */ int gp_port_usb_msg_write (GPPort *port, int request, int value, int index, char *bytes, int size) { int retval; gp_log (GP_LOG_DEBUG, "gphoto2-port", _("Writing message " "(request=0x%x value=0x%x index=0x%x size=%i=0x%x)..."), request, value, index, size, size); gp_log_data ("gphoto2-port", bytes, size); CHECK_NULL (port); CHECK_INIT (port); CHECK_SUPP (port, "msg_write", port->pc->ops->msg_write); retval = port->pc->ops->msg_write(port, request, value, index, bytes, size); CHECK_RESULT (retval); return (retval); }
static uint16_t ptp_ptpip_init_command_request (PTPParams* params) { char hostname[100]; unsigned char* cmdrequest; unsigned int i; int len, ret; unsigned char guid[16]; ptp_nikon_getptpipguid(guid); if (gethostname (hostname, sizeof(hostname))) return PTP_RC_GeneralError; len = ptpip_initcmd_name + (strlen(hostname)+1)*2 + 4; cmdrequest = malloc(len); htod32a(&cmdrequest[ptpip_type],PTPIP_INIT_COMMAND_REQUEST); htod32a(&cmdrequest[ptpip_len],len); memcpy(&cmdrequest[ptpip_initcmd_guid], guid, 16); for (i=0;i<strlen(hostname)+1;i++) { /* -> ucs-2 in little endian */ cmdrequest[ptpip_initcmd_name+i*2] = hostname[i]; cmdrequest[ptpip_initcmd_name+i*2+1] = 0; } htod16a(&cmdrequest[ptpip_initcmd_name+(strlen(hostname)+1)*2],PTPIP_VERSION_MINOR); htod16a(&cmdrequest[ptpip_initcmd_name+(strlen(hostname)+1)*2+2],PTPIP_VERSION_MAJOR); gp_log_data ( "ptpip/init_cmd", (char*)cmdrequest, len); ret = write (params->cmdfd, cmdrequest, len); free (cmdrequest); if (ret == -1) { perror("write init cmd request"); return PTP_RC_GeneralError; } gp_log (GP_LOG_ERROR,"ptpip/init_cmd", "return %d / len %d", ret, len); if (ret != len) { gp_log (GP_LOG_ERROR, "ptpip", "return %d vs len %d", ret, len); return PTP_RC_GeneralError; } return PTP_RC_OK; }
static uint16_t ptp_ptpip_init_event_request (PTPParams* params) { unsigned char evtrequest[ptpip_eventinit_size]; int ret; htod32a(&evtrequest[ptpip_type],PTPIP_INIT_EVENT_REQUEST); htod32a(&evtrequest[ptpip_len],ptpip_eventinit_size); htod32a(&evtrequest[ptpip_eventinit_idx],params->eventpipeid); gp_log_data ( "ptpip/init_event", (char*)evtrequest, ptpip_eventinit_size); ret = write (params->evtfd, evtrequest, ptpip_eventinit_size); if (ret == -1) { perror("write init evt request"); return PTP_RC_GeneralError; } if (ret != ptpip_eventinit_size) { gp_log (GP_LOG_ERROR, "ptpip", "unexpected retsize %d, expected %d", ret, ptpip_eventinit_size); return PTP_RC_GeneralError; } return PTP_RC_OK; }
/** * \brief Writes a specified amount of data to a port. * \param port a #GPPort * \param data the data to write to the port * \param size the number of bytes to write to the port * * Writes data to the port. On non-serial ports the amount of data * written is returned (and not just GP_OK). * * \return a negative gphoto2 error code or the amount of data written. **/ int gp_port_write (GPPort *port, const char *data, int size) { int retval; gp_log (GP_LOG_DEBUG, "gphoto2-port", _("Writing %i=0x%x byte(s) " "to port..."), size, size); CHECK_NULL (port && data); CHECK_INIT (port); gp_log_data ("gphoto2-port", data, size); /* Check if we wrote all bytes */ CHECK_SUPP (port, "write", port->pc->ops->write); retval = port->pc->ops->write (port, data, size); CHECK_RESULT (retval); if ((port->type != GP_PORT_SERIAL) && (retval != size)) gp_log (GP_LOG_DEBUG, "gphoto2-port", ngettext("Could only write %i out of %i byte","Could only write %i out of %i bytes",size), retval, size); return (retval); }
/** * \brief Read data from port * * \param port a #GPPort * \param data a pointer to an allocated buffer * \param size the number of bytes that should be read * * Reads a specified number of bytes from the port into the supplied buffer. * It returns the number of bytes read or a negative error code. * * \return a gphoto2 error code or the amount of data read **/ int gp_port_read (GPPort *port, char *data, int size) { int retval; gp_log (GP_LOG_DEBUG, "gphoto2-port", _("Reading %i=0x%x bytes from port..."), size, size); CHECK_NULL (port); CHECK_INIT (port); /* Check if we read as many bytes as expected */ CHECK_SUPP (port, "read", port->pc->ops->read); retval = port->pc->ops->read (port, data, size); CHECK_RESULT (retval); if (retval != size) gp_log (GP_LOG_DEBUG, "gphoto2-port", _("Could only read %i " "out of %i byte(s)"), retval, size); gp_log_data ("gphoto2-port", data, retval); return (retval); }
/* send / receive functions */ uint16_t ptp_ptpip_sendreq (PTPParams* params, PTPContainer* req) { int ret; int len = 18+req->Nparam*4; unsigned char *request = malloc(len); ptp_ptpip_check_event (params); htod32a(&request[ptpip_type],PTPIP_CMD_REQUEST); htod32a(&request[ptpip_len],len); htod32a(&request[ptpip_cmd_dataphase],1); /* FIXME: dataphase handling */ htod16a(&request[ptpip_cmd_code],req->Code); htod32a(&request[ptpip_cmd_transid],req->Transaction_ID); switch (req->Nparam) { case 5: htod32a(&request[ptpip_cmd_param5],req->Param5); case 4: htod32a(&request[ptpip_cmd_param4],req->Param4); case 3: htod32a(&request[ptpip_cmd_param3],req->Param3); case 2: htod32a(&request[ptpip_cmd_param2],req->Param2); case 1: htod32a(&request[ptpip_cmd_param1],req->Param1); case 0: default: break; } gp_log_data ( "ptpip/oprequest", (char*)request, len); ret = write (params->cmdfd, request, len); free (request); if (ret == -1) perror ("sendreq/write to cmdfd"); if (ret != len) { gp_log (GP_LOG_ERROR, "ptpip","ptp_ptpip_sendreq() len =%d but ret=%d", len, ret); return PTP_RC_OK; } return PTP_RC_OK; }
uint16_t ptp_ptpip_senddata (PTPParams* params, PTPContainer* ptp, uint64_t size, PTPDataHandler *handler ) { unsigned char request[0x14]; unsigned int curwrite, towrite; int ret; unsigned char* xdata; htod32a(&request[ptpip_type],PTPIP_START_DATA_PACKET); htod32a(&request[ptpip_len],sizeof(request)); htod32a(&request[ptpip_startdata_transid + 8],ptp->Transaction_ID); htod32a(&request[ptpip_startdata_totallen + 8],size); htod32a(&request[ptpip_startdata_unknown + 8],0); gp_log_data ( "ptpip/senddata", (char*)request, sizeof(request)); ret = write (params->cmdfd, request, sizeof(request)); if (ret == -1) perror ("sendreq/write to cmdfd"); if (ret != sizeof(request)) { gp_log (GP_LOG_ERROR, "ptpip/senddata", "ptp_ptpip_senddata() len=%d but ret=%d", (int)sizeof(request), ret); return PTP_RC_GeneralError; } xdata = malloc(WRITE_BLOCKSIZE+8+4); if (!xdata) return PTP_RC_GeneralError; curwrite = 0; while (curwrite < size) { unsigned long type, written, towrite2, xtowrite; ptp_ptpip_check_event (params); towrite = size - curwrite; if (towrite > WRITE_BLOCKSIZE) { towrite = WRITE_BLOCKSIZE; type = PTPIP_DATA_PACKET; } else { type = PTPIP_END_DATA_PACKET; } ret = handler->getfunc (params, handler->priv, towrite, &xdata[ptpip_data_payload+8], &xtowrite); if (ret == -1) { perror ("getfunc in senddata failed"); free (xdata); return PTP_RC_GeneralError; } towrite2 = xtowrite + 12; htod32a(&xdata[ptpip_type], type); htod32a(&xdata[ptpip_len], towrite2); htod32a(&xdata[ptpip_data_transid+8], ptp->Transaction_ID); gp_log_data("ptpip/senddata", (char*)xdata, towrite2); written = 0; while (written < towrite2) { ret = write (params->cmdfd, xdata+written, towrite2-written); if (ret == -1) { perror ("write in senddata failed"); free (xdata); return PTP_RC_GeneralError; } written += ret; } curwrite += towrite; } free (xdata); return PTP_RC_OK; }
/* This function reads the Microsoft OS Descriptor and looks inside to * find if it is a MTP device. This is the similar to the way that * Windows Media Player 10 uses. * It is documented to some degree on various internet pages. */ static int gp_port_usb_match_mtp_device(struct libusb_device *dev,int *configno, int *interfaceno, int *altsettingno) { /* Marcus: Avoid this probing altogether, its too unstable on some devices */ return 0; #if 0 char buf[1000], cmd; int ret,i,i1,i2, xifaces,xnocamifaces; usb_dev_handle *devh; /* All of them are "vendor specific" device class */ #if 0 if ((desc.bDeviceClass!=0xff) && (desc.bDeviceClass!=0)) return 0; #endif if (dev->config) { xifaces = xnocamifaces = 0; for (i = 0; i < desc.bNumConfigurations; i++) { unsigned int j; for (j = 0; j < dev->config[i].bNumInterfaces; j++) { int k; xifaces++; for (k = 0; k < dev->config[i].interface[j].num_altsetting; k++) { struct usb_interface_descriptor *intf = &dev->config[i].interface[j].altsetting[k]; if ( (intf->bInterfaceClass == LIBUSB_CLASS_HID) || (intf->bInterfaceClass == LIBUSB_CLASS_PRINTER) || (intf->bInterfaceClass == LIBUSB_CLASS_AUDIO) || (intf->bInterfaceClass == LIBUSB_CLASS_HUB) || (intf->bInterfaceClass == LIBUSB_CLASS_COMM) || (intf->bInterfaceClass == 0xe0) /* wireless/bluetooth*/ ) xnocamifaces++; } } } } if (xifaces == xnocamifaces) /* only non-camera ifaces */ return 0; devh = usb_open (dev); if (!devh) return 0; /* * Loop over the device configurations and interfaces. Nokia MTP-capable * handsets (possibly others) typically have the string "MTP" in their * MTP interface descriptions, that's how they can be detected, before * we try the more esoteric "OS descriptors" (below). */ if (dev->config) { for (i = 0; i < desc.bNumConfigurations; i++) { unsigned int j; for (j = 0; j < dev->config[i].bNumInterfaces; j++) { int k; for (k = 0; k < dev->config[i].interface[j].num_altsetting; k++) { buf[0] = '\0'; ret = usb_get_string_simple(devh, dev->config[i].interface[j].altsetting[k].iInterface, (char *) buf, 1024); if (ret < 3) continue; if (strcmp((char *) buf, "MTP") == 0) { gp_log (GP_LOG_DEBUG, "mtp matcher", "Configuration %d, interface %d, altsetting %d:\n", i, j, k); gp_log (GP_LOG_DEBUG, "mtp matcher", " Interface description contains the string \"MTP\"\n"); gp_log (GP_LOG_DEBUG, "mtp matcher", " Device recognized as MTP, no further probing.\n"); goto found; } } } } } /* get string descriptor at 0xEE */ ret = usb_get_descriptor (devh, 0x03, 0xee, buf, sizeof(buf)); if (ret > 0) gp_log_data("get_MS_OSD",buf, ret); if (ret < 10) goto errout; if (!((buf[2] == 'M') && (buf[4]=='S') && (buf[6]=='F') && (buf[8]=='T'))) goto errout; cmd = buf[16]; ret = usb_control_msg (devh, USB_ENDPOINT_IN|USB_RECIP_DEVICE|LIBUSB_REQUEST_TYPE_VENDOR, cmd, 0, 4, buf, sizeof(buf), 1000); if (ret == -1) { gp_log (GP_LOG_ERROR, "mtp matcher", "control message says %d\n", ret); goto errout; } if (buf[0] != 0x28) { gp_log (GP_LOG_ERROR, "mtp matcher", "ret is %d, buf[0] is %x\n", ret, buf[0]); goto errout; } if (ret > 0) gp_log_data("get_MS_ExtDesc",buf, ret); if ((buf[0x12] != 'M') || (buf[0x13] != 'T') || (buf[0x14] != 'P')) { gp_log (GP_LOG_ERROR, "mtp matcher", "buf at 0x12 is %02x%02x%02x\n", buf[0x12], buf[0x13], buf[0x14]); goto errout; } ret = usb_control_msg (devh, USB_ENDPOINT_IN|USB_RECIP_DEVICE|LIBUSB_REQUEST_TYPE_VENDOR, cmd, 0, 5, buf, sizeof(buf), 1000); if (ret == -1) goto errout; if (buf[0] != 0x28) { gp_log (GP_LOG_ERROR, "mtp matcher", "ret is %d, buf[0] is %x\n", ret, buf[0]); goto errout; } if (ret > 0) gp_log_data("get_MS_ExtProp",buf, ret); if ((buf[0x12] != 'M') || (buf[0x13] != 'T') || (buf[0x14] != 'P')) { gp_log (GP_LOG_ERROR, "mtp matcher", "buf at 0x12 is %02x%02x%02x\n", buf[0x12], buf[0x13], buf[0x14]); goto errout; } found: usb_close (devh); /* Now chose a nice interface for us to use ... Just take the first. */ if (desc.bNumConfigurations > 1) gp_log (GP_LOG_ERROR, "mtp matcher", "The device has %d configurations!\n", desc.bNumConfigurations); for (i = 0; i < desc.bNumConfigurations; i++) { struct usb_config_descriptor *config = &dev->config[i]; if (config->bNumInterfaces > 1) gp_log (GP_LOG_ERROR, "mtp matcher", "The configuration has %d interfaces!\n", config->bNumInterfaces); for (i1 = 0; i1 < config->bNumInterfaces; i1++) { struct usb_interface *interface = &config->interface[i1]; if (interface->num_altsetting > 1) gp_log (GP_LOG_ERROR, "mtp matcher", "The interface has %d altsettings!\n", interface->num_altsetting); for (i2 = 0; i2 < interface->num_altsetting; i2++) { *configno = i; *interfaceno = i1; *altsettingno = i2; return 1; } } } return 1; errout: usb_close (devh); return 0; #endif }