2023-11-14 09:25 AM
Hello,
I am trying to implement the DFU protocol in an Android app. I am following AN3156.
I am using UsbDeviceConnection class for the control transfer in android.
I am able to write to the memory to any address using the routines described in AN3156 (using command DFU_DNLOAD).
I am NOT able to read from memory from a specific address using command DFU_UPLOAD. I am only able to read from the very first address of flash memory, whose pointer is by default set to it (0x08000000). For some reason, when running the setAddressPointer routine, the controlTransfer method then returns -1 (USB communication fails). This routine uses command DFU_DNLOAD to set the address pointer.
My code is rather hard to follow since it uses multiple functions but I will post it anyway. I can provide any clarification of it of course. But it essencially just follows the procedures in AN3156, regarding the IDLE state, and DFU_GETSTATUS command. And since those routines are common, I created functions.
So, the problem is in function readBlock(). if I comment out the setAddressPointer, it reads successfully from the default address 0x08000000 as I said earlier. But when invoke this routine to set the address (which, as I said earlier, works fine for writting blocks to a specific address (DFU_DNLOAD command)), it messes up the USB communication itself. The controlTransfer does returns an error when DFU_UPLOAD command is sent.
Any help would be appreciated.
private final static int USB_DIR_IN = 0x80;
private final static int DFU_RequestType = 0x21; // '2' => Class request ; '1' => to interface
private final static int STATE_IDLE = 0x00;
private final static int STATE_DFU_IDLE = 0x02;
private final static int STATE_DFU_UPLOAD_IDLE = 0x09;
private final static int STATE_DFU_ERROR = 0x0A;
private final static int STATE_DFU_UPLOAD_BUSY = 0x92;
private final static int DFU_DNLOAD = 0x01;
private final static int DFU_UPLOAD = 0x02;
private final static int DFU_GETSTATUS = 0x03;
private final static int DFU_CLRSTATUS = 0x04;
private final static int DFU_GETSTATE = 0x05;
public void readBlock(int address, byte[] block, int blockNumber) throws Exception {
DfuStatus dfuStatus = new DfuStatus();
clearWaitIDLE(dfuStatus);
//set address for the first block written
if (0 == blockNumber) {
Log.i(TAG, "reading address: 0x" + Integer.toHexString(address));
setAddressPointer(address);
}
clearWaitIDLE(dfuStatus);
upload(block, block.length, (blockNumber + 2));
//executeVerifyStatus(dfuStatus, "block read");
clearWaitIDLE(dfuStatus);
}
//execute and verify action using getStatus(). arguments: where the error occurs (for what operation)
private void executeVerifyStatus(DfuStatus dfuStatus, String operation) throws Exception {
getStatus(dfuStatus); // to execute
if (dfuStatus.bState != STATE_DFU_DOWNLOAD_BUSY) {
throw new Exception("error on " + operation + " , execution failed (dfuBUSY not returned)");
}
getStatus(dfuStatus); // to verify action
if (dfuStatus.bState == STATE_DFU_ERROR) {
throw new Exception("error on " + operation + " , verification failed (dfuERROR)");
}
}
// Clears status and waits for DFU_IDLE status. useful to crear DFU_ERROR status.
private void clearWaitIDLE(DfuStatus dfuStatus) throws Exception {
while (dfuStatus.bState != STATE_DFU_IDLE) {
clearStatus();
getStatus(dfuStatus);
}
}
private void getStatus(DfuStatus status) throws Exception {
byte[] buffer = new byte[6];
int length = usb.controlTransfer(DFU_RequestType | USB_DIR_IN, DFU_GETSTATUS, 0, 0, buffer, 6, 500);
if (length < 0) {
throw new Exception("USB Failed during getStatus");
}
status.bStatus = buffer[0]; // state during request
status.bState = buffer[4]; // state after request
status.bwPollTimeout = (buffer[3] & 0xFF) << 16;
status.bwPollTimeout |= (buffer[2] & 0xFF) << 8;
status.bwPollTimeout |= (buffer[1] & 0xFF);
}
private void clearStatus() throws Exception {
int length = usb.controlTransfer(DFU_RequestType, DFU_CLRSTATUS, 0, 0, null, 0, 0);
if (length < 0) {
throw new Exception("USB Failed during clearStatus");
}
}
private void upload(byte[] data, int length, int blockNum) throws Exception {
int len = usb.controlTransfer(DFU_RequestType | USB_DIR_IN, DFU_UPLOAD, blockNum, 0, data, length, 100);
if (len < 0) {
throw new Exception("USB comm failed during upload");
}
}
2023-11-14 09:43 AM
Double-check that the USB communication parameters, such as the endpoint, interface, and USB configuration, are set up correctly.
2023-11-14 09:54 AM
Of course they are, how else would the commands I mentioned all be working? As I said, the problem is that setting the address pointer before a DFU_UPLOAD command is issued (for reading at that address) is what messes up the communication for some reason, otherwise it works. As I said, DFU_UPLOAD works when no address pointer is set, and (see AN3156) the default address is 0x08000000.
2023-11-14 02:27 PM - edited 2023-11-14 02:28 PM
Dear @LLope.31 ,
Thank you for pointing this case , indeed very strange behavior , it should work for both Uploading and downloading some specific blocks , here is the very first specification of our DFU protocol extension - please see Figure 21 and also subsequent sections , if it helps to move on the different states especially for Download command
I have a doubt if the SetPointer subset of DOWNLOAD is well implemented at driver host level . Let me know please and the best to provide a usb traffic sniffer log to debug your case versus our Tools .
hope it helps ,
Cheers,
STOne-32
2023-11-17 01:36 PM
Hello, thank you for your help.
I did find a way to avoid the USB communication error. I changed my clearWaitIDLE function:
public void clearWaitIDLE(DfuStatus dfuStatus) throws Exception {
do {
clearStatus();
getStatus(dfuStatus);
} while (dfuStatus.bState != STATE_DFU_IDLE);
}
What this change means is that now I always clear the status (DFU_CLRSTATUS) even if the DFU_GETSTATUS returns dfuIDLE. This is very weird behaviour, because clearing the status should only be needed when we are NOT in IDLE state. But now I can successfully set the pointer to whatever address I wish and read the block. If you could explain to me why this happens it would be great.
But now I have another issue. I can't use the address increasing mechanism described in AN3156:
Address = ((wBlockNum – 2) × wTransferSize) + Address_Pointer
So the logic here is to increase wBlockNum every block read. But wTransferSize is ambiguous. Is this field wLength? If this field is not wLength but is actually the descriptor of the DFU, wTransferSize = 2048 bytes for my MCU, then we have problems. The USB DFU specification says that this descriptor is supposed to be the maximum block size, and that wLength that is sent in the control transfer the actually block size that we watn. So I assumed this, but I do not get any reads that make sense. Appart from the first address, when I increase the BlockNumber, it reads from a different address than the next block from the first. I do not understand what is going on, this formula above should be working exactly as described, and you guys need to fix this specification too because table 3 does not make sense, wValue for DFU_UPLOAD should not be "Zero", it should be BlockNumber... But you seem to get it right on the other documentation you sent in this message for some reason.
Anyways lots of ambiguity, and I'm sorry for not being able to provide USB sniffer logs, because I am running this on and Android phone.
Kind regards and I await your reply,
Luis
2023-11-21 01:47 AM
I will try soon to use libusb on my stm32 device to sniff the packets and debugg this, before implementing the logic in my Android app. I'll then post all the packet transactions.
2023-11-30 10:23 AM - edited 2023-11-30 10:26 AM
Hello again,
Today I successfully read the first 2 blocks of 2048 bytes.
My confusion was I thought wTransferSize was a variable we could specify through wLength (the number of bytes to read, so I thought it was also the block size). But turns out I was wrong, wTransferSize is fixed and is one of the descriptors of the interface, in the case of my STM32042, it's equal to 2048. What this means is, after reading somewhere between 2-2048 bytes and incrementing wBlockNum, the address always updates to: Address = ((wBlockNum – 2) × wTransferSize) + Address_Pointer, which has nothing to do with the number of bytes read.
There is just something I think is still wrong in the most recent application note AN3156, which is "Table 3. Summary of DFU class-specific requests". This table shoud actually be the same as Table 26 in UM0424, because firstly, DFU_GETSTATUS and DFU_GETSTATE should be IN endpoint requests, and the first bit in bmRequestType should be 1, and secondly, wValue in DFU_UPLOAD should not be Zero, it should be wBlockNum. I have been doing my DFU_GETSTATUS requests using IN endpoint, so I think this is wrong. Can someone clarify that I am in fact right about this being wrong?
I'll attach my libusb program for anyone that wants so see a simple implementation for reading the flash.
2023-11-30 11:26 AM
Dear @LLope.31
Very happy to see you are able to manage a successful read of 2 blocks of 2048 bytes and the explanation is correct. wBlockNum is fixed - exported by the device descriptor.
I confirm that the Table 3 of AN3156 should be fixed as in USB-IF DFU specification DFU_1.1.doc (usb.org) and a bit extended by us in UM0424 - while fixing the wBlockNum for Upload in wValue . Even the USB Spec in Table 3.1 is not accurate , but is in section 6.2.1 .
For the GET_STATUS and GET_STATE you are absolutely right both bmRequest first bit should be 1 !
an internal Request is done to fix that with this number : 167701 for future follow-up.
Thank you again very much for the valuable contribution.
Cheers,
STOne-32