//
// start
// when this method is called, I have been selected as the driver for this device.
// I can still return false to allow a different driver to load
//
bool 
local_IOath3kfrmwr::start(IOService *provider)
{
    IOReturn 				err;
    const IOUSBConfigurationDescriptor *cd;
    
    // Do all the work here, on an IOKit matching thread.
    
    // 0.1 Get my USB Device
    DEBUG_LOG("%s(%p)::start!\n", getName(), this);
    pUsbDev = OSDynamicCast(IOUSBDevice, provider);
    if(!pUsbDev) 
    {
        DEBUG_LOG("%s(%p)::start - Provider isn't a USB device!!!\n", getName(), this);
        return false;
    }

    // 0.2 Reset the device
    err = pUsbDev->ResetDevice();
    if (err)
    {
        DEBUG_LOG("%s(%p)::start - failed to reset the device\n", getName(), this);
        pUsbDev->close(this);
        return false;
    } else IOLog("%s(%p)::start: device reset\n", getName(), this);
    
    // 0.3 Find the first config/interface
    int numconf = 0;
    if ((numconf = pUsbDev->GetNumConfigurations()) < 1)
    {
        DEBUG_LOG("%s(%p)::start - no composite configurations\n", getName(), this);
        return false;
    } else DEBUG_LOG("%s(%p)::start: num configurations %d\n", getName(), this, numconf);
        
    cd = pUsbDev->GetFullConfigurationDescriptor(0);
    
    // Set the configuration to the first config
    if (!cd)
    {
        DEBUG_LOG("%s(%p)::start - no config descriptor\n", getName(), this);
        return false;
    }
	
    // 1.0 Open the USB device
    if (!pUsbDev->open(this))
    {
        DEBUG_LOG("%s(%p)::start - unable to open device for configuration\n", getName(), this);
        return false;
    }
    
    // 1.1 Set the configuration to the first config
    err = pUsbDev->SetConfiguration(this, cd->bConfigurationValue, true);
    if (err)
    {
        DEBUG_LOG("%s(%p)::start - unable to set the configuration\n", getName(), this);
        pUsbDev->close(this);
        return false;
    }
    
    DEBUG_LOG("%s(%p)::start - UPLOADED - %c\n", getName(), this,uploaded);
    //Uploading only one time
    if (!uploaded) {
        
    
    // 1.2 Get the status of the USB device (optional, for diag.)
    USBStatus status;
    err = pUsbDev->GetDeviceStatus(&status);
    if (err)
    {
        DEBUG_LOG("%s(%p)::start - unable to get device status\n", getName(), this);
        pUsbDev->close(this);
        return false;
    } else IOLog("%s(%p)::start: device status %d\n", getName(), this, (int)status);

    // 2.0 Find the interface for bulk endpoint transfers
    IOUSBFindInterfaceRequest request;
    request.bInterfaceClass = kIOUSBFindInterfaceDontCare;
    request.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
    request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
    request.bAlternateSetting = kIOUSBFindInterfaceDontCare;
    
    IOUSBInterface * intf = pUsbDev->FindNextInterface(NULL, &request);
    if (!intf) {
        DEBUG_LOG("%s(%p)::start - unable to find interface\n", getName(), this);
        pUsbDev->close(this);
        return false;
    }

    // 2.1 Open the interface
    if (!intf->open(this))
    {
        DEBUG_LOG("%s(%p)::start - unable to open interface\n", getName(), this);
        pUsbDev->close(this);
        return false;
    }

    // 2.2 Get info on endpoints (optional, for diag.)
    int numep = intf->GetNumEndpoints();
    DEBUG_LOG("%s(%p)::start: interface has %d endpoints\n", getName(), this, numep);
    
    UInt8 transferType = 0;
    UInt16 maxPacketSize = 0;
    UInt8 interval = 0;
    err = intf->GetEndpointProperties(0, 0x02, kUSBOut, &transferType, &maxPacketSize, &interval);
    if (err) {
        DEBUG_LOG("%s(%p)::start - failed to get endpoint 2 properties\n", getName(), this);
        intf->close(this);
        pUsbDev->close(this);
        return false;    
    } else DEBUG_LOG("%s(%p)::start: EP2 %d %d %d\n", getName(), this, transferType, maxPacketSize, interval);
    
    err = intf->GetEndpointProperties(0, 0x01, kUSBIn, &transferType, &maxPacketSize, &interval);
    if (err) {
        DEBUG_LOG("%s(%p)::start - failed to get endpoint 1 properties\n", getName(), this);
        intf->close(this);
        pUsbDev->close(this);
        return false;    
    } else {DEBUG_LOG("%s(%p)::start: EP1 %d %d %d\n", getName(), this, transferType, maxPacketSize, interval);
        }

    // 2.3 Get the pipe for bulk endpoint 2 Out
    IOUSBPipe * pipe = intf->GetPipeObj(0x02);
    if (!pipe) {
        DEBUG_LOG("%s(%p)::start - failed to find bulk out pipe\n", getName(), this);
        intf->close(this);
        pUsbDev->close(this);
        return false;    
    }
    /*  // TODO: Test the alternative way to do it:
     IOUSBFindEndpointRequest pipereq;
     pipereq.type = kUSBBulk;
     pipereq.direction = kUSBOut;
     pipereq.maxPacketSize = BULK_SIZE;
     pipereq.interval = 0;
     IOUSBPipe *pipe = intf->FindNextPipe(NULL, &pipereq);
     pipe = intf->FindNextPipe(pipe, &pipereq);
     if (!pipe) {
     DEBUG_LOG("%s(%p)::start - failed to find bulk out pipe 2\n", getName(), this);
     intf->close(this);
     pUsbDev->close(this);
     return false;    
     }
     */

    
    // 3.0 Send request to Control Endpoint to initiate the firmware transfer
    IOUSBDevRequest ctlreq;
    ctlreq.bmRequestType = USBmakebmRequestType(kUSBOut, kUSBVendor, kUSBDevice);
    ctlreq.bRequest = USB_REQ_DFU_DNLOAD;
    ctlreq.wValue = 0;
    ctlreq.wIndex = 0;
    ctlreq.wLength = 20;
    ctlreq.pData = firmware_buf;

#if 0  // Trying to troubleshoot the problem after Restart (with OSBundleRequired Root)
    for (int irep = 0; irep < 5; irep++) { // retry on error
        err = pUsbDev->DeviceRequest(&ctlreq); // (synchronous, will block)
        if (err)
            DEBUG_LOG("%s(%p)::start - failed to initiate firmware transfer (%d), retrying (%d)\n", getName(), this, err, irep+1);
        else
            break;
    }
#else
    err = pUsbDev->DeviceRequest(&ctlreq); // (synchronous, will block)
#endif
    if (err) {
        DEBUG_LOG("%s(%p)::start - failed to initiate firmware transfer (%d)\n", getName(), this, err);
        intf->close(this);
        pUsbDev->close(this);
        return false;
    }

    // 3.1 Create IOMemoryDescriptor for bulk transfers
    char buftmp[BULK_SIZE];
    IOMemoryDescriptor * membuf = IOMemoryDescriptor::withAddress(&buftmp, BULK_SIZE, kIODirectionNone);
    if (!membuf) {
        DEBUG_LOG("%s(%p)::start - failed to map memory descriptor\n", getName(), this);
        intf->close(this);
        pUsbDev->close(this);
        return false; 
    }
    err = membuf->prepare();
    if (err) {
        DEBUG_LOG("%s(%p)::start - failed to prepare memory descriptor\n", getName(), this);
        intf->close(this);
        pUsbDev->close(this);
        return false; 
    }
    
    // 3.2 Send the rest of firmware to the bulk pipe
    char * buf = firmware_buf;
    int size = sizeof(firmware_buf); 
    buf += 20;
    size -= 20;
    int ii = 1;
    while (size) {
        int to_send = size < BULK_SIZE ? size : BULK_SIZE; 
        
        memcpy(buftmp, buf, to_send);
        err = pipe->Write(membuf, 10000, 10000, to_send);
        if (err) {
            DEBUG_LOG("%s(%p)::start - failed to write firmware to bulk pipe (%d)\n", getName(), this, ii);
            intf->close(this);
            pUsbDev->close(this);
            return false; 
        }
        buf += to_send;
        size -= to_send;
        ii++;
    }
    IOLog("%s(%p)::start: firmware was sent to bulk pipe\n", getName(), this);
    
    err = membuf->complete();
    if (err) {
        DEBUG_LOG("%s(%p)::start - failed to complete memory descriptor\n", getName(), this);
        intf->close(this);
        pUsbDev->close(this);
        uploaded = false;
        return false; 
    }

    /*  // TODO: Test the alternative way to do it:
     IOMemoryDescriptor * membuf = IOMemoryDescriptor::withAddress(&firmware_buf[20], 246804-20, kIODirectionNone); // sizeof(firmware_buf)
     if (!membuf) {
     DEBUG_LOG("%s(%p)::start - failed to map memory descriptor\n", getName(), this);
     intf->close(this);
     pUsbDev->close(this);
     return false; 
     }
     err = membuf->prepare();
     if (err) {
     DEBUG_LOG("%s(%p)::start - failed to prepare memory descriptor\n", getName(), this);
     intf->close(this);
     pUsbDev->close(this);
     return false; 
     }
     
     //err = pipe->Write(membuf);
     err = pipe->Write(membuf, 10000, 10000, 246804-20, NULL);
     if (err) {
     DEBUG_LOG("%s(%p)::start - failed to write firmware to bulk pipe\n", getName(), this);
     intf->close(this);
     pUsbDev->close(this);
     return false; 
     }
     DEBUG_LOG("%s(%p)::start: firmware was sent to bulk pipe\n", getName(), this);
     */
    
    
    // 4.0 Get device status (it fails, but somehow is important for operational device)
    err = pUsbDev->GetDeviceStatus(&status);
    if (err)
    {
        DEBUG_LOG("%s(%p)::start - unable to get device status\n", getName(), this);
        intf->close(this);
        pUsbDev->close(this);
        
        DEBUG_LOG("%s(%p)::start - Firmware Uploaded State Changed to - %c\n", getName(), this,uploaded);
        // Set the Upload State
        uploaded = true;
        
        return false;
    } else {DEBUG_LOG("%s(%p)::start: device status %d\n", getName(), this, (int)status);
    
    }
    
    // Close the interface
    intf->close(this);
        
    DEBUG_LOG("%s(%p)::start - Firmware Uploaded State Changed to - %c\n", getName(), this,uploaded);
    // Set the Upload State
        uploaded = true;
    }
    else {
        IOLog("%s(%p)::Firmware already uploaded\n", getName(), this);
    }
    
    // Close the USB device
    pUsbDev->close(this);
    return false;  // return false to allow a different driver to load
}