How to NAK an HID report request

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

How to NAK an HID report request

1,880 Views
gjm
Contributor I

Background: I'm using a KL26 MCU, and I'm still on version 1.3.0 of the KSDK. (But an answer that begins "First, move to a more recent SDK version" is better than none at all :-).) I'm making a USB HID device. It runs at the hilariously-misnamed "full speed".

Under some circumstances (more details available on request, but I don't think they're important) I would like my device to respond to get/set-report requests with a NAK, so that the host will retry a little later.

I haven't found anything in the documentation indicating how to do this (nor anything indicating that it's impossible).

My report-processing code is called from the HID class-specific callback, so in outline it looks like this:

uint8_t class_callback(uint8_t request, uint16_t value,                        uint8_t ** pData, uint32_t * pSize, void * arg) {   uint8_t lvalue = value & 0xFF;   switch (request) {     case USB_HID_GET_REPORT_REQUEST:       // write report into buffer       *pData = &buffer;       *pSize = /* size of report */;       return USB_OK;     case USB_HID_SET_REPORT_REQUEST:       // parse report and do whatever's necessary       return USB_OK;     // other cases   } }

In particular, I am not explicitly calling USB_Class_HID_Send_Data (nor USB_Class_Send_Data, nor usb_device_send_data, nor anything else that sends data explicitly). There's a thread elsewhere in the NXP Community where someone asks a question similar to mine and the answer is to refrain from calling the data-sending function; but that doesn't seem applicable here.

Is there anything I can do that will make the host get a NAK in response to its request? For instance, will setting *pSize = 0 do that, or will it simply produce a report of length zero, or is its effect undefined and not-to-be-relied-on?

Looking briefly at the USB stack source code, it looks to me -- but it's fairly complicated and I haven't spent long trying to disentangle it -- as if the return value from the class-specific callback is completely ignored. (I wondered about returning USBERR_DEVICE_BUSY or something, but it doesn't seem as if that will help.)

I can do some experiments with a USB analyser, but for obvious reasons I would prefer my code to be based on something more solid than "I tried this and it seemed to work" :-).

Thanks!

Labels (2)
0 Kudos
7 Replies

1,181 Views
mjbcswitzerland
Specialist V

Hi Gareth

It is not possible to send a NAK token "in response" to a specific reception. This is because the ACK is sent (by HW) as soon as the USB controller has received the SETUP (with get/set report or whatever) without an error and before it is passed on to the software. The only thing that the software can decide to do at this point is not to send back a zero-data frame (the status stage) so that the host doesn't continue (yet). The host may try again later or may declare an error and stop working (would need to be tried but may be dependent on the host implementation).

To get a NAK sent you need to block the endpoint 0 reception by keeping it (for example) as being owned by the software still after the previous reception. This will cause the device to always NAK all receptions on the control endpoint until the ownership is set back to USB controller ownership. This is simple to do but is general (not for specific SETUPs only) and will not cause the host to report an error (as long as not done for too long I expect).

Another possibility may be to return a STALL when you don't want to accept particular get/set reports since this allows reacting to a specific case 'after' the SETUP has been received. The host will clear the stall, which will take a short time and probably retry a little later.

As reference: In the uTasker USB stack the user can hold the control endpoint buffer by returning MAINTAIN_OWNERSHIP to a setup frame and later free it with fnConsumeUSB_out(endpoint_number)), or stall by returning STALL_ENDPOINT.

Regards

Mark

http://www.utasker.com/kinetis/FRDM-KL26Z.html
http://www.utasker.com/kinetis/TEENSY_LC.html

0 Kudos

1,181 Views
gjm
Contributor I

OK. So it seems like there are (up to) three possibilities. (1) NAK in the setup stage. This can't be controlled in software on a per-request basis, but there may be a way to tell the hardware in advance that we're busy so that it will do that. (2) NAK in the status stage. This may be controllable in software. (3) STALL in the data or status stage. This may be controllable in software.

My understanding (which may very well be defective; I would be glad to be corrected if so) is that #3 is intended for use when something has actually gone wrong, which isn't really the case here, so either #1 or #2 seems preferable.

I don't see anything in the documentation of the Kinetis SDK (reminder: I'm using version 1.3 of the KSDK which is not the latest; I haven't checked what more recent KSDK versions support) that indicates how to do either #1 or #2. It looks as if there's a function usb_device_stall_endpoint (and a corresponding one for unstalling) that would let me do #3. Again, stalling feels to me like the Wrong Thing but I'd rather do a documented Slightly Wrong Thing than an undocumented hack that I hope does the Right Thing :-).

So this is as much a question about the Kinetis SDK as about the hardware or the USB protocol itself (my apologies if there's some other place I should be asking such questions): does it provide a way to make #1 or #2 happen?

0 Kudos

1,181 Views
mjbcswitzerland
Specialist V

Gareth

STALL is not necessary to indicate an error - it is a normal part of various class operations. Usually it signals that something is not supported, rather than being defective, however it also is often used to signal that something "isn't ready" too in real devices.

For example, when a memory stick is connected to a host some are quite slow to become usable and they may STALL on some standard requests. The host then repeats them and at some point the response comes back and all starts working. I have developed USB drivers and classes for about 9 years and tested many devices and seen stuff that is very specific to individual devices where there is probably no hard specification defining how it should/must be done (especially pen drives) so it may be worth a try if it controls the behavior as you want.

Regards

Mark

0 Kudos

1,181 Views
gjm
Contributor I

OK, that's good to know; thanks for dispelling some of my ignorance.

0 Kudos

1,181 Views
gjm
Contributor I

Hmm. I'm not sure I can do #3 after all, because of a layering issue.

There is indeed a function usb_device_stall_endpoint, which takes a usb_device_handle. I would have a usb_device_handle if I were initializing the device directly via usb_device_init, but I'm not: I'm calling USB_Class_HID_Init. Under the hood that calls usb_device_init and keeps track of the device handle, but there doesn't seem to be any API for getting at that handle.

If I don't mind being horrible and hackish, I can still get at the handle via ((hid_device_struct *)hid_handle)->handle but this is clearly very unsupported -- that thing I'm casting to a hid_device_struct * is exposed to client code as a hid_handle_t, and that's a typedef for uint32_t: it's obviously intended to be opaque. Which in turn suggests that the HID layer may be written with the assumption that client code isn't going behind its back and calling lower-level functions like usb_device_stall_endpoint.

So ... Is there a not-too-cowboyish way to make any of #1, #2, or #3 happen using the Kinetis SDK (reminder: I'm using the old version 1.3, but an answer depending on a newer version is better than no answer)?

0 Kudos

1,181 Views
mjbcswitzerland
Specialist V

Gareth

For SDK specific questions you may do better in the SDK forum.

For a complete solution you could also look at the uTasker project - it is Open Source and free so nothing to be frightened of and allows full control, as well a simulation of the Kinetis USB controller, making it simple to follow and test (9 years of industrial reliability and compatibility between all Kinetis USB parts).

Regards

Mark

0 Kudos

1,181 Views
gjm
Contributor I

I had failed to find the SDK forum. I have now looked again and found it, and will ask there. My apologies. Thanks for your advice.

0 Kudos