I have a buffer that I use for UART, which is declared this way:
union Eusart_Buff {
uint8_t b8[16];
uint16_t b9[16];
};
struct Eusart_Msg {
uint8_t msg_posn;
uint8_t msg_len;
union Eusart_Buff buff;
};
struct Eusart {
struct Eusart_Msg tx;
struct Eusart_Msg rx;
};
extern volatile struct Eusart eusart;
And here is the function that fills the buffer (which will be sent using interrupts):
void eusart_msg_transmit (uint8_t n, void *msg)
{
if (!n)
return;
/*
* The end of the previous transmission will reset
* eusart.tx.msg_len (i.e. ISR is off)
*/
while (eusart.tx.msg_len)
;
if (data_9b) {
memcpy((void *)eusart.tx.buff.b9, msg,
sizeof(eusart.tx.buff.b9[0]) * n);
} else {
memcpy((void *)eusart.tx.buff.b8, msg,
sizeof(eusart.tx.buff.b8[0]) * n);
}
eusart.tx.msg_len = n;
eusart.tx.msg_posn = 0;
reg_PIE1_TXIE_write(true);
}
At the moment of using memcpy()
, I know no one else is going to use the buffer (atomic), because the while
loop ensures that the last message has been sent, and therefore the interrupt is disabled.
Is it safe to cast away volatile
this way so that I am able to use memcpy()
or should I make a function maybe called memcpy_v()
like these to be safe?:
void *memcpy_vin(void *dest, const volatile void *src, size_t n)
{
const volatile char *src_c = (const volatile char *)src;
char *dest_c = (char *)dest;
for (size_t i = 0; i < n; i++)
dest_c[i] = src_c[i];
return dest;
}
volatile void *memcpy_vout(volatile void *dest, const void *src, size_t n)
{
const char *src_c = (const char *)src;
volatile char *dest_c = (volatile char *)dest;
for (size_t i = 0; i < n; i++)
dest_c[i] = src_c[i];
return dest;
}
volatile void *memcpy_v(volatile void *dest, const volatile void *src, size_t n)
{
const volatile char *src_c = (const volatile char *)src;
volatile char *dest_c = (volatile char *)dest;
for (size_t i = 0; i < n; i++)
dest_c[i] = src_c[i];
return dest;
}
Edit:
If I need those new functions,
given that I know no one is going to modify the array at the same time, would it make sense to use restrict
to (maybe) help the compiler optimize (if it can)?
Possibly this way (correct me if I'm wrong):
volatile void *memcpy_v(restrict volatile void *dest,
const restrict volatile void *src,
size_t n)
{
const restrict volatile char *src_c = src;
restrict volatile char *dest_c = dest;
for (size_t i = 0; i < n; i++)
dest_c[i] = src_c[i];
return dest;
}
Edit 2 (add context):
void eusart_end_transmission (void)
{
reg_PIE1_TXIE_write(false); /* TXIE is TX interrupt enable */
eusart.tx.msg_len = 0;
eusart.tx.msg_posn = 0;
}
void eusart_tx_send_next_c (void)
{
uint16_t tmp;
if (data_9b) {
tmp = eusart.tx.buff.b9[eusart.tx.msg_posn++];
reg_TXSTA_TX9D_write(tmp >> 8);
TXREG = tmp;
} else {
TXREG = eusart.tx.buff.b8[eusart.tx.msg_posn++];
}
}
void __interrupt() isr(void)
{
if (reg_PIR1_TXIF_read()) {
if (eusart.tx.msg_posn >= eusart.tx.msg_len)
eusart_end_transmission();
else
eusart_tx_send_next_c();
}
}
Although volatile
may not be is needed (I asked it in another question: volatile for variable that is only read in ISR?), this question still should be answered in the assumption that volatile
is needed so that future users that really need volatile
(for example me when I implement the RX buffer), can know what to do.
EDIT (Related) (Jul/19):
volatile vs memory barrier for interrupts
Basically says that volatile
is not needed, and therefore this issue disappears.
Is
memcpy((void *)dest, src, n)
with avolatile
array safe?
No. In the general case, memcpy()
is not specified to work correctly with volatile memory.
OP's case looks OK to cast away volatile
, yet posted code is insufficient to be certain.
If code wants to memcpy()
volatile
memory, write the helper function.
OP's code has restrict
in the wrong place. Suggest
volatile void *memcpy_v(volatile void *restrict dest,
const volatile void *restrict src, size_t n) {
const volatile unsigned char *src_c = src;
volatile unsigned char *dest_c = dest;
while (n > 0) {
n--;
dest_c[n] = src_c[n];
}
return dest;
}
A singular reason for writing your own memcpy_v()
is the a compiler can "understand"/analyze memcpy()
and emit code that is very different than expected - even optimize it out, if the compiler thinks the copy is not needed. Remind oneself that the compiler thinks memcpy()
manipulated memory is non-volatile.
Yet OP is using volatile struct Eusart eusart;
incorrectly. Access to eusart
needs protection that volatile
does not provide.
In OP's case, code can drop volatile
on the buffers and then use memcpy()
just fine.
A remaining issue is in the scant code of how OP is using eusart
. Using volatile
does not solve OP's problem there. OP's does assert "I write to it atomically,", yet without posted atomic
code, that is not certain.
Code like the below benefits with eusart.tx.msg_len
being volatile
, yet that is not sufficient. volatile
insures .tx.msg_len
is not cached and instead re-reads each time.
while (eusart.tx.msg_len)
;
Yet the read of .tx.msg_len
is not specified as atomic. When .tx.msg_len == 256
and a ISR fires, decrementing .tx.msg_len
, the read of the the LSbyte (0 from 256) and MSbyte (0 from 255), the non-ISR code may see .tx.msg_len
as 0, not 255 nor 256, thus ending the loop at the wrong time. The access of .tx.msg_len
needs to be specified as indivisible (atomic), else, once in a while code fails mysteriously.
while (eusart.tx.msg_len);
also suffers from being an end-less loop. Should the transmission stop for some reason other than empty, the while loop never exits.
Recommend instead to block interrupts while inspecting or changing eusart.tx.msg_len, eusart.tx.msg_posn
. Review your compilers support of atomic
or
size_t tx_msg_len(void) {
// pseudo-code
interrupt_enable_state = get_state();
disable_interrupts();
size_t len = eusart.tx.msg_len;
restore_state(interrupt_enable_state);
return len;
}
General communication code ideas:
While non-ISR code reads or writes eusart
, make sure the ISR cannot ever change eusart
.
Do not block ISR
for long in step #1.
Do not assume underlying ISR()
will chain input/output successfully without a hiccup. Top level code should be prepared to re-start output if it gets stalled.
The Standard lacks any means by which programmers can demand that operations that access a region of storage by means of an ordinary pointer are completed before a particular volatile
pointer access is performed, and also lacks any means of ensuring that operations which access a region of storage by means of an ordinary pointer are not carried out until after some particular volatile
pointer access is performed. Since the semantics of volatile
operations are Implementation-Defined, the authors of the Standard may have expected that compiler writers would recognize when their customers might need such semantics, and specify their behavior in a fashion consistent with those needs. Unfortunately, that hasn't happened.
Achieving the semantics you require will either making use of a "popular extension", such as the -fms-volatile
mode of clang, a compiler-specific intrinsic, or else replacing memcpy
with something that's so horribly inefficient as to swamp any supposed advantage compilers could gain by not supporting such semantics.
The C17 standard says this in section 6.7.3 "Type qualifiers":
If an attempt is made to refer to an object defined with a volatile-qualified type through use of an lvalue with non-volatile-qualified type, the behavior is undefined.135)
135)This applies to those objects that behave as if they were defined with qualified types, even if they are never actually defined as objects in the program (such as an object at a memory-mapped input/output address).
So no, that's not safe.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With