Skip to content

sdcard: replace VLA with static buffer, add card presence guard#205

Merged
bessman merged 2 commits into
fossasia:mainfrom
dhruvmittal41:dhruv/fix/sdcard-static-buffer-and-presence-check
May 3, 2026
Merged

sdcard: replace VLA with static buffer, add card presence guard#205
bessman merged 2 commits into
fossasia:mainfrom
dhruvmittal41:dhruv/fix/sdcard-static-buffer-and-presence-check

Conversation

@dhruvmittal41

@dhruvmittal41 dhruvmittal41 commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

This PR fixes three code quality issues in src/sdcard/sdcard.c identified
during codebase analysis for the standalone SD logging project.

Changes

1. Replace VLA with static sector buffer
TCHAR buffer[block_size] in the read and write loops is a variable-length
array (VLA) allocated on the stack at runtime. On PIC24EP256GP204 with limited
stack space, this risks silent stack overflow if block_size is unexpectedly
large. Replaced with a single static 512-byte buffer s_sector_buf in .bss.
A _Static_assert is added to catch any future size mismatch at compile time.

2. Add SD card presence check before f_mount
All three command handlers called f_mount unconditionally. If no SD card is
inserted, the SPI driver waits for responses that never arrive.
SD_SPI_IsMediaPresent() already exists in sd_spi.c — this PR uses it as a
guard at the top of each handler, returning FR_NOT_READY to the host
immediately if no card is present.

3. Consistent drive path via named constant
Introduce #define SDCARD_DRIVE "0:" and replace all inline "0:" strings.
This makes the drive path a single source of truth.

No functional change

All existing commands (write_file, read_file, get_file_info) behave
identically when an SD card is present. The only behavioural change is
a fast FR_NOT_READY response when no card is inserted, instead of hanging.

Summary by Sourcery

Guard SD card file operations with a media presence check and replace stack-allocated variable-length sector buffers with a shared static buffer.

Bug Fixes:

  • Avoid SD card SPI hangs by checking media presence before mounting and handling file operations, returning FR_NOT_READY when no card is present instead.
  • Prevent potential stack overflows by replacing per-iteration variable-length sector buffers with a fixed-size static buffer.

Enhancements:

  • Introduce a named constant for the SD card drive path to ensure consistent usage across file operations.

Copilot AI review requested due to automatic review settings April 16, 2026 16:26
@sourcery-ai

sourcery-ai Bot commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Refactors sdcard.c to remove stack-allocated VLAs in favor of a single static sector buffer, adds an SD card presence guard before mounting the filesystem in all handlers, and centralizes the drive string into a named constant while making minor style/ordering tweaks.

Sequence diagram for SD card command handlers with presence guard

sequenceDiagram
    actor Host
    participant MCU
    participant UART1
    participant SD_SPI
    participant FatFs

    Host->>MCU: Send command and filename
    MCU->>UART1: UART1_Read (get_filename loop)
    UART1-->>MCU: Filename characters
    MCU->>UART1: UART1_Read (mode or request params)
    UART1-->>MCU: Mode or params

    MCU->>SD_SPI: SD_SPI_IsMediaPresent
    SD_SPI-->>MCU: Card present?

    alt Card not present
        MCU->>UART1: UART1_Write FR_NOT_READY
        MCU-->>Host: Return DO_NOT_BOTHER
    else Card present
        MCU->>FatFs: f_mount FATFS SDCARD_DRIVE 1
        FatFs-->>MCU: Mount result

        opt Write_file handler
            loop For each data block
                MCU->>UART1: UART1_Read block into s_sector_buf
                UART1-->>MCU: Block bytes
                MCU->>FatFs: f_write file s_sector_buf block_size
                FatFs-->>MCU: Write result and bytes_written
            end
        end

        opt Read_file handler
            loop For each data block
                MCU->>FatFs: f_read file s_sector_buf block_size
                FatFs-->>MCU: Read result and bytes_read
                MCU->>UART1: UART1_Write bytes from s_sector_buf
                UART1-->>Host: Block bytes
            end
        end

        opt Get_file_info handler
            MCU->>FatFs: f_stat filename FILINFO
            FatFs-->>MCU: Populate FILINFO
            MCU->>UART1: UART1_Write info fields
        end

        MCU->>FatFs: f_close file (if opened)
        FatFs-->>MCU: Close result
        MCU->>FatFs: f_mount 0 SDCARD_DRIVE 0
        FatFs-->>MCU: Unmount result
        MCU-->>Host: Command response complete
    end
Loading

Flow diagram for block-based I/O using static sector buffer

flowchart TD
    A[Start handler with total_size] --> B[Set remaining = total_size]
    B --> C{remaining > 0?}
    C -->|No| Z[End loop]
    C -->|Yes| D[Set block_size = remaining if remaining < BUF_MAX else BUF_MAX]
    D --> E[Clear watchdog timer]
    E --> F[For i from 0 to block_size - 1 read byte from UART1 into s_sector_buf for write or prepare for read]
    F --> G{Write_file handler?}
    G -->|Yes| H[Call f_write with s_sector_buf and block_size]
    H --> I[Increment bytes_written by written]
    G -->|No| J[Call f_read into s_sector_buf with block_size]
    J --> K[Increment bytes_read by read]
    I --> L[For i from 0 to block_size - 1 write s_sector_buf_i to UART1 for read response]
    K --> L
    L --> M[remaining = remaining - block_size]
    M --> C
    Z --> N[Send final byte count and close file]
    N --> O[Unmount drive and return response]
Loading

File-Level Changes

Change Details Files
Eliminate variable-length stack buffers in read/write loops in favor of a single static sector buffer with a compile-time size check.
  • Define a global static TCHAR sector buffer sized to BUF_MAX in sdcard.c.
  • Add a _Static_assert to ensure the sector buffer size is exactly 512 bytes, matching the expected SD sector size.
  • Update SDCARD_write_file to fill and pass the static buffer to f_write instead of allocating a per-iteration VLA.
  • Update SDCARD_read_file to read into the static buffer and stream its contents over UART instead of using a per-iteration VLA.
src/sdcard/sdcard.c
Guard all filesystem operations with an SD card presence check to avoid hanging when no card is inserted.
  • Include sd_spi.h so that SD_SPI_IsMediaPresent is available in sdcard.c.
  • At the start of SDCARD_write_file, SDCARD_read_file, and SDCARD_get_file_info, call SD_SPI_IsMediaPresent and, if false, immediately write FR_NOT_READY to UART and return DO_NOT_BOTHER.
src/sdcard/sdcard.c
Centralize and standardize the SD card drive path via a named constant and adjust minor style issues.
  • Define SDCARD_DRIVE "0:" and replace all hard-coded "0:" usages in f_mount calls with the macro.
  • Reorder includes so fatfs headers and sd_spi.h are grouped after project headers.
  • Apply minor formatting/style tweaks to function signatures and for loops to match the codebase style (brace placement, spacing).
src/sdcard/sdcard.c

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • The _Static_assert on s_sector_buf hardcodes 512 rather than asserting against BUF_MAX, which will cause an unnecessary compile-time failure if FF_MIN_SS is ever changed to a different valid sector size; consider asserting sizeof(s_sector_buf) == BUF_MAX instead.
  • Using a single global s_sector_buf for both read and write paths makes these handlers non-reentrant and unsafe for concurrent use (e.g., from multiple tasks/contexts); if concurrent access is possible, consider adding serialization or documenting this constraint explicitly in the module.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `_Static_assert` on `s_sector_buf` hardcodes `512` rather than asserting against `BUF_MAX`, which will cause an unnecessary compile-time failure if `FF_MIN_SS` is ever changed to a different valid sector size; consider asserting `sizeof(s_sector_buf) == BUF_MAX` instead.
- Using a single global `s_sector_buf` for both read and write paths makes these handlers non-reentrant and unsafe for concurrent use (e.g., from multiple tasks/contexts); if concurrent access is possible, consider adding serialization or documenting this constraint explicitly in the module.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the SD card UART command handlers in src/sdcard/sdcard.c by reducing stack usage and avoiding hangs when no SD card is inserted.

Changes:

  • Replaces per-iteration variable-length sector buffers with a single static 512-byte buffer.
  • Adds an SD card presence guard (SD_SPI_IsMediaPresent()) before attempting to mount.
  • Introduces SDCARD_DRIVE to centralize the FatFS drive path string.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/sdcard/sdcard.c
Comment thread src/sdcard/sdcard.c Outdated
Comment thread src/sdcard/sdcard.c
Comment thread src/sdcard/sdcard.c Outdated
Comment thread src/sdcard/sdcard.c Outdated
@marcnause marcnause requested a review from bessman April 19, 2026 17:07
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 3, 2026 14:47

@bessman bessman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

Note that SD_SPI_IsMediaPresent does not actually check media presence; it always returns true, so the added check currently adds no functionality. However, it is still good to have it in case SD_SPI_IsMediaPresent is properly implemented in the future.

@bessman bessman merged commit af77853 into fossasia:main May 3, 2026
7 checks passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/sdcard/sdcard.c
BYTE const mode = UART1_Read();

if (!SD_SPI_IsMediaPresent()) {
return FR_NOT_READY;
Comment thread src/sdcard/sdcard.c
get_filename(filename, sizeof filename);

if (!SD_SPI_IsMediaPresent()) {
UART1_Write(FR_NOT_READY);
Comment thread src/sdcard/sdcard.c
Comment on lines +102 to +110
FRESULT read_result = f_read(&file, s_sector_buf, (UINT)block_size, &read);
bytes_read += read;

for (UINT i = 0; i < block_size; ++i) {
UART1_Write(buffer[i]);
for (UINT i = 0; i < read; ++i) {
UART1_Write(s_sector_buf[i]);
}

if (read_result != FR_OK || read < block_size) {
break;
Comment thread src/sdcard/sdcard.c
Comment on lines +19 to +23
static TCHAR s_sector_buf[BUF_MAX];
_Static_assert(
sizeof(s_sector_buf) == 512,
"SD sector buffer must be 512 bytes"
);
Comment thread src/sdcard/sdcard.c
Comment on lines +25 to 28
static void get_filename(TCHAR *const fn_buf, UINT const size)
{
for (UINT i = 0; i < size; ++i) {
if (!(fn_buf[i] = UART1_Read())) {
Comment thread src/sdcard/sdcard.c
TCHAR filename[SFN_MAX + SFN_SUFFIX_LEN + 1]; // +1 is null-terminator.
get_filename(filename, sizeof filename);

if (!SD_SPI_IsMediaPresent()) {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants