EmbeddedRelated.com
Forums

How to write a simple driver in bare metal systems: volatile, memory barrier, critical sections and so on

Started by pozz October 22, 2021
On 2021-10-24 23:27, Dimiter_Popoff wrote:
> On 10/24/2021 22:54, Don Y wrote: >> On 10/24/2021 4:14 AM, Dimiter_Popoff wrote: >>>> Disable interrupts while accessing the fifo. you really have to. >>>> alternatively you'll often get away not using a fifo at all, >>>> unless you're blocking for a long while in some part of the code. >>> >>> Why would you do that. The fifo write pointer is only modified by >>> the interrupt handler, the read pointer is only modified by the >>> interrupted code. Has been done so for times immemorial. >> >> The OPs code doesn't differentiate between FIFO full and empty. > > So he should fix that first, there is no sane reason why not. > Few things are simpler to do than that.
[snip]
> Whatever handshakes he makes there is no problem knowing whether > the fifo is full - just check if the position the write pointer > will have after putting the next byte matches the read pointer > at the moment.� Like I said before, few things are simpler than > that, can't imagine someone working as a programmer being > stuck at *that*.
That simple check would require keeping a maximum of only N-1 entries in the N-position FIFO buffer, and the OP explicitly said they did not want to allocate an unused place in the buffer (which I think is unreasonable of the OP, but that is only IMO). The simple explanation for the N-1 limit is that the difference between two wrap-around pointers into an N-place buffer has at most N different values, while there are N+1 possible filling states of the buffer, from empty (zero items) to full (N items).
On 10/25/2021 12:56 AM, Niklas Holsti wrote:
> On 2021-10-25 0:08, Don Y wrote: > >> There are (and have been) many "safer" languages. Many that are more >> descriptive (for certain classes of problem). But, C has survived to >> handle all-of-the-above... perhaps in a suboptimal way but at least >> a manner that can get to the desired solution. >> >> Look at how few applications SNOBOL handles. Write an OS in COBOL? Ada? > > I don't know about COBOL, but typically the real-time kernels ("run-time > systems") associated with Ada compilers for bare-board embedded systems are > written in Ada, with a minor amount of assembly language for the most > HW-related bits like HW context saving and restoring. I'm pretty sure that > C-language OS kernels also use assembly for those things.
Of course you *can* do these things. The question is how often they are ACTUALLY done with these other languages. "Suitability for a particular task" isn't often the criteria that is used to make a selection -- for better or worse. There are countless other factors that affect an implementation, depending on the environment in which it is undertaken (e.g., designs from academia are considerably different than hobbyist designs which are different from commercial designs which are...) This is true of other disciplines, as well. How often do you think a hardware design follows a course heavily influenced by the "prejudices"/"preferences" of the folks responsible for the design vs. the "best" approach to it? Step back yet another level of abstraction and see that even the tools chosen to perform those tasks are often not "optimally chosen". If you are the sole entity involved in a decision making process, then you've (typically) got /carte blanche/. But, in most cases, there are other voices -- seats at the table -- that shape the final decisions. It pays to lift one's head and see which way the wind is blowing, *today*... [By the same token, expecting the past to mirror the present is equally naive. People forget that tools and processes have evolved (in the 40+ years that I've been designing embedded products). And, that the isssues folks now face often weren't issues when tools were "stupider" (I've probably got $60K of obsolete compilers to prove this -- anyone written any C on an 1802 recently? Or, a 2A03? 65816? Z180? 6809?) Don't even *think* about finding an Ada compiler for them -- in the past!]
On 10/25/2021 1:09 AM, Niklas Holsti wrote:
> On 2021-10-24 23:27, Dimiter_Popoff wrote: >> On 10/24/2021 22:54, Don Y wrote: >>> On 10/24/2021 4:14 AM, Dimiter_Popoff wrote: >>>>> Disable interrupts while accessing the fifo. you really have to. >>>>> alternatively you'll often get away not using a fifo at all, >>>>> unless you're blocking for a long while in some part of the code. >>>> >>>> Why would you do that. The fifo write pointer is only modified by >>>> the interrupt handler, the read pointer is only modified by the >>>> interrupted code. Has been done so for times immemorial. >>> >>> The OPs code doesn't differentiate between FIFO full and empty. >> >> So he should fix that first, there is no sane reason why not. >> Few things are simpler to do than that. > > > [snip] > > >> Whatever handshakes he makes there is no problem knowing whether >> the fifo is full - just check if the position the write pointer >> will have after putting the next byte matches the read pointer >> at the moment. Like I said before, few things are simpler than >> that, can't imagine someone working as a programmer being >> stuck at *that*. > > That simple check would require keeping a maximum of only N-1 entries in the > N-position FIFO buffer, and the OP explicitly said they did not want to > allocate an unused place in the buffer (which I think is unreasonable of the > OP, but that is only IMO). > > The simple explanation for the N-1 limit is that the difference between two > wrap-around pointers into an N-place buffer has at most N different values, > while there are N+1 possible filling states of the buffer, from empty (zero > items) to full (N items).
But, again, that just deals with the "full check". The easiest way to do this is just to check ".in" *after* advancement and inhibit the store if it coincides with the ".out" value. Checking for a "high water mark" to enable flow control requires more computation (albeit simple) as you have to accommodate the delays in that notification reaching the remote sender (lest he continue sending and overrun your buffer). And, later noting when you've consumed enough of the FIFO's contents to reach a "low water mark" and reenable the remote's transmissions. [And, if you ever have to deal with more "established" protocols that require the sequencing of specific control signals DURING a transfer, the ISR quickly becomes very complex!] When you start "fleshing out" an ISR in this way, you see the code quickly becomes more involved than just pushing bytes into a buffer. (and, this should give you pause to rethink what you are doing *in* the ISR and what can best be handled out of that "precious" environment)
On 2021-10-25 11:19, Don Y wrote:
> On 10/25/2021 12:56 AM, Niklas Holsti wrote: >> On 2021-10-25 0:08, Don Y wrote: >> >>> There are (and have been) many "safer" languages.  Many that are more >>> descriptive (for certain classes of problem).  But, C has survived to >>> handle all-of-the-above... perhaps in a suboptimal way but at least >>> a manner that can get to the desired solution. >>> >>> Look at how few applications SNOBOL handles.  Write an OS in COBOL? >>> Ada? >> >> I don't know about COBOL, but typically the real-time kernels >> ("run-time systems") associated with Ada compilers for bare-board >> embedded systems are written in Ada, with a minor amount of assembly >> language for the most HW-related bits like HW context saving and >> restoring. I'm pretty sure that C-language OS kernels also use >> assembly for those things. > > Of course you *can* do these things.
Then I misunderstood your (rhetorical?) question.
> The question is how often > they are ACTUALLY done with these other languages.
I don't find that question very interesting. It is a typical chicken-and-egg, first-to-market conundrum. There is an enormous amount of status-quo-favouring friction in awareness, education, tool availability, and legacy code.
> [By the same token, expecting the past to mirror the present is equally > naive.  People forget that tools and processes have evolved (in the 40+ > years that I've been designing embedded products).  And, that the isssues > folks now face often weren't issues when tools were "stupider" (I've > probably got $60K of obsolete compilers to prove this -- anyone written > any C on an 1802 recently?  Or, a 2A03?  65816?  Z180?  6809?)  Don't > even *think* about finding an Ada compiler for them -- in the past!]
Well, the Janus/Ada compiler was available for Z80 in its day. There are also Ada compilers that use C as an intermediate language, with applications for example on TI MSP430's, but those were probably not available in the past ages you refer to.
On 2021-10-25 11:28, Don Y wrote:
> On 10/25/2021 1:09 AM, Niklas Holsti wrote: >> On 2021-10-24 23:27, Dimiter_Popoff wrote: >>> On 10/24/2021 22:54, Don Y wrote: >>>> On 10/24/2021 4:14 AM, Dimiter_Popoff wrote: >>>>>> Disable interrupts while accessing the fifo. you really have to. >>>>>> alternatively you'll often get away not using a fifo at all, >>>>>> unless you're blocking for a long while in some part of the code. >>>>> >>>>> Why would you do that. The fifo write pointer is only modified by >>>>> the interrupt handler, the read pointer is only modified by the >>>>> interrupted code. Has been done so for times immemorial. >>>> >>>> The OPs code doesn't differentiate between FIFO full and empty. >>> >>> So he should fix that first, there is no sane reason why not. >>> Few things are simpler to do than that. >> >> >> ��� [snip] >> >> >>> Whatever handshakes he makes there is no problem knowing whether >>> the fifo is full - just check if the position the write pointer >>> will have after putting the next byte matches the read pointer >>> at the moment.� Like I said before, few things are simpler than >>> that, can't imagine someone working as a programmer being >>> stuck at *that*. >> >> That simple check would require keeping a maximum of only N-1 entries >> in the N-position FIFO buffer, and the OP explicitly said they did not >> want to allocate an unused place in the buffer (which I think is >> unreasonable of the OP, but that is only IMO). >> >> The simple explanation for the N-1 limit is that the difference >> between two wrap-around pointers into an N-place buffer has at most N >> different values, while there are N+1 possible filling states of the >> buffer, from empty (zero items) to full (N items). > > But, again, that just deals with the "full check".� The easiest way to do > this is just to check ".in" *after* advancement and inhibit the store if > it coincides with the ".out" value. > > Checking for a "high water mark" to enable flow control requires more > computation (albeit simple) as you have to accommodate the delays in > that notification reaching the remote sender (lest he continue > sending and overrun your buffer). > > And, later noting when you've consumed enough of the FIFO's contents > to reach a "low water mark" and reenable the remote's transmissions. > > [And, if you ever have to deal with more "established" protocols > that require the sequencing of specific control signals DURING > a transfer, the ISR quickly becomes very complex!]
Of course. Perhaps you (Don) did not see that I was agreeing with your position and objecting to the "it is very simple" stance of Dimiter (considering the OP's expressed constraints). Personally I would use critical sections to avoid relying on delicate reasoning about interleaved executions. And to allow for easy future complexification of the concurrent activities. The overhead of interrupt disabling and enabling is seldom significant when that can be done directly without kernel calls.
On 10/25/2021 2:06 AM, Niklas Holsti wrote:
> On 2021-10-25 11:28, Don Y wrote: >> On 10/25/2021 1:09 AM, Niklas Holsti wrote: >>> On 2021-10-24 23:27, Dimiter_Popoff wrote: >>>> On 10/24/2021 22:54, Don Y wrote: >>>>> On 10/24/2021 4:14 AM, Dimiter_Popoff wrote: >>>>>>> Disable interrupts while accessing the fifo. you really have to. >>>>>>> alternatively you'll often get away not using a fifo at all, >>>>>>> unless you're blocking for a long while in some part of the code. >>>>>> >>>>>> Why would you do that. The fifo write pointer is only modified by >>>>>> the interrupt handler, the read pointer is only modified by the >>>>>> interrupted code. Has been done so for times immemorial. >>>>> >>>>> The OPs code doesn't differentiate between FIFO full and empty. >>>> >>>> So he should fix that first, there is no sane reason why not. >>>> Few things are simpler to do than that. >>> >>> >>> [snip] >>> >>> >>>> Whatever handshakes he makes there is no problem knowing whether >>>> the fifo is full - just check if the position the write pointer >>>> will have after putting the next byte matches the read pointer >>>> at the moment. Like I said before, few things are simpler than >>>> that, can't imagine someone working as a programmer being >>>> stuck at *that*. >>> >>> That simple check would require keeping a maximum of only N-1 entries in the >>> N-position FIFO buffer, and the OP explicitly said they did not want to >>> allocate an unused place in the buffer (which I think is unreasonable of the >>> OP, but that is only IMO). >>> >>> The simple explanation for the N-1 limit is that the difference between two >>> wrap-around pointers into an N-place buffer has at most N different values, >>> while there are N+1 possible filling states of the buffer, from empty (zero >>> items) to full (N items). >> >> But, again, that just deals with the "full check". The easiest way to do >> this is just to check ".in" *after* advancement and inhibit the store if >> it coincides with the ".out" value. >> >> Checking for a "high water mark" to enable flow control requires more >> computation (albeit simple) as you have to accommodate the delays in >> that notification reaching the remote sender (lest he continue >> sending and overrun your buffer). >> >> And, later noting when you've consumed enough of the FIFO's contents >> to reach a "low water mark" and reenable the remote's transmissions. >> >> [And, if you ever have to deal with more "established" protocols >> that require the sequencing of specific control signals DURING >> a transfer, the ISR quickly becomes very complex!] > > Of course. Perhaps you (Don) did not see that I was agreeing with your position > and objecting to the "it is very simple" stance of Dimiter (considering the > OP's expressed constraints).
Yes, but I was afraid the emphasis would shift away from the "more involved" case (by trivializing the "full" case).
> Personally I would use critical sections to avoid relying on delicate reasoning > about interleaved executions. And to allow for easy future complexification of > the concurrent activities. The overhead of interrupt disabling and enabling is > seldom significant when that can be done directly without kernel calls.
We (developers, in general) tend to forget how often we cobble together solutions from past implementations. And, as those past implementations tend to be lax when it comes to enumerating the assumptions under which they were created, we end up propagating a bunch of dubious qualifiers that ultimately affect the code's performance and "correctness". Someone (including ourselves) trying to pilfer code from THIS project might incorrectly expect the ISR to protect against buffer wrap. Or, implement flow control. Or, be designed for a higher data rate than it actually sees (saw!) -- will the buffer size -- and task() timing -- be adequate to handle burst transmissions at 115Kbaud? If not, where is the upper bound? What if the CPU clock is changed? Or, the processor load? ... "Steal" several bits of code -- possibly from different projects -- and you've got an assortment of such hidden assumptions, all willing to eat your lunch! While you remain convinced that none of those things can happen! My first UART driver had to manage about a dozen control signals as the standard had a different intent and interpretation in the mid 70's, early 80's (anyone remember TWX? TELEX? DB25s?). Porting it forward ended up with a bunch of "issues" that no longer applied. (e.g., RTS/CTS weren't originally used as flow control/handshaking signals as they are commonly used, now). *Assuming* a letter revision of the standard was benign wrt the timing of signal transitions was folly. You only see these things when you lay out all of your assumptions in the codebase. And, hope the next guy actually READS what you took the time to WRITE! [When I wrote my 9 track tape driver, I had ~200 lines of commentary just explaining the role of the interface wrt the formatter, transports, etc. E.g., when you can read reverse, seek forward, rewind, etc. with multiple transports hung off that same interface. Otherwise, an observant developer would falsely conclude that the driver was riddled with bugs -- as it *facilitated* a multiplicity of concurrent operations]
On 10/25/2021 1:52 AM, Niklas Holsti wrote:
> On 2021-10-25 11:19, Don Y wrote: >> On 10/25/2021 12:56 AM, Niklas Holsti wrote: >>> On 2021-10-25 0:08, Don Y wrote: >>> >>>> There are (and have been) many "safer" languages. Many that are more >>>> descriptive (for certain classes of problem). But, C has survived to >>>> handle all-of-the-above... perhaps in a suboptimal way but at least >>>> a manner that can get to the desired solution. >>>> >>>> Look at how few applications SNOBOL handles. Write an OS in COBOL? Ada? >>> >>> I don't know about COBOL, but typically the real-time kernels ("run-time >>> systems") associated with Ada compilers for bare-board embedded systems are >>> written in Ada, with a minor amount of assembly language for the most >>> HW-related bits like HW context saving and restoring. I'm pretty sure that >>> C-language OS kernels also use assembly for those things. >> >> Of course you *can* do these things. > > Then I misunderstood your (rhetorical?) question. > >> The question is how often >> they are ACTUALLY done with these other languages. > > I don't find that question very interesting.
Why not? If a tool isn't used for a purpose for which it *should* be "ideal", you have to start wondering "why not?" Was it NOT suited to the task? Was it too costly (money and/or experience)? How do we not repeat that problem, going forward? I.e., is it better to EVOLVE a language to acquire the characteristics of the "better" one -- rather than trying to encourage people to "jump ship"?
> It is a typical chicken-and-egg, first-to-market conundrum. There is an > enormous amount of status-quo-favouring friction in awareness, education, tool > availability, and legacy code.
Of course! And, there is also the pressure of the market. Do *you* want to be The Guy who tries something new and sinks a product's development or market release? If your approach proves to be a big hit, will you benefit as much as you'd LOSE if it was a flop?
>> [By the same token, expecting the past to mirror the present is equally >> naive. People forget that tools and processes have evolved (in the 40+ >> years that I've been designing embedded products). And, that the isssues >> folks now face often weren't issues when tools were "stupider" (I've >> probably got $60K of obsolete compilers to prove this -- anyone written >> any C on an 1802 recently? Or, a 2A03? 65816? Z180? 6809?) Don't >> even *think* about finding an Ada compiler for them -- in the past!] > > Well, the Janus/Ada compiler was available for Z80 in its day. There are also > Ada compilers that use C as an intermediate language, with applications for > example on TI MSP430's, but those were probably not available in the past ages > you refer to.
I recall JRT Pascal and PL/M as the "high level" languages, back then. C compilers were notoriously bad. You could literally predict the code that would be generated for any statement. The whole idea of "peephole optimizers" looking for: STORE A LOAD A sequences to elide is testament to how little global knowledge they had of the code they were processing. Performance? A skilled ASM coder could beat the generated code (in time AND space) without breaking into a sweat. And, you bought a compiler/assembler/linker/debugger for *each* processor -- not just a simple command line switch to alter the code generation, etc. Vendors might have a common codebase for the tools but built each variant conditionally. The limits of the language were largely influenced by the targeted hardware -- "helper routines" to support longs, floats, etc. ("Oh, did you want that support to be *reentrant*? We assumed there would be a single floating point accumulator used throughout the code, not one per thread!") Different sizes of addresses (e.g., for the Z180, you could have 16b "logical" addresses and 24b physical addresses -- mapped into that logical space by the compiler's runtime support and linkage editor.) Portable code? Maybe -- with quite a bit of work! Fast/small? Meh...
On 25/10/2021 10:52, Niklas Holsti wrote:

> > Well, the Janus/Ada compiler was available for Z80 in its day. There are > also Ada compilers that use C as an intermediate language, with > applications for example on TI MSP430's, but those were probably not > available in the past ages you refer to.
Presumably there is gcc-based Ada for the msp430 (as there is for the 8-bit AVR)? There might not be a full library available, or possibly some missing features in the language.
On 2021-10-25 14:49, David Brown wrote:
> On 25/10/2021 10:52, Niklas Holsti wrote: > >> >> Well, the Janus/Ada compiler was available for Z80 in its day. There are >> also Ada compilers that use C as an intermediate language, with >> applications for example on TI MSP430's, but those were probably not >> available in the past ages you refer to. > > Presumably there is gcc-based Ada for the msp430 (as there is for the > 8-bit AVR)?
Indeed there seems to be one, or at least work towards one: https://sourceforge.net/p/msp430ada/wiki/Home/.
> There might not be a full library available, or possibly > some missing features in the language.
Certainly. I think that Janus/Ada for the Z80 was limited to the original Ada (Ada 83), and may well have also had some significant missing features. But I believe it was self-hosted on CP/M, quite a feat.
On 10/25/2021 11:09, Niklas Holsti wrote:
> On 2021-10-24 23:27, Dimiter_Popoff wrote: >> On 10/24/2021 22:54, Don Y wrote: >>> On 10/24/2021 4:14 AM, Dimiter_Popoff wrote: >>>>> Disable interrupts while accessing the fifo. you really have to. >>>>> alternatively you'll often get away not using a fifo at all, >>>>> unless you're blocking for a long while in some part of the code. >>>> >>>> Why would you do that. The fifo write pointer is only modified by >>>> the interrupt handler, the read pointer is only modified by the >>>> interrupted code. Has been done so for times immemorial. >>> >>> The OPs code doesn't differentiate between FIFO full and empty. >> >> So he should fix that first, there is no sane reason why not. >> Few things are simpler to do than that. > > > �� [snip] > > >> Whatever handshakes he makes there is no problem knowing whether >> the fifo is full - just check if the position the write pointer >> will have after putting the next byte matches the read pointer >> at the moment.� Like I said before, few things are simpler than >> that, can't imagine someone working as a programmer being >> stuck at *that*. > > That simple check would require keeping a maximum of only N-1 entries in > the N-position FIFO buffer, and the OP explicitly said they did not want > to allocate an unused place in the buffer (which I think is unreasonable > of the OP, but that is only IMO).
Well it might be reasonable if the fifo has a size of two, you know :-).