Skip to content

Commit c3c8ac3

Browse files
committed
Updated per @hathach requested changes
* Changed to use compiler intrinsics * Significantly reduced comments near memory barrier usage, as per request * Updating `attachInterrupt()` loops to more closely match requested style
1 parent 48fda18 commit c3c8ac3

File tree

1 file changed

+52
-85
lines changed

1 file changed

+52
-85
lines changed

cores/nRF5/WInterrupts.c

Lines changed: 52 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -107,57 +107,52 @@ int attachInterrupt(uint32_t pin, voidFuncPtr callback, uint32_t mode)
107107
((polarity << GPIOTE_CONFIG_POLARITY_Pos) & GPIOTE_CONFIG_POLARITY_Msk) |
108108
((GPIOTE_CONFIG_MODE_Event << GPIOTE_CONFIG_MODE_Pos ) & GPIOTE_CONFIG_MODE_Msk ) ;
109109

110-
// To avoid unexpected consequences, first loop through the channelMap
111-
// to preferably re-use any channel that was already in use (even if
112-
// earlier channel is no longer in use).
113-
for (int ch = 0; ch < NUMBER_OF_GPIO_TE; ch++) {
114-
// skip if the channel was not already assigned to this pin
115-
if ((uint32_t)channelMap[ch] != pin) continue;
116-
117-
// The pin is already allocated in the channelMap
118-
// update the polarity (when events fire) and callbacks
119-
// However, do NOT clear any GPIOTE events
120-
uint32_t tmp = NRF_GPIOTE->CONFIG[ch];
121-
channelMap[ch] = pin;
122-
callbacksInt[ch] = callback;
123-
callbackDeferred[ch] = deferred;
124-
tmp &= oldRegMask;
125-
tmp |= newRegBits;
126-
NRF_GPIOTE->CONFIG[ch] = tmp;
127-
asm volatile ("" : : : "memory");
128-
__asm__ __volatile__ ("nop\n\tnop\n\tnop\n\tnop\n");
129-
NRF_GPIOTE->INTENSET = (1 << ch); // old code did this ... no harm in ensuring this is set
130-
return (1 << ch);
110+
int ch = -1;
111+
int newChannel = 0;
112+
113+
// Find channel where pin is already assigned, if any
114+
for (int i = 0; i < NUMBER_OF_GPIO_TE; i++) {
115+
if ((uint32_t)channelMap[i] != pin) continue;
116+
ch = i;
117+
break;
118+
}
119+
// else, find one not already mapped and also not in use by others
120+
if (ch == -1) {
121+
for (int i = 0; i < NUMBER_OF_GPIO_TE; i++) {
122+
if (channelMap[i] != -1) continue;
123+
if (nrf_gpiote_te_is_enabled(NRF_GPIOTE, i)) continue;
124+
125+
ch = i;
126+
newChannel = 1;
127+
break;
128+
}
129+
}
130+
// if no channel found, exit
131+
if (ch == -1) {
132+
return 0; // no channel available
131133
}
132134

133-
// When the pin isn't already configured for interrupts, then attempt to
134-
// find an unused GPIOTE channel. This code is identical to the above,
135-
// except for also validating that the MODE bits show the channel is disabled.
136-
for (int ch=0; ch < NUMBER_OF_GPIO_TE; ch++) {
137-
// skip if already used in AttachInterrupt for another pin
138-
if (channelMap[ch] != -1) continue;
139-
// skip if channel is not disabled (e.g., in use by some other component or library)
140-
if (nrf_gpiote_te_is_enabled(NRF_GPIOTE, ch)) continue;
141-
142-
// clear any old events on this GPIOTE channel
143-
uint32_t tmp = NRF_GPIOTE->CONFIG[ch];
144-
channelMap[ch] = pin;
145-
callbacksInt[ch] = callback;
146-
callbackDeferred[ch] = deferred;
147-
tmp &= oldRegMask;
148-
tmp |= newRegBits;
149-
// TODO: make check/set for new channel an atomic operation
150-
NRF_GPIOTE->CONFIG[ch] = tmp;
151-
asm volatile ("" : : : "memory");
152-
__asm__ __volatile__ ("nop\n\tnop\n\tnop\n\tnop\n");
135+
uint32_t tmp = NRF_GPIOTE->CONFIG[ch];
136+
tmp &= oldRegMask;
137+
tmp |= newRegBits; // for existing channel, effectively updates only the polarity
138+
channelMap[ch] = pin; // harmless for existing channel
139+
callbacksInt[ch] = callback; // caller might be updating this for existing channel
140+
callbackDeferred[ch] = deferred; // caller might be updating this for existing channel
141+
NRF_GPIOTE->CONFIG[ch] = tmp;
142+
143+
// For a new channel, additionally ensure no old events existed, and enable the interrupt
144+
if (newChannel) {
145+
// Why the memory barrier and NOPs?
146+
// See page 4 at https://web.archive.org/web/20190823222657/http://infocenter.arm.com/help/topic/com.arm.doc.dai0321a/DAI0321A_programming_guide_memory_barriers_for_m_profile.pdf
147+
// and recall the AHB runs at 16MHz, so may take 4 cycles before peripheral register is actually updated.
148+
__DSB(); __NOP();__NOP();__NOP();__NOP();
153149
NRF_GPIOTE->EVENTS_IN[ch] = 0;
154-
asm volatile ("" : : : "memory");
155-
__asm__ __volatile__ ("nop\n\tnop\n\tnop\n\tnop\n");
150+
__DSB(); __NOP();__NOP();__NOP();__NOP();
156151
NRF_GPIOTE->INTENSET = (1 << ch);
157-
return (1 << ch);
158152
}
159-
// Else, pin was neither already setup, nor could a GPIOTE be allocated
160-
return 0;
153+
154+
// Finally, indicate to caller the allocated / updated channel
155+
return (1 << ch);
161156
}
162157

163158
/*
@@ -173,28 +168,19 @@ void detachInterrupt(uint32_t pin)
173168

174169
for (int ch = 0; ch < NUMBER_OF_GPIO_TE; ch++) {
175170
if ((uint32_t)channelMap[ch] == pin) {
176-
// Unfortunately, simply marking a variable as volatile is not enough to
177-
// prevent GCC from reordering or combining memory accesses, without also
178-
// having an explicit sequence point.
179-
// See https://gcc.gnu.org/onlinedocs/gcc/Volatiles.html
180-
//
181-
// Here, ensure a sequence point by adding a memory barrier
182-
// followed with four nops after having disabled the interrupt,
183-
// to ensure the peripheral registers have been updated.
171+
// Why the memory barrier and NOPs?
172+
// See page 4 at https://web.archive.org/web/20190823222657/http://infocenter.arm.com/help/topic/com.arm.doc.dai0321a/DAI0321A_programming_guide_memory_barriers_for_m_profile.pdf
173+
// and recall the AHB runs at 16MHz, so may take 4 cycles before peripheral register is actually updated.
184174
NRF_GPIOTE->INTENCLR = (1 << ch);
185-
asm volatile ("" : : : "memory");
186-
__asm__ __volatile__ ("nop\n\tnop\n\tnop\n\tnop\n");
175+
__DSB(); __NOP();__NOP();__NOP();__NOP();
176+
NRF_GPIOTE->CONFIG[ch] = 0;
177+
__DSB(); __NOP();__NOP();__NOP();__NOP();
178+
NRF_GPIOTE->EVENTS_IN[ch] = 0; // clear any final events
187179

188180
// now cleanup the rest of the use of the channel
189181
channelMap[ch] = -1;
190182
callbacksInt[ch] = NULL;
191183
callbackDeferred[ch] = false;
192-
NRF_GPIOTE->EVENTS_IN[ch] = 0; // clear the event
193-
// Finally, clear the CONFIG register only after ensure
194-
// all the other state has been written to the peripheral registers
195-
asm volatile ("" : : : "memory");
196-
__asm__ __volatile__ ("nop\n\tnop\n\tnop\n\tnop\n");
197-
NRF_GPIOTE->CONFIG[ch] = 0;
198184
break;
199185
}
200186
}
@@ -232,29 +218,10 @@ void GPIOTE_IRQHandler()
232218
NRF_GPIOTE->EVENTS_IN[ch] = 0;
233219
}
234220
#if __CORTEX_M == 0x04
235-
// To quote from nRF52840 product specification:
236-
// --------------------------------------------------------------------------
237-
// Note: To avoid an interrupt reoccurring before a new event has come,
238-
// the program should perform a read from one of the peripheral
239-
// registers. For example, the event register that has been cleared,
240-
// or the INTENCLR register that has been used to disable the
241-
// interrupt. This will cause a one to three cycle delay and ensure
242-
// the interrupt is cleared before exiting the interrupt handler.
243-
// Care should be taken to ensure the compiler does not remove the read
244-
// operation as an optimization. If the program can guarantee a four-cycle
245-
// delay after event being cleared or interrupt disabled in any other way,
246-
// then a read of a register is not required.
247-
// --------------------------------------------------------------------------
248-
//
249-
// Unfortunately, simply marking a variable as volatile is not enough to
250-
// prevent GCC from reordering or combining memory accesses, without also
251-
// having an explicit sequence point.
252-
// See https://gcc.gnu.org/onlinedocs/gcc/Volatiles.html
253-
//
254-
// Here, ensure a sequence point by adding a memory barrier
255-
// followed with four nops before exiting the ISR
256-
asm volatile ("" : : : "memory");
257-
__asm__ __volatile__ ("nop\n\tnop\n\tnop\n\tnop\n");
221+
// See note at nRF52840_PS_v1.1.pdf section 6.1.8 ("interrupt clearing")
222+
// See also https://gcc.gnu.org/onlinedocs/gcc/Volatiles.html for why
223+
// using memory barrier instead of read of an unrelated volatile
224+
__DSB(); __NOP();__NOP();__NOP();__NOP();
258225
#endif
259226

260227
#if CFG_SYSVIEW

0 commit comments

Comments
 (0)