Template_Endpoint_Control_W - Significance of LastPacketFull looping

Go To Last Post
13 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm looking thru some LUFA code (trying to understand how the USB communication works), and encountered this: Template_Endpoint_Control_W

Can anyone explain to me why the code is looping when "LastPacketFull" is true even though all bytes have been written ("Length" is zero) ?

All I see is that (compared to the loop at the bottom) one extra check is done ( Endpoint_IsSETUPReceived() ), and that (possibly, if no OUT packet is received) a last, empty packet is transmitted. And I have no idea why that is done, as the chapter "20.12.2 control read" in the PDF to the AT90USB162 does not mention it.


P.s.

@Admins and/or maintainers:

I've not copy-pasted the C code from the above link into this message, as I feel that would be wasting space. Though if this forum likes the code to be part of the question (as the contents of that link could change or even disappear) than let me know and I will edit this message accordingly.

Last Edited: Tue. Nov 1, 2016 - 06:11 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Nestor wrote:
I've not copy-pasted the C code from the above link into this message, as I feel that would be wasting space.

 

So, then, your posts will be devoid of code.  We'll just refer to that phenomenon as "empty Nestor".  ;)  [taking up some of that saved space; sorry I can't help with your LUFA query]

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Your best bet is to ask Dean. We only see him rarely here these days after he was "burned" by Atmel and then his next employer. To contact him the fast way is @abcminuser on Twitter:

 

https://twitter.com/abcminiuser?...

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
To contact him the fast way is @abcminuser on Twitter:
Thanks for that reference. If only I would be using Twitter (I'm not) ... :-)

Also, when I was looking for a contact address I noticed that the earliest date I could find was more than 5 years old, leading me to believe he might have ended any further involvement in it. Hence me posting the question here.

Also too bad, as I have a few more questions about it (like why, when the datasize is zero, a packet is send without any checking, or why the source data is, if its larger than the target buffer, just clipped -- throwing possibly important data away. Or when an RXOUTI packet is detected while sending the data the function returns with an OK -- even though not all data is send).
Oh well, those will just keep being the misteries of life/programming. :-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Nestor wrote:
Also, when I was looking for a contact address I noticed that the earliest date I could find was more than 5 years old, leading me to believe he might have ended any further involvement in it.

Oh no he's still very active - just not in the realm of AVRs any more. He wrote some famous projects for AVR (Buttload, LUFA are just two). He was then invited to work for Atmel and wen to live in Trondheim for a year or two. A lot of what is in things like ASF and Studio have his hand in them. He then moved back to Australia and worked for an internet startup making wi-fi controlled LED bulbs (but had a terrible time there!). So he's now moved on to pastures new.

 

But he does pop up from time to time to respond to questions about LUFA. As you aren't on Twitter, but I am, I'll tweet him to take a look at this thread.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

We only see him rarely here these days after he was "burned" by Atmel and then his next employer. To contact him the fast way is @abcminuser on Twitter:

Clarification: Atmel didn't burn me at all - I left begrudgingly because of my long time partner didn't want to stay in Australia (turned out for the best, given we got married earlier this year). I still miss the place and wasn't thrown out of the building when I visited to say hi a few months ago. The job I took after I got back was a whole other kettle of rotten fish, but I've since moved on to another completely awesome and decidedly more local job. I'm not on here much simply due to time; my last job sapped my will to live, and now I work so darn hard making complex products I don't have the stomach for more troubleshooting in the evenings. On the plus side, I actually wrote some XMEGA code the other day, which was a nice break from multi-threaded ARM system coding.

 

Can anyone explain to me why the code is looping when "LastPacketFull" is true even though all bytes have been written ("Length" is zero) ?

 

It's to ensure that a zero length packet is sent after the data has been sent, if the last packet completely filled the endpoint bank. A very quick search through the USB 2.0 spec yields the following paragraph - it's from the host end of the communications but applies equally to the device end:

 

8.5.3.2 Variable-length Data Stage
A control pipe may have a variable-length data phase in which the host requests more data than is contained
in the specified data structure. When all of the data structure is returned to the host, the function should
indicate that the Data stage is ended by returning a packet that is shorter than the MaxPacketSize for the
pipe. If the data structure is an exact multiple of wMaxPacketSize for the pipe, the function will return a
zero-length packet to indicate the end of the Data stage.

In USB land, data is sent from one end to another as a number of chunked packets, each up to the endpoint (or pipe, to use the host-side terminology) bank size, which is set in the device descriptors. Sending the data in packets makes for more efficient scheduling on the bus. USB transfers generally terminate when either all the data that one end expects has been sent, or a short packet (a bank of less data than the maximum bank size) has been sent. To terminate the data stage of the control request, the device needs to send a packet of zero bytes if the last packet it needed to send just happened to completely fill up the endpoint's bank to signal the end of the transfer to the host.

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Apologies Dean. I could have sworn that at the time you were looking for Atmel to let you "telework" and they wouldn't let you. I probably got the wrong end of the stick.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Apologies Dean. I could have sworn that at the time you were looking for Atmel to let you "telework" and they wouldn't let you. I probably got the wrong end of the stick.

Oh yes, good memory. That was a potential solution to the "girlfriend" problem, but fell apart due to overseas politics. Frankly I just appreciated that they even tried rather than just cutting their losses, so it's certainly not something I hold a grudge about. Not releasing AS7's backend on Linux as a separate download so I can quickly program devices without installing AS7, however...

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

abcminiuser wrote:

Can anyone explain to me why the code is looping when "LastPacketFull" is true even though all bytes have been written ("Length" is zero) ?

 

It's to ensure that a zero length packet is sent after the data has been sent, if the last packet completely filled the endpoint bank.

Thank you. And yes, that was the only reason I could think of. I did have a bit of a doubt though, as the packets send (by that routine) are actually records containing their own lengths as the first byte. It looked to be a bit double-up. Later on I realized that this "data ends with a non-full packet" -mechanism probably is aimed at the hardware, and the records (length-byte) gets handled on a "higher" level.

Do you maybe also have some insights in regard to my other observations: why, when the datasize is zero, a packet is send without any checking (if the fifo is ready), or why the source data is, if its larger than the target buffer, just clipped -- throwing possibly important data away. Or when an RXOUTI packet is detected while sending the data the function returns with an OK -- even though not all data is send.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Can anyone explain to me why the code is looping when "LastPacketFull" is true even though all bytes have been written ("Length" is zero) ?

Oh, sorry, missed the additional questions. Man, this is some crusty code - it's been so long that I don't even remember writing it, let alone exactly why I wrote things the way I did.

 

why, when the datasize is zero, a packet is send without any checking (if the fifo is ready)

 

If the control request has no data to send, it still needs to send a zero length packet to notify the host that the data phase has ended (and the status phase should begin). It just so happens that the endpoint bank is ready when the function enters as no data has been loaded into it yet, but looking at it more closely that's more of a guarantee on the specific AVR8 USB controller and may not hold true on the other architectures. Can you file a bug on Github for me to patch it please? You're right that I haven't been working on it in many months now (real life plus a lot of heavy complex development at work means I don't do much coding for fun anymore) but I do want to keep things maintained.

 

or why the source data is, if its larger than the target buffer, just clipped

 

That's actually how USB control requests are supposed to work. The host needs to indicate how much buffer space it has allocated, and you're straight up supposed to just fill it up until you run out of data or the buffer is full. This mechanism is used when fetching the device's configuration descriptor for example; you can start by issuing a GET DESCRIPTOR request with just enough buffer space for the header, then re-issue it with a large enough buffer for all the data (as indicated in the returned header). The host could check if the buffer was exceeded (or, as an edge case, met exactly) by seeing if the device returned exactly the buffer's size of data and re-issue or fail the request as appropriate.

 

Or when an RXOUTI packet is detected while sending the data the function returns with an OK

 

The host can prematurely abort the transfer by sending an OUT packet to begin the status phase. The host always maintains complete control over the bus, and can issue and abort control requests at will.

 

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

abcminiuser wrote:
Man, this is some crusty code - it's been so long that I don't even remember writing it, let alone exactly why I wrote things the way I did
The same feeling I get when looking at code I wrote a while ago. :-)

Quote:
Can you file a bug on Github for me to patch it please?
I would love to, but a few things speak against it: I'm not programming in C and nor do I have a github account. Also, I have no way/tools to test if what I write will work in edge cases, nor if it will work on controllers other than the one I'm having here (AT90USB162, minimus). In short, I feel unqualified and ill-equiped for the task.

In case you're wondering, I'm looking at a disassembled .hex file, and am using the C sourcecode (downloaded from github using a web browser) to, by way of the function names, get a gist of what the code is supposed to do.
Quote:
You're right that I haven't been working on it in many months now but I do want to keep things maintained.
Thanks. But I hope you've set yourself up to be innundated with lots of questions regarding what you wrote ... :-)
Quote:

or why the source data is, if its larger than the target buffer, just clipped

That's actually how USB control requests are supposed to work.


Ackk... after I read your reply I realized that I took the method too generic, as being applicable to the datachannel too. In the case of the control channel all data seems to be replicable. In the case of the data channel, not so much.
Quote:
The host can prematurely abort the transfer by sending an OUT packet to begin the status phase
Thats another thing: although the host lets the client know its datatransfer should be aborted, your code sends a packet regardless.

Question: Why did you do that (instead of clearing the current buffer), and what happens to that datapacket: does it actually get send to the host (which than has to discard it), or is it supposed to be discarded by the clients hardware ?

One other question: your code uses a variable to keep track of how many bytes have been written to the (current) buffer, even though you can retrieve that count from the buffer itself (UEBCLX). Can you remember the reason for that ?

And in regard to this internal buffer contents byte count I've got one additional question: in doc7707.pdf, chapter "20.12.2 - Control Read" ends with a warning: "the byte count is reset when a OUT zero length packet is received." This sounds like a bit of a "duh!" to me, which makes me feel like I've missed something: do they maybe mean that the byte count of the current IN buffer is reset (and doesn't the byte count than not get altered when a non-zero length OUT packet is received - but that does not make much sense ... ) ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

> I would love to, but a few things speak against it: I'm not programming in C and nor do I have a github account. Also, I have no way/tools to test if what I write will work in edge cases, nor if it will work on controllers other than the one I'm having here (AT90USB162, minimus). In short, I feel unqualified and ill-equiped for the task.

 

Oh, anyone can sign up for a GitHub account and post an issue/bug report, you don't even have to (and it's sadly uncommon to) post a solution. That's fine, I can open an issue for it.

 

> Thats another thing: although the host lets the client know its datatransfer should be aborted, your code sends a packet regardless.

> Question: Why did you do that (instead of clearing the current buffer), and what happens to that datapacket: does it actually get send to the host (which than has to discard it), or is it supposed to be discarded by the clients hardware ?
 

The abort condition(s) are checked before filling the endpoint buffer and attempting to send it to the host. Control endpoints are slightly different to regular endpoints -- the IN packet will be discarded if the host then sends a new SETUP or OUT packet. It may make its way to the host but ultimately they will tolerate this, as there's a race condition on the bus where the device re-arms the endpoint before the host sends the OUT or SETUP packet to abort the rest of the transfer.

 

> One other question: your code uses a variable to keep track of how many bytes have been written to the (current) buffer, even though you can retrieve that count from the buffer itself (UEBCLX). Can you remember the reason for that ?
 

It optimizes better, and shaves off a byte or two of flash. On the USB AVR8 devices the endpoint registers live in the upper addresses and require long 32-bit instructions to read and write, so I read and cache the value into a byte-sized variable (which will be a register when optimized) to save the cycles.

 

> And in regard to this internal buffer contents byte count I've got one additional question: in doc7707.pdf, chapter "20.12.2 - Control Read" ends with a warning: "the byte count is reset when a OUT zero length packet is received." This sounds like a bit of a "duh!" to me, which makes me feel like I've missed something: do they maybe mean that the byte count of the current IN buffer is reset (and doesn't the byte count than not get altered when a non-zero length OUT packet is received - but that does not make much sense ... ) ?

 

USB data endpoints are actually unidirectional, in that you would need both an OUT and IN endpoint of a given index to get bidirectional communications. The exception is the control endpoint, where the communication is bidirectional but follows the rigorous control request flow, where the host sends the request which dictates the direction of the following data and status stages. Despite this, there's still a single endpoint involved with a single data bank, so anything the host sends on the control endpoint wipes out and resets anything already stored in the endpoint.
 

That means each endpoint doesn't have a "IN" byte counter and buffer as well as an "OUT" byte counter and buffer, it just has the one counter and buffer and you have to configure the type, size and direction to determine how it behaves.

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

abcminiuser wrote:
Control endpoints are slightly different to regular endpoints -- the IN packet will be discarded if the host then sends a new SETUP or OUT packet
In other words: Even though the client can be busy filling-and-sending multiple(!) IN packets (outgoing for the client), its hardware will never actually send them when an OUT or SETUP (incoming for the client) is received.
Quote:
It may make its way to the host but ultimately they will tolerate this, as there's a race condition on the bus where the device re-arms the endpoint before the host sends the OUT or SETUP packet to abort the rest of the transfer.
:-) Forgot about that. And just outof curiosity, any idea if that rejection is done in the hosts hardware or software ?
Quote:
It optimizes better, and shaves off a byte or two of flash
I can't say anything about the first, but are you sure about the second ? Your code retrieves the current count before trying to fill up the (rest of) the buffer. Moving that command to another spot (as I have done) would not make a difference byte-count wise. And also, although using a variable could create an improvement you've been using a two-byte counter (where a single one suffices -- IIRC the USB buffer size is max 64 bytes) which makes mince-meat of that. :-)
Quote:
USB data endpoints are actually unidirectional,

....

so anything the host sends on the control endpoint wipes out and resets anything already stored in the endpoint.
Thats even more confusing. I can't imagine that an incoming packet (from the host) will muck around with the buffer the client is currently writing in. Just imagine (your) code filling up the IN buffer (to send it to the host) and an incoming packet changing its contents and byte counter ... Al kinds of "bad things(tm)" would happen :-(

As I do not see you check for an incoming packet before storing each byte into the IN buffer I must assume that the above does not happen.

So, what happens instead ? If both incoming and outgoing byte counters are the same, how come that your code, which blindly copies upto 8 bytes into that buffer, does not interfere with the data and byte count of the packet that has just been received ? Some hardware shennigans going on (which would explain the need for a seperate byte counter like you used it) ?