Hi zusammen
Kurz und einfach gesagt: Ich habe hier ein ganz seltsames Verhalten beim
Debuggen eines SPI Treibers aud FreeRTOS basierend.
Wichtigster Code-Ausschnitt (vollständige Datei im Anhang):
```c
static volatile SemaphoreHandle_t spiMutex = NULL;
static volatile StaticSemaphore_t spiMutexStatic;
static inline error_t spi_mutexTake()
{
if (spiMutex == NULL) // <- Break-Point 1
spiMutex =
xSemaphoreCreateMutexStatic((StaticSemaphore_t*)&spiMutexStatic);
if (xSemaphoreTake(spiMutex, SPI_CONFIG_PORT_DELAY) != pdTRUE) // <-
Break-Point 2
return SPI_ERROR_MUTEX_TAKE_FAILED;
else
return SPI_ERROR_SUCCESS;
}
static inline error_t spi_mutexGive()
{
if (xSemaphoreGive(spiMutex) != pdTRUE)
return SPI_ERROR_MUTEX_GIVE_FAILED;
else
return SPI_ERROR_SUCCESS;
}
error_t spi_run(/* params */)
{
ON_ERROR_RETURN(spi_mutexTake());
spi_doStuff(); // ...
ON_ERROR_RETURN(spi_mutexGive());
}
```
Ich habe nun 2 tasks laufen. Task 1 greift als erstes auf den SPI
Treiber zu. Er sendet und empfängt wenige bytes indem zwei mal
nacheinander `spi_run()` aufgerufen wird. Hier läuft alles einwandfrei,
beim ersten aufruf von `spi_mutexTake()` wird das statische Semaphore
initialisiert und `spiMutex` enthält ab Break-Point 2 die Adresse
`0x02f0`.
Nun wird zu Task 2 gewechselt, welcher ebenfalls `spi_run()` aufruft.
Doch hier geschiet was ganz interessantes. `spi_mutexTake()` wird
aufgerufen und ist zu Break-Point 1 noch adresse `0x02f0` (die Bedingung
`if (spiMutex == NULL)` müsste also false sein), doch ab Break-Point 2
zeigt dieser ins völlige Nirwana von `0x2001` ausserhalb des Speichers
und nach dem Aufruf von `spi_mutexGive()` zeigt spiMutex sogar auf
`NULL` (hier wird aber glücklicherweise auch
`SPI_ERROR_MUTEX_GIVE_FAILED` zurückgegeben).
Ich habe leider wenig Ahnung, was der Compiler alles hier versucht zu
(über-)optimieren, doch beim besten Willen kann ich mir nicht erklären,
was da geschiet.
Ausserhalb von `spi_MutexTake()` findet nie eine Zuweisung für spiMutex
statt. Darum habe ich auch keinen Schimmer, wieso sich dessen Wert auf
einmal ändert.
Falls jemand einen Tipp hat, gerne her damit...
Hab keine Ahnung von FreeRTOS, aber wo ist die Mutex, die den Zugriff
auf spiMutex schützt? Die volatiles bringen nichts.
Sieh lieber zu, dass spiMutex initialisiert wird, bevor du die Tasks
startest.
foobar schrieb:> Hab keine Ahnung von FreeRTOS, aber wo ist die Mutex, die den Zugriff> auf spiMutex schützt? Die volatiles bringen nichts.
Die volatiles sind nur dazu da damit ich das zeug anständig debuggen
kann.
Im Anhang siehst du das ursprünglich `taskENTER_CRITICAL()` dabei ist im
Makro `_SPI_MUTEX_TAKE(m,s,e)`. Das wäre der Schutz vor dem Zugriff.
foobar schrieb:> Sieh lieber zu, dass spiMutex initialisiert wird, bevor du die Tasks> startest.
Hat bisher eigentlich immer ganz gut so geklappt, aber in der aktuellen
Situation tatsächlich gar nicht verkehrt. Probier ich später gleich aus,
danke!
> taskENTER_CRITICAL ... Das wäre der Schutz vor dem Zugriff.
Hmmm... ich denke, das reicht nicht. Laut Doku sperrt das nur
Interrupts und dadurch das preemptive Scheduling, nicht das explizite.
Bei SMP reicht das eh nicht, da braucht man Spinlocks (keine Ahnung, ob
die dann in taskXX_Critical drin sind, Doku äußert sich nicht dazu).
Insb steht da auch noch: "FreeRTOS API functions must not be called from
within a critical section." ...
foobar schrieb:> Insb steht da auch noch: "FreeRTOS API functions must not be called from> within a critical section." ...
Stimmt, weil das nämlich in den APIs selbst aufgerufen wird (ist mir
gerade bei genaueren debuggen aufgefallen).
Die API functions sind also bereits selbst thread-safe. (Erklärt auch
warum in den Docs in keinem Beispiel irgend ein mechanismus dazu
verwendet wird)
Wenn das preemptive Scheduling nämlich deaktiviert ist und die API
Funktion selbst keinen Taskwechsel erzeugt, dann muss der aktuelle Task
zwangsweise die Funktion vollständig auführen ohne eine Gefahr...
Den Fehler selbst habe ich derweil gefunden. Man sollte nicht zu sparsam
sein bei der Stackzuweisung, auch wenn dieser AVR nicht gerade der
speicherreichste ist. Der Stackoverflow hat hat perfekt den angrenzenden
Speicherbereich überschrieben, in dem gerade der Semaphore-Pointer
versorgt war. Mein Fehler...
@foobar danke dir trotzdem: Die enter und exit critical sections hab ich
jetzt raus genommen. Sie sind nicht unbedingt gefährlich, aber nach der
Überprüfung zumindest tatsächlich überflüssig an dieser Stelle.
Gefährlich wird es erst wenn man die critical section tatsächlich
benötigt, denn wenn man darin eine API aufruft, dann hebt diese API die
critical section auf (deswegen auch "FreeRTOS API functions must not be
called from
within a critical section.").
Wie gesagt, hab bisher nichts mit FreeRTOS gemacht, aber:
> Gefährlich wird es erst wenn man die critical section tatsächlich> benötigt, denn wenn man darin eine API aufruft, dann hebt diese API> die critical section auf
Laut der Doku sind Critical Sections (CS) verschachtelbar und damit
heben sie die äußere CS nicht auf. Das Verbot dürfte andere Gründe
haben, z.B. dass innerhalb einer CS keine blockierenden Funktionen
aufgerufen werden dürfen.
foobar schrieb:> Laut der Doku sind Critical Sections (CS) verschachtelbar und damit> heben sie die äußere CS nicht auf. Das Verbot dürfte andere Gründe> haben, z.B. dass innerhalb einer CS keine blockierenden Funktionen> aufgerufen werden dürfen.
Ups, da hast du recht... Habs gerade selbst durchgelesen.
Dann wird es ganz sicher daran liegen, danke dir!
Typisch implementiert man Interfaces als Stream.
Benötigt eine Task ein Interface, wird es mit fopen belegt und nach Ende
mit fclose wieder freigegeben.
Eine andere Möglichkeit ist, keine Task darf das Interface direkt
benutzen. Es werden entsprechend viele Puffer angelegt, die ein
gemeinsamer Interrupthandler der Reihe nach abarbeitet. Jede Task
schreibt und liest dann ihren Puffer.