Compare commits
1
Commits
zps-module
..
halow
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1acad6dc4 |
@@ -18,7 +18,7 @@ ENV PIP_ROOT_USER_ACTION=ignore
|
||||
# trunk-ignore(hadolint/DL3008): apt packages are not pinned.
|
||||
# trunk-ignore(terrascan/AC_DOCKER_0002): apt packages are not pinned.
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
cmake git zip libgpiod-dev libjsoncpp-dev libbluetooth-dev libi2c-dev \
|
||||
cmake git zip libgpiod-dev libbluetooth-dev libi2c-dev \
|
||||
libunistring-dev libmicrohttpd-dev libgnutls28-dev libgcrypt20-dev \
|
||||
libusb-1.0-0-dev libssl-dev pkg-config libsqlite3-dev libsdl2-dev && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/* && \
|
||||
|
||||
@@ -31,7 +31,7 @@ cmake --install "$WORK/ulfius/$SANITIZER" --prefix /usr
|
||||
cd "$SRC/firmware"
|
||||
|
||||
PLATFORMIO_EXTRA_SCRIPTS=$(echo -e "pre:.clusterfuzzlite/platformio-clusterfuzzlite-pre.py\npost:.clusterfuzzlite/platformio-clusterfuzzlite-post.py")
|
||||
STATIC_LIBS=$(pkg-config --libs --static libulfius openssl libgpiod yaml-cpp jsoncpp bluez --silence-errors)
|
||||
STATIC_LIBS=$(pkg-config --libs --static libulfius openssl libgpiod yaml-cpp bluez --silence-errors)
|
||||
export PLATFORMIO_EXTRA_SCRIPTS
|
||||
export STATIC_LIBS
|
||||
export PLATFORMIO_WORKSPACE_DIR="$WORK/pio/$SANITIZER"
|
||||
|
||||
@@ -16,7 +16,6 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
libssl-dev \
|
||||
libulfius-dev \
|
||||
libyaml-cpp-dev \
|
||||
libjsoncpp-dev \
|
||||
pipx \
|
||||
pkg-config \
|
||||
python3 \
|
||||
|
||||
@@ -13,7 +13,7 @@ runs:
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get -y update --fix-missing
|
||||
sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev libjsoncpp-dev lsb-release
|
||||
sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev lsb-release
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
|
||||
@@ -11,4 +11,4 @@ runs:
|
||||
- name: Install libs needed for native build
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev libjsoncpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev libuv1-dev
|
||||
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev libuv1-dev
|
||||
|
||||
@@ -609,35 +609,20 @@ Most workflows can be triggered manually via `workflow_dispatch` for testing.
|
||||
|
||||
### Native unit tests (C++)
|
||||
|
||||
Unit tests in `test/` directory with 17 test suites:
|
||||
Unit tests in `test/` directory with 12 test suites:
|
||||
|
||||
- `test_admin_radio/` - LoRa region/config validation and AdminModule dispatch
|
||||
- `test_atak/` - ATAK integration
|
||||
- `test_crypto/` - Cryptography
|
||||
- `test_default/` - Default configuration
|
||||
- `test_http_content_handler/` - HTTP handling
|
||||
- `test_mac_from_string/` - MAC address parsing
|
||||
- `test_mqtt/` - MQTT integration
|
||||
- `test_radio/` - Radio interface
|
||||
- `test_mesh_module/` - Module framework
|
||||
- `test_meshpacket_serializer/` - Packet serialization
|
||||
- `test_mqtt/` - MQTT integration
|
||||
- `test_packet_history/` - Packet history tracking
|
||||
- `test_position_precision/` - Position precision helpers
|
||||
- `test_radio/` - Radio interface
|
||||
- `test_serial/` - Serial communication
|
||||
- `test_traffic_management/` - Traffic management
|
||||
- `test_transmit_history/` - Retransmission tracking
|
||||
- `test_type_conversions/` - NodeDB v25 type conversion (bitfield round-trips, NodeInfoLite)
|
||||
- `test_utf8/` - UTF-8 utilities
|
||||
- `test_atak/` - ATAK integration
|
||||
- `test_default/` - Default configuration
|
||||
- `test_http_content_handler/` - HTTP handling
|
||||
- `test_serial/` - Serial communication
|
||||
|
||||
Run command (preferred — avoids pipe-buffering and the Ubuntu externally-managed-environment error):
|
||||
|
||||
```bash
|
||||
~/.platformio/penv/bin/python -m platformio test -e native -f test_your_suite > /tmp/test_out.txt 2>&1
|
||||
grep -E 'error:|PASS|FAIL|succeeded|failed' /tmp/test_out.txt
|
||||
tail -15 /tmp/test_out.txt
|
||||
```
|
||||
|
||||
Do **not** use `pio test … | tail -N` — it discards build errors and shows stale cached results. Do **not** use `pio test … | grep` — line-buffering makes the terminal appear hung until the process exits. Redirect to a file first, then grep.
|
||||
Run with: `pio test -e native`
|
||||
|
||||
Simulation testing: `bin/test-simulator.sh`
|
||||
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
name: Post Firmware Size Comment
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [CI]
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
post-size-comment:
|
||||
if: >
|
||||
github.event.workflow_run.event == 'pull_request' &&
|
||||
github.event.workflow_run.conclusion != 'cancelled' &&
|
||||
github.repository == 'meshtastic/firmware'
|
||||
continue-on-error: true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download size report
|
||||
id: download
|
||||
uses: actions/download-artifact@v8
|
||||
continue-on-error: true
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
name: size-report
|
||||
path: ./
|
||||
|
||||
- name: Post or update PR comment
|
||||
if: steps.download.outcome == 'success'
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const marker = '<!-- firmware-size-report -->';
|
||||
const body = fs.readFileSync('./size-report.md', 'utf8');
|
||||
const prNumber = parseInt(fs.readFileSync('./pr-number.txt', 'utf8').trim(), 10);
|
||||
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
});
|
||||
|
||||
const existing = comments.find(c => c.body.includes(marker));
|
||||
if (existing) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: existing.id,
|
||||
body,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
body,
|
||||
});
|
||||
}
|
||||
@@ -245,126 +245,47 @@ jobs:
|
||||
path: ./*.elf
|
||||
retention-days: 30
|
||||
|
||||
firmware-size-report:
|
||||
shame:
|
||||
if: github.repository == 'meshtastic/firmware'
|
||||
continue-on-error: true
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Download current manifests
|
||||
if: github.event_name == 'pull_request'
|
||||
with:
|
||||
filter: blob:none # means we download all the git history but none of the commit (except ones with checkout like the head)
|
||||
fetch-depth: 0
|
||||
- name: Download the current manifests
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: ./manifests/
|
||||
path: ./manifests-new/
|
||||
pattern: manifest-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Collect current firmware sizes
|
||||
run: python3 bin/collect_sizes.py ./manifests/ ./current-sizes.json
|
||||
|
||||
- name: Upload size report artifact
|
||||
- name: Upload combined manifests for later commit and global stats crunching.
|
||||
uses: actions/upload-artifact@v7
|
||||
id: upload-manifest
|
||||
with:
|
||||
name: firmware-sizes-${{ github.sha }}
|
||||
name: manifests-${{ github.sha }}
|
||||
overwrite: true
|
||||
path: ./current-sizes.json
|
||||
retention-days: 90
|
||||
|
||||
- name: Download baseline sizes from develop
|
||||
path: manifests-new/*.mt.json
|
||||
- name: Find the merge base
|
||||
if: github.event_name == 'pull_request'
|
||||
continue-on-error: true
|
||||
id: baseline-develop
|
||||
run: echo "MERGE_BASE=$(git merge-base "origin/$base" "$head")" >> $GITHUB_ENV
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
RUN_ID=$(gh run list -R "${{ github.repository }}" \
|
||||
--workflow CI --branch develop --status success \
|
||||
--limit 1 --json databaseId --jq '.[0].databaseId // empty')
|
||||
if [ -n "$RUN_ID" ]; then
|
||||
ARTIFACT_NAME=$(gh api "repos/${{ github.repository }}/actions/runs/${RUN_ID}/artifacts" \
|
||||
--jq '.artifacts[] | select(.name | startswith("firmware-sizes-")) | .name' | head -1)
|
||||
if [ -n "$ARTIFACT_NAME" ]; then
|
||||
gh run download "$RUN_ID" -R "${{ github.repository }}" \
|
||||
--name "$ARTIFACT_NAME" --dir ./baseline-develop/
|
||||
cp "./baseline-develop/${ARTIFACT_NAME}/current-sizes.json" ./develop-sizes.json
|
||||
echo "found=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "found=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
else
|
||||
echo "found=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Download baseline sizes from master
|
||||
if: github.event_name == 'pull_request'
|
||||
continue-on-error: true
|
||||
id: baseline-master
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
RUN_ID=$(gh run list -R "${{ github.repository }}" \
|
||||
--workflow CI --branch master --status success \
|
||||
--limit 1 --json databaseId --jq '.[0].databaseId // empty')
|
||||
if [ -n "$RUN_ID" ]; then
|
||||
ARTIFACT_NAME=$(gh api "repos/${{ github.repository }}/actions/runs/${RUN_ID}/artifacts" \
|
||||
--jq '.artifacts[] | select(.name | startswith("firmware-sizes-")) | .name' | head -1)
|
||||
if [ -n "$ARTIFACT_NAME" ]; then
|
||||
gh run download "$RUN_ID" -R "${{ github.repository }}" \
|
||||
--name "$ARTIFACT_NAME" --dir ./baseline-master/
|
||||
cp "./baseline-master/${ARTIFACT_NAME}/current-sizes.json" ./master-sizes.json
|
||||
echo "found=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "found=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
else
|
||||
echo "found=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Generate size comparison report
|
||||
if: github.event_name == 'pull_request'
|
||||
id: report
|
||||
run: |
|
||||
ARGS="./current-sizes.json"
|
||||
if [ -f ./develop-sizes.json ]; then
|
||||
ARGS="$ARGS --baseline develop:./develop-sizes.json"
|
||||
fi
|
||||
if [ -f ./master-sizes.json ]; then
|
||||
ARGS="$ARGS --baseline master:./master-sizes.json"
|
||||
fi
|
||||
REPORT=$(python3 bin/size_report.py $ARGS)
|
||||
if [ -z "$REPORT" ]; then
|
||||
echo "has_report=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "has_report=true" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo '<!-- firmware-size-report -->'
|
||||
echo '# Firmware Size Report'
|
||||
echo ''
|
||||
echo "$REPORT"
|
||||
echo ''
|
||||
echo '---'
|
||||
echo "*Updated for ${{ github.sha }}*"
|
||||
} > ./size-report.md
|
||||
cat ./size-report.md >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
- name: Save PR number
|
||||
if: github.event_name == 'pull_request' && steps.report.outputs.has_report == 'true'
|
||||
run: echo "${{ github.event.pull_request.number }}" > ./pr-number.txt
|
||||
|
||||
- name: Upload size report
|
||||
if: github.event_name == 'pull_request' && steps.report.outputs.has_report == 'true'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: size-report
|
||||
path: |
|
||||
./size-report.md
|
||||
./pr-number.txt
|
||||
retention-days: 5
|
||||
base: ${{ github.base_ref }}
|
||||
head: ${{ github.sha }}
|
||||
# Currently broken (for-loop through EVERY artifact -- rate limiting)
|
||||
# - name: Download the old manifests
|
||||
# if: github.event_name == 'pull_request'
|
||||
# run: gh run download -R "$repo" --name "manifests-$merge_base" --dir manifest-old/
|
||||
# env:
|
||||
# GH_TOKEN: ${{ github.token }}
|
||||
# merge_base: ${{ env.MERGE_BASE }}
|
||||
# repo: ${{ github.repository }}
|
||||
# - name: Do scan and post comment
|
||||
# if: github.event_name == 'pull_request'
|
||||
# run: python3 bin/shame.py ${{ github.event.pull_request.number }} manifests-old/ manifests-new/
|
||||
|
||||
release-artifacts:
|
||||
permissions: # Needed for 'gh release upload'.
|
||||
@@ -412,7 +333,6 @@ jobs:
|
||||
prerelease: true
|
||||
name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
|
||||
tag_name: v${{ needs.version.outputs.long }}
|
||||
target_commitish: ${{ github.sha }}
|
||||
body: ${{ steps.release_notes.outputs.notes }}
|
||||
|
||||
- name: Download source deb
|
||||
|
||||
+7
-4
@@ -47,10 +47,6 @@ data/boot/logo.*
|
||||
managed_components/*
|
||||
arduino-lib-builder*
|
||||
dependencies.lock
|
||||
|
||||
# JLink / RTT debug artifacts (nRF SoCs)
|
||||
flash.jlink
|
||||
rtt_*.txt
|
||||
idf_component.yml
|
||||
CMakeLists.txt
|
||||
/sdkconfig.*
|
||||
@@ -66,3 +62,10 @@ userPrefs.jsonc.mcp-session-bak
|
||||
# compiled .proto outputs are ephemeral build artifacts.
|
||||
build/fixtures/
|
||||
bin/_generated/
|
||||
|
||||
# Build artifacts: anything compiling .o/.a outside .pio/ is accidental.
|
||||
# Explicit exceptions for vendored binaries (Morse Micro mm-iot-esp32 SDK).
|
||||
*.o
|
||||
*.a
|
||||
!lib/MorseWlan/lib/**/*.a
|
||||
!lib/MorseWlan/src/*.mbin.o
|
||||
|
||||
+4
-4
@@ -4,11 +4,11 @@ cli:
|
||||
plugins:
|
||||
sources:
|
||||
- id: trunk
|
||||
ref: v1.10.0
|
||||
ref: v1.9.0
|
||||
uri: https://github.com/trunk-io/plugins
|
||||
lint:
|
||||
enabled:
|
||||
- checkov@3.2.529
|
||||
- checkov@3.2.528
|
||||
- renovate@43.150.0
|
||||
- prettier@3.8.3
|
||||
- trufflehog@3.95.3
|
||||
@@ -16,7 +16,7 @@ lint:
|
||||
- bandit@1.9.4
|
||||
- trivy@0.70.0
|
||||
- taplo@0.10.0
|
||||
- ruff@0.15.13
|
||||
- ruff@0.15.12
|
||||
- isort@8.0.1
|
||||
- markdownlint@0.48.0
|
||||
- oxipng@10.1.1
|
||||
@@ -26,7 +26,7 @@ lint:
|
||||
- hadolint@2.14.0
|
||||
- shfmt@3.6.0
|
||||
- shellcheck@0.11.0
|
||||
- black@26.5.1
|
||||
- black@26.3.1
|
||||
- git-diff-check
|
||||
- gitleaks@8.30.1
|
||||
- clang-format@16.0.3
|
||||
|
||||
@@ -10,18 +10,18 @@ This file (`AGENTS.md`) is a short pointer + quick reference for agents that don
|
||||
|
||||
## Quick command reference
|
||||
|
||||
| Action | Command |
|
||||
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Build a firmware variant | `pio run -e <env>` (e.g. `pio run -e rak4631`, `pio run -e heltec-v3`) |
|
||||
| Build native macOS host binary | `pio run -e native-macos` (Homebrew prereqs + CH341 LoRa setup in `variants/native/portduino/platformio.ini`) |
|
||||
| Clean + rebuild | `pio run -e <env> -t clean && pio run -e <env>` |
|
||||
| Flash a device | `pio run -e <env> -t upload --upload-port <port>` (or use the `pio_flash` MCP tool) |
|
||||
| Run firmware unit tests (native) | `~/.platformio/penv/bin/python -m platformio test -e native > /tmp/test_out.txt 2>&1` then `grep -E 'error:\|PASS\|FAIL\|succeeded\|failed' /tmp/test_out.txt` (redirect first — piping causes line-buffering) |
|
||||
| Run MCP hardware tests | `./mcp-server/run-tests.sh` |
|
||||
| Live TUI test runner | `mcp-server/.venv/bin/meshtastic-mcp-test-tui` |
|
||||
| Format before commit | `trunk fmt` |
|
||||
| Regenerate protobuf bindings | `bin/regen-protos.sh` |
|
||||
| Generate CI matrix | `./bin/generate_ci_matrix.py all [--level pr]` |
|
||||
| Action | Command |
|
||||
| -------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
||||
| Build a firmware variant | `pio run -e <env>` (e.g. `pio run -e rak4631`, `pio run -e heltec-v3`) |
|
||||
| Build native macOS host binary | `pio run -e native-macos` (Homebrew prereqs + CH341 LoRa setup in `variants/native/portduino/platformio.ini`) |
|
||||
| Clean + rebuild | `pio run -e <env> -t clean && pio run -e <env>` |
|
||||
| Flash a device | `pio run -e <env> -t upload --upload-port <port>` (or use the `pio_flash` MCP tool) |
|
||||
| Run firmware unit tests (native) | `pio test -e native` |
|
||||
| Run MCP hardware tests | `./mcp-server/run-tests.sh` |
|
||||
| Live TUI test runner | `mcp-server/.venv/bin/meshtastic-mcp-test-tui` |
|
||||
| Format before commit | `trunk fmt` |
|
||||
| Regenerate protobuf bindings | `bin/regen-protos.sh` |
|
||||
| Generate CI matrix | `./bin/generate_ci_matrix.py all [--level pr]` |
|
||||
|
||||
## MCP server (device + test automation)
|
||||
|
||||
@@ -108,7 +108,7 @@ Sequence these; don't parallelize on the same port.
|
||||
| `src/modules/` | Feature modules; `Telemetry/Sensor/` has 50+ I2C sensor drivers |
|
||||
| `variants/` | 200+ hardware variant definitions (`variant.h` + `platformio.ini` per board) |
|
||||
| `protobufs/` | `.proto` definitions; regenerate with `bin/regen-protos.sh` |
|
||||
| `test/` | Firmware unit tests (17 suites; `pio test -e native`) |
|
||||
| `test/` | Firmware unit tests (12 suites; `pio test -e native`) |
|
||||
| `mcp-server/` | Python MCP server + pytest hardware integration tests |
|
||||
| `mcp-server/tests/` | Tiered pytest suite: `unit/`, `mesh/`, `telemetry/`, `monitor/`, `recovery/`, `ui/`, `fleet/`, `admin/`, `provisioning/` |
|
||||
| `.claude/commands/` | Claude Code slash command bodies |
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ ENV PIP_BREAK_SYSTEM_PACKAGES=1
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
curl wget g++ zip git ca-certificates pkg-config \
|
||||
python3-pip python3-grpc-tools \
|
||||
libgpiod-dev libyaml-cpp-dev libjsoncpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
|
||||
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
|
||||
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \
|
||||
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||
@@ -53,7 +53,7 @@ ENV TZ=Etc/UTC
|
||||
USER root
|
||||
|
||||
RUN apt-get update && apt-get --no-install-recommends -y install \
|
||||
libc-bin libc6 libgpiod3 libyaml-cpp0.8 libjsoncpp26 libi2c0 libuv1t64 libusb-1.0-0-dev \
|
||||
libc-bin libc6 libgpiod3 libyaml-cpp0.8 libi2c0 libuv1t64 libusb-1.0-0-dev \
|
||||
liborcania2.3 libulfius2.7t64 libssl3t64 \
|
||||
libx11-6 libinput10 libxkbcommon-x11-0 libsdl2-2.0-0 \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ ENV PIP_ROOT_USER_ACTION=ignore
|
||||
# hadolint ignore=DL3008
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
g++ git ca-certificates pkg-config \
|
||||
libgpiod-dev libyaml-cpp-dev libjsoncpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
|
||||
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
|
||||
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \
|
||||
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@ ENV PIP_BREAK_SYSTEM_PACKAGES=1
|
||||
RUN apk --no-cache add \
|
||||
bash g++ libstdc++-dev linux-headers zip git ca-certificates libbsd-dev \
|
||||
py3-pip py3-grpcio-tools \
|
||||
libgpiod-dev yaml-cpp-dev jsoncpp-dev bluez-dev \
|
||||
libgpiod-dev yaml-cpp-dev bluez-dev \
|
||||
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
|
||||
libx11-dev libinput-dev libxkbcommon-dev sqlite-dev sdl2-dev \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
@@ -48,7 +48,7 @@ LABEL org.opencontainers.image.title="Meshtastic" \
|
||||
USER root
|
||||
|
||||
RUN apk --no-cache add \
|
||||
shadow libstdc++ libbsd libgpiod yaml-cpp jsoncpp libusb \
|
||||
shadow libstdc++ libbsd libgpiod yaml-cpp libusb \
|
||||
i2c-tools libuv libx11 libinput libxkbcommon sdl2 \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& mkdir -p /var/lib/meshtasticd \
|
||||
|
||||
+1
-1
@@ -38,4 +38,4 @@ cp bin/device-install.* $OUTDIR/
|
||||
cp bin/device-update.* $OUTDIR/
|
||||
|
||||
echo "Copying manifest"
|
||||
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
|
||||
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json || true
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Collect firmware binary sizes from manifest (.mt.json) files into a single report."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def collect_sizes(manifest_dir):
|
||||
"""Scan manifest_dir for .mt.json files and return {board: size_bytes} dict."""
|
||||
sizes = {}
|
||||
for fname in sorted(os.listdir(manifest_dir)):
|
||||
if not fname.endswith(".mt.json"):
|
||||
continue
|
||||
path = os.path.join(manifest_dir, fname)
|
||||
with open(path) as f:
|
||||
data = json.load(f)
|
||||
board = data.get("platformioTarget", fname.replace(".mt.json", ""))
|
||||
# Find the main firmware .bin size (largest .bin, excluding OTA/littlefs/bleota)
|
||||
bin_size = None
|
||||
for entry in data.get("files", []):
|
||||
name = entry.get("name", "")
|
||||
if name.startswith("firmware-") and name.endswith(".bin"):
|
||||
bin_size = entry["bytes"]
|
||||
break
|
||||
# Fallback: any .bin that isn't ota/littlefs/bleota
|
||||
if bin_size is None:
|
||||
for entry in data.get("files", []):
|
||||
name = entry.get("name", "")
|
||||
if name.endswith(".bin") and not any(
|
||||
x in name for x in ["littlefs", "bleota", "ota"]
|
||||
):
|
||||
bin_size = entry["bytes"]
|
||||
break
|
||||
if bin_size is not None:
|
||||
sizes[board] = bin_size
|
||||
return sizes
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 3:
|
||||
print(f"Usage: {sys.argv[0]} <manifest_dir> <output.json>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
manifest_dir = sys.argv[1]
|
||||
output_path = sys.argv[2]
|
||||
|
||||
sizes = collect_sizes(manifest_dir)
|
||||
with open(output_path, "w") as f:
|
||||
json.dump(sizes, f, indent=2, sort_keys=True)
|
||||
|
||||
print(f"Collected sizes for {len(sizes)} targets -> {output_path}")
|
||||
@@ -5,9 +5,7 @@ Meta:
|
||||
- raspberry-pi
|
||||
|
||||
Lora:
|
||||
|
||||
### RAK13300 in Slot 2
|
||||
Module: sx1262
|
||||
### RAK13300 in Slot 2 pins
|
||||
IRQ: 18 #IO6
|
||||
Reset: 24 # IO4
|
||||
Busy: 19 # IO5
|
||||
@@ -15,7 +13,5 @@ Lora:
|
||||
Enable_Pins:
|
||||
- 26
|
||||
- 23
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
spidev: spidev0.1
|
||||
# CS: 7
|
||||
@@ -5,18 +5,14 @@ Meta:
|
||||
- raspberry-pi
|
||||
|
||||
Lora:
|
||||
|
||||
### RAK13302 in Slot 2
|
||||
Module: sx1262
|
||||
### RAK13302 in Slot 2 pins
|
||||
IRQ: 18 #IO6
|
||||
Reset: 24 # IO4
|
||||
Busy: 19 # IO5
|
||||
# Ant_sw: 23 # IO3
|
||||
# Ant_sw: 23 # IO3
|
||||
Enable_Pins:
|
||||
- 26
|
||||
- 23
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
spidev: spidev0.1
|
||||
# CS: 7
|
||||
TX_GAIN_LORA: [9, 9, 10, 11, 9, 8, 9, 10, 10, 10, 11, 12, 12, 12, 12, 12, 12, 12, 12, 10, 9, 8]
|
||||
@@ -293,12 +293,9 @@ if ("HAS_TFT", 1) in env.get("CPPDEFINES", []):
|
||||
board_arch = infer_architecture(env.BoardConfig())
|
||||
should_skip_manifest = board_arch is None
|
||||
|
||||
# Most platforms can generate the manifest as part of the default 'buildprog' target.
|
||||
# Typically this passes success/failure properly.
|
||||
mtjson_deps = ["buildprog"]
|
||||
if platform.name == "espressif32":
|
||||
# On ESP32, we need to explicitly depend upon the binary to prevent fake-success upon failure.
|
||||
mtjson_deps = ["$BUILD_DIR/${PROGNAME}.bin"]
|
||||
# For host/native envs, avoid depending on 'buildprog' (some targets don't define it)
|
||||
mtjson_deps = [] if should_skip_manifest else ["buildprog"]
|
||||
if not should_skip_manifest and platform.name == "espressif32":
|
||||
# Build littlefs image as part of mtjson target
|
||||
# Equivalent to `pio run -t buildfs`
|
||||
target_lfs = env.DataToBin(
|
||||
@@ -312,8 +309,7 @@ if should_skip_manifest:
|
||||
|
||||
env.AddCustomTarget(
|
||||
name="mtjson",
|
||||
# For host/native envs, avoid depending on 'buildprog' (some targets don't define it)
|
||||
dependencies=[],
|
||||
dependencies=mtjson_deps,
|
||||
actions=[skip_manifest],
|
||||
title="Meshtastic Manifest (skipped)",
|
||||
description="mtjson generation is skipped for native environments",
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
from github import Github
|
||||
|
||||
def parseFile(path):
|
||||
with open(path, "r") as f:
|
||||
data = json.loads(f)
|
||||
for file in data["files"]:
|
||||
if file["name"].endswith(".bin"):
|
||||
return file["name"], file["bytes"]
|
||||
|
||||
if len(sys.argv) != 4:
|
||||
print(f"expected usage: {sys.argv[0]} <PR number> <path to old-manifests> <path to new-manifests>")
|
||||
sys.exit(1)
|
||||
|
||||
pr_number = int(sys.argv[1])
|
||||
|
||||
token = os.getenv("GITHUB_TOKEN")
|
||||
if not token:
|
||||
raise EnvironmentError("GITHUB_TOKEN not found in environment.")
|
||||
|
||||
repo_name = os.getenv("GITHUB_REPOSITORY") # "owner/repo"
|
||||
if not repo_name:
|
||||
raise EnvironmentError("GITHUB_REPOSITORY not found in environment.")
|
||||
|
||||
oldFiles = sys.argv[2]
|
||||
old = set(os.path.join(oldFiles, f) for f in os.listdir(oldFiles) if os.path.isfile(f))
|
||||
newFiles = sys.argv[3]
|
||||
new = set(os.path.join(newFiles, f) for f in os.listdir(newFiles) if os.path.isfile(f))
|
||||
|
||||
startMarkdown = "# Target Size Changes\n\n"
|
||||
markdown = ""
|
||||
|
||||
newlyIntroduced = new - old
|
||||
if len(newlyIntroduced) > 0:
|
||||
markdown += "## Newly Introduced Targets\n\n"
|
||||
# create a table
|
||||
markdown += "| File | Size |\n"
|
||||
markdown += "| ---- | ---- |\n"
|
||||
for f in newlyIntroduced:
|
||||
name, size = parseFile(f)
|
||||
markdown += f"| `{name}` | {size}b |\n"
|
||||
|
||||
# do not log removed targets
|
||||
# PRs only run a small subset of builds, so removed targets are not meaningful
|
||||
# since they are very likely to just be not ran in PR CI
|
||||
|
||||
both = old & new
|
||||
degradations = []
|
||||
improvements = []
|
||||
for f in both:
|
||||
oldName, oldSize = parseFile(f)
|
||||
_, newSize = parseFile(f)
|
||||
if oldSize != newSize:
|
||||
if newSize < oldSize:
|
||||
improvements.append((oldName, oldSize, newSize))
|
||||
else:
|
||||
degradations.append((oldName, oldSize, newSize))
|
||||
|
||||
if len(degradations) > 0:
|
||||
markdown += "\n## Degradation\n\n"
|
||||
# create a table
|
||||
markdown += "| File | Difference | Old Size | New Size |\n"
|
||||
markdown += "| ---- | ---------- | -------- | -------- |\n"
|
||||
for oldName, oldSize, newSize in degradations:
|
||||
markdown += f"| `{oldName}` | **{oldSize - newSize}b** | {oldSize}b | {newSize}b |\n"
|
||||
|
||||
if len(improvements) > 0:
|
||||
markdown += "\n## Improvement\n\n"
|
||||
# create a table
|
||||
markdown += "| File | Difference | Old Size | New Size |\n"
|
||||
markdown += "| ---- | ---------- | -------- | -------- |\n"
|
||||
for oldName, oldSize, newSize in improvements:
|
||||
markdown += f"| `{oldName}` | **{oldSize - newSize}b** | {oldSize}b | {newSize}b |\n"
|
||||
|
||||
if len(markdown) == 0:
|
||||
markdown = "No changes in target sizes detected."
|
||||
|
||||
g = Github(token)
|
||||
repo = g.get_repo(repo_name)
|
||||
pr = repo.get_pull(pr_number)
|
||||
|
||||
existing_comment = None
|
||||
for comment in pr.get_issue_comments():
|
||||
if comment.body.startswith(startMarkdown):
|
||||
existing_comment = comment
|
||||
break
|
||||
|
||||
final_markdown = startMarkdown + markdown
|
||||
|
||||
if existing_comment:
|
||||
existing_comment.edit(body=final_markdown)
|
||||
else:
|
||||
pr.create_issue_comment(body=final_markdown)
|
||||
@@ -1,171 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Compare firmware size reports and generate a markdown summary.
|
||||
|
||||
Usage:
|
||||
size_report.py <new_sizes.json> [--baseline <label>:<old_sizes.json>]...
|
||||
|
||||
Examples:
|
||||
# Compare PR against develop and master baselines
|
||||
size_report.py pr.json --baseline develop:develop.json --baseline master:master.json
|
||||
|
||||
# Single baseline comparison
|
||||
size_report.py pr.json --baseline develop:develop.json
|
||||
|
||||
# No baselines — shows sizes with blank delta columns
|
||||
size_report.py pr.json
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
def load_sizes(path):
|
||||
with open(path) as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def format_delta(n):
|
||||
"""Format byte delta with sign and human-friendly suffix."""
|
||||
sign = "+" if n > 0 else ""
|
||||
if abs(n) >= 1024:
|
||||
return f"{sign}{n:,} ({sign}{n / 1024:.1f} KB)"
|
||||
return f"{sign}{n:,}"
|
||||
|
||||
|
||||
def generate_markdown(new_sizes, baselines, top_n=5):
|
||||
"""Generate a single table with current size and delta columns per baseline.
|
||||
|
||||
baselines: list of (label, old_sizes_dict), may be empty
|
||||
"""
|
||||
labels = [label for label, _ in baselines]
|
||||
|
||||
# Build rows: (board, current_size, [(delta, abs_delta) per baseline])
|
||||
rows = []
|
||||
for board in sorted(new_sizes):
|
||||
current = new_sizes[board]
|
||||
deltas = []
|
||||
for _, old_sizes in baselines:
|
||||
old = old_sizes.get(board)
|
||||
if old is not None:
|
||||
d = current - old
|
||||
deltas.append((d, abs(d)))
|
||||
else:
|
||||
deltas.append((None, 0))
|
||||
# Sort key: max abs delta across baselines (biggest changes first)
|
||||
max_abs = max((ad for _, ad in deltas), default=0)
|
||||
rows.append((board, current, deltas, max_abs))
|
||||
|
||||
rows.sort(key=lambda r: r[3], reverse=True)
|
||||
|
||||
# Summary line
|
||||
sections = []
|
||||
summary_parts = [f"{len(new_sizes)} targets"]
|
||||
for i, (label, old_sizes) in enumerate(baselines):
|
||||
increases = sum(
|
||||
1 for _, _, deltas, _ in rows if deltas[i][0] is not None and deltas[i][0] > 0
|
||||
)
|
||||
decreases = sum(
|
||||
1 for _, _, deltas, _ in rows if deltas[i][0] is not None and deltas[i][0] < 0
|
||||
)
|
||||
net = sum(
|
||||
deltas[i][0] for _, _, deltas, _ in rows if deltas[i][0] is not None
|
||||
)
|
||||
parts = []
|
||||
if increases:
|
||||
parts.append(f"{increases} increased")
|
||||
if decreases:
|
||||
parts.append(f"{decreases} decreased")
|
||||
if parts:
|
||||
parts.append(f"net {format_delta(net)}")
|
||||
summary_parts.append(f"vs `{label}`: {', '.join(parts)}")
|
||||
else:
|
||||
summary_parts.append(f"vs `{label}`: no changes")
|
||||
|
||||
if not baselines:
|
||||
summary_parts.append("no baseline available yet")
|
||||
|
||||
sections.append(f"**{' | '.join(summary_parts)}**\n")
|
||||
|
||||
# Table header
|
||||
header = "| Target | Size |"
|
||||
separator = "|--------|-----:|"
|
||||
for label in labels:
|
||||
header += f" vs `{label}` |"
|
||||
separator += "----------:|"
|
||||
sections.append(header)
|
||||
sections.append(separator)
|
||||
|
||||
def format_row(board, current, deltas):
|
||||
row = f"| `{board}` | {current:,} |"
|
||||
for d, _ in deltas:
|
||||
if d is None:
|
||||
row += " |"
|
||||
elif d == 0:
|
||||
row += " 0 |"
|
||||
else:
|
||||
icon = "📈" if d > 0 else "📉"
|
||||
row += f" {icon} {format_delta(d)} |"
|
||||
return row
|
||||
|
||||
# Top N rows always visible
|
||||
top = rows[:top_n]
|
||||
for board, current, deltas, _ in top:
|
||||
sections.append(format_row(board, current, deltas))
|
||||
|
||||
# Remaining rows in expandable section
|
||||
rest = rows[top_n:]
|
||||
if rest:
|
||||
sections.append("")
|
||||
sections.append(
|
||||
f"<details><summary>Show {len(rest)} more target(s)</summary>\n"
|
||||
)
|
||||
sections.append(header)
|
||||
sections.append(separator)
|
||||
for board, current, deltas, _ in rest:
|
||||
sections.append(format_row(board, current, deltas))
|
||||
sections.append("\n</details>")
|
||||
|
||||
sections.append("")
|
||||
return "\n".join(sections)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Compare firmware size reports")
|
||||
parser.add_argument("new_sizes", help="Path to new sizes JSON")
|
||||
parser.add_argument(
|
||||
"--baseline",
|
||||
action="append",
|
||||
default=[],
|
||||
metavar="LABEL:PATH",
|
||||
help="Baseline to compare against (e.g. develop:develop.json)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--top",
|
||||
type=int,
|
||||
default=5,
|
||||
help="Number of top changes to show before collapsing (default: 5)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
new_sizes = load_sizes(args.new_sizes)
|
||||
|
||||
# Silence output when no targets were built — repo maintainer choice
|
||||
if not new_sizes:
|
||||
return
|
||||
|
||||
baselines = []
|
||||
for b in args.baseline:
|
||||
if ":" not in b:
|
||||
print(f"Error: baseline must be LABEL:PATH, got '{b}'", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
label, path = b.split(":", 1)
|
||||
baselines.append((label, load_sizes(path)))
|
||||
|
||||
md = generate_markdown(new_sizes, baselines, top_n=args.top)
|
||||
print(md)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,262 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Tests for bin/collect_sizes.py and bin/size_report.py."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), "..", "bin")
|
||||
|
||||
|
||||
def make_manifest(target, firmware_bytes, extra_files=None):
|
||||
"""Create a minimal .mt.json manifest dict."""
|
||||
files = [{"name": f"firmware-{target}-2.6.0.bin", "bytes": firmware_bytes}]
|
||||
if extra_files:
|
||||
files.extend(extra_files)
|
||||
return {
|
||||
"platformioTarget": target,
|
||||
"version": "2.6.0.test",
|
||||
"files": files,
|
||||
}
|
||||
|
||||
|
||||
def write_manifests(tmpdir, manifests):
|
||||
"""Write manifest dicts as .mt.json files into tmpdir."""
|
||||
for target, data in manifests.items():
|
||||
path = os.path.join(tmpdir, f"firmware-{target}.mt.json")
|
||||
with open(path, "w") as f:
|
||||
json.dump(data, f)
|
||||
|
||||
|
||||
def run_script(script, args):
|
||||
"""Run a Python script and return (returncode, stdout, stderr)."""
|
||||
result = subprocess.run(
|
||||
[sys.executable, os.path.join(SCRIPTS_DIR, script)] + args,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
return result.returncode, result.stdout, result.stderr
|
||||
|
||||
|
||||
def test_collect_sizes_basic():
|
||||
"""collect_sizes picks up firmware-*.bin entries from manifests."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
outfile = os.path.join(tmpdir, "sizes.json")
|
||||
manifests = {
|
||||
"heltec-v3": make_manifest("heltec-v3", 1048576),
|
||||
"rak4631": make_manifest("rak4631", 524288),
|
||||
"tbeam": make_manifest("tbeam", 786432),
|
||||
}
|
||||
write_manifests(tmpdir, manifests)
|
||||
|
||||
rc, stdout, stderr = run_script("collect_sizes.py", [tmpdir, outfile])
|
||||
assert rc == 0, f"collect_sizes failed: {stderr}"
|
||||
assert "3 targets" in stdout
|
||||
|
||||
with open(outfile) as f:
|
||||
sizes = json.load(f)
|
||||
assert sizes == {"heltec-v3": 1048576, "rak4631": 524288, "tbeam": 786432}
|
||||
|
||||
|
||||
def test_collect_sizes_fallback_bin():
|
||||
"""collect_sizes falls back to non-firmware-prefixed .bin if no firmware-*.bin."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
outfile = os.path.join(tmpdir, "sizes.json")
|
||||
# Manifest with only a generic .bin (no firmware- prefix)
|
||||
data = {
|
||||
"platformioTarget": "custom-board",
|
||||
"files": [
|
||||
{"name": "littlefs-custom-board.bin", "bytes": 100000},
|
||||
{"name": "custom-board.bin", "bytes": 500000},
|
||||
],
|
||||
}
|
||||
path = os.path.join(tmpdir, "firmware-custom-board.mt.json")
|
||||
with open(path, "w") as f:
|
||||
json.dump(data, f)
|
||||
|
||||
rc, stdout, stderr = run_script("collect_sizes.py", [tmpdir, outfile])
|
||||
assert rc == 0, f"collect_sizes failed: {stderr}"
|
||||
|
||||
with open(outfile) as f:
|
||||
sizes = json.load(f)
|
||||
assert sizes == {"custom-board": 500000}
|
||||
|
||||
|
||||
def test_collect_sizes_skips_ota_littlefs():
|
||||
"""collect_sizes ignores ota/littlefs/bleota .bin files in fallback."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
outfile = os.path.join(tmpdir, "sizes.json")
|
||||
data = {
|
||||
"platformioTarget": "board-x",
|
||||
"files": [
|
||||
{"name": "littlefs-board-x.bin", "bytes": 100000},
|
||||
{"name": "bleota-board-x.bin", "bytes": 50000},
|
||||
{"name": "mt-board-x-ota.bin", "bytes": 60000},
|
||||
],
|
||||
}
|
||||
path = os.path.join(tmpdir, "firmware-board-x.mt.json")
|
||||
with open(path, "w") as f:
|
||||
json.dump(data, f)
|
||||
|
||||
rc, stdout, stderr = run_script("collect_sizes.py", [tmpdir, outfile])
|
||||
assert rc == 0
|
||||
with open(outfile) as f:
|
||||
sizes = json.load(f)
|
||||
# No valid firmware .bin found, board should be absent
|
||||
assert sizes == {}
|
||||
|
||||
|
||||
def test_collect_sizes_ignores_non_mt_json():
|
||||
"""collect_sizes skips non .mt.json files."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
outfile = os.path.join(tmpdir, "sizes.json")
|
||||
# Write a valid manifest
|
||||
manifests = {"rak4631": make_manifest("rak4631", 500000)}
|
||||
write_manifests(tmpdir, manifests)
|
||||
# Write a decoy file
|
||||
with open(os.path.join(tmpdir, "readme.txt"), "w") as f:
|
||||
f.write("not a manifest")
|
||||
|
||||
rc, stdout, stderr = run_script("collect_sizes.py", [tmpdir, outfile])
|
||||
assert rc == 0
|
||||
with open(outfile) as f:
|
||||
sizes = json.load(f)
|
||||
assert list(sizes.keys()) == ["rak4631"]
|
||||
|
||||
|
||||
def test_size_report_no_baseline():
|
||||
"""size_report with no baselines shows sizes only."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
sizes_file = os.path.join(tmpdir, "new.json")
|
||||
with open(sizes_file, "w") as f:
|
||||
json.dump({"heltec-v3": 1000000, "rak4631": 500000}, f)
|
||||
|
||||
rc, stdout, stderr = run_script("size_report.py", [sizes_file])
|
||||
assert rc == 0, f"size_report failed: {stderr}"
|
||||
assert "2 targets" in stdout
|
||||
assert "no baseline available yet" in stdout
|
||||
assert "`heltec-v3`" in stdout
|
||||
assert "`rak4631`" in stdout
|
||||
|
||||
|
||||
def test_size_report_with_baseline():
|
||||
"""size_report shows deltas against a baseline."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
new_file = os.path.join(tmpdir, "new.json")
|
||||
old_file = os.path.join(tmpdir, "old.json")
|
||||
with open(new_file, "w") as f:
|
||||
json.dump({"heltec-v3": 1050000, "rak4631": 500000, "tbeam": 800000}, f)
|
||||
with open(old_file, "w") as f:
|
||||
json.dump({"heltec-v3": 1000000, "rak4631": 500000, "tbeam": 810000}, f)
|
||||
|
||||
rc, stdout, stderr = run_script(
|
||||
"size_report.py", [new_file, "--baseline", f"develop:{old_file}"]
|
||||
)
|
||||
assert rc == 0, f"size_report failed: {stderr}"
|
||||
assert "3 targets" in stdout
|
||||
assert "1 increased" in stdout
|
||||
assert "1 decreased" in stdout
|
||||
# heltec-v3 grew by 50000
|
||||
assert "📈" in stdout
|
||||
# tbeam shrank by 10000
|
||||
assert "📉" in stdout
|
||||
# rak4631 unchanged
|
||||
assert "vs `develop`" in stdout
|
||||
|
||||
|
||||
def test_size_report_multiple_baselines():
|
||||
"""size_report handles multiple baselines."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
new_file = os.path.join(tmpdir, "new.json")
|
||||
dev_file = os.path.join(tmpdir, "develop.json")
|
||||
master_file = os.path.join(tmpdir, "master.json")
|
||||
with open(new_file, "w") as f:
|
||||
json.dump({"board-a": 100000}, f)
|
||||
with open(dev_file, "w") as f:
|
||||
json.dump({"board-a": 95000}, f)
|
||||
with open(master_file, "w") as f:
|
||||
json.dump({"board-a": 90000}, f)
|
||||
|
||||
rc, stdout, stderr = run_script(
|
||||
"size_report.py",
|
||||
[new_file, "--baseline", f"develop:{dev_file}", "--baseline", f"master:{master_file}"],
|
||||
)
|
||||
assert rc == 0, f"size_report failed: {stderr}"
|
||||
assert "vs `develop`" in stdout
|
||||
assert "vs `master`" in stdout
|
||||
|
||||
|
||||
def test_size_report_new_target_no_baseline_entry():
|
||||
"""size_report handles targets not present in baseline (new boards)."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
new_file = os.path.join(tmpdir, "new.json")
|
||||
old_file = os.path.join(tmpdir, "old.json")
|
||||
with open(new_file, "w") as f:
|
||||
json.dump({"new-board": 300000, "existing": 500000}, f)
|
||||
with open(old_file, "w") as f:
|
||||
json.dump({"existing": 500000}, f)
|
||||
|
||||
rc, stdout, stderr = run_script(
|
||||
"size_report.py", [new_file, "--baseline", f"develop:{old_file}"]
|
||||
)
|
||||
assert rc == 0, f"size_report failed: {stderr}"
|
||||
assert "`new-board`" in stdout
|
||||
assert "no changes" in stdout # only existing is compared, delta=0
|
||||
|
||||
|
||||
def test_size_report_all_unchanged():
|
||||
"""size_report shows 'no changes' when all sizes match."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
sizes_file = os.path.join(tmpdir, "sizes.json")
|
||||
with open(sizes_file, "w") as f:
|
||||
json.dump({"board-a": 100000, "board-b": 200000}, f)
|
||||
|
||||
rc, stdout, stderr = run_script(
|
||||
"size_report.py", [sizes_file, "--baseline", f"develop:{sizes_file}"]
|
||||
)
|
||||
assert rc == 0, f"size_report failed: {stderr}"
|
||||
assert "no changes" in stdout
|
||||
|
||||
|
||||
def test_collect_sizes_bad_args():
|
||||
"""collect_sizes exits with error on wrong arg count."""
|
||||
rc, stdout, stderr = run_script("collect_sizes.py", [])
|
||||
assert rc == 1
|
||||
assert "Usage" in stderr
|
||||
|
||||
|
||||
def test_size_report_bad_baseline_format():
|
||||
"""size_report exits with error on malformed --baseline."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
sizes_file = os.path.join(tmpdir, "sizes.json")
|
||||
with open(sizes_file, "w") as f:
|
||||
json.dump({"x": 1}, f)
|
||||
|
||||
rc, stdout, stderr = run_script(
|
||||
"size_report.py", [sizes_file, "--baseline", "no-colon-here"]
|
||||
)
|
||||
assert rc == 1
|
||||
assert "LABEL:PATH" in stderr
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
tests = [v for k, v in globals().items() if k.startswith("test_")]
|
||||
passed = 0
|
||||
failed = 0
|
||||
for test in tests:
|
||||
try:
|
||||
test()
|
||||
print(f" PASS: {test.__name__}")
|
||||
passed += 1
|
||||
except AssertionError as e:
|
||||
print(f" FAIL: {test.__name__}: {e}")
|
||||
failed += 1
|
||||
except Exception as e:
|
||||
print(f" ERROR: {test.__name__}: {type(e).__name__}: {e}")
|
||||
failed += 1
|
||||
|
||||
print(f"\n{passed} passed, {failed} failed out of {passed + failed}")
|
||||
sys.exit(1 if failed else 0)
|
||||
@@ -7,7 +7,7 @@
|
||||
"extra_flags": [
|
||||
"-D CDEBYTE_EORA_S3",
|
||||
"-D ARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-D ARDUINO_USB_MODE=1",
|
||||
"-D ARDUINO_USB_MODE=0",
|
||||
"-D ARDUINO_RUNNING_CORE=1",
|
||||
"-D ARDUINO_EVENT_RUNNING_CORE=1",
|
||||
"-D BOARD_HAS_PSRAM"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1",
|
||||
"-DBOARD_HAS_PSRAM"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x239A", "0x4405"],
|
||||
["0x239A", "0x0029"],
|
||||
["0x239A", "0x002A"],
|
||||
["0x2886", "0x1667"]
|
||||
],
|
||||
"usb_product": "HT-n5262",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "heltec_mesh_node_t1",
|
||||
"variants_dir": "variants",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"onboard_tools": ["jlink"],
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Heltec Mesh Node T1",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://heltec.org",
|
||||
"vendor": "Heltec"
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"extra_flags": [
|
||||
"-DHELTEC_WIRELESS_TRACKER",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"build": {
|
||||
"cpu": "cortex-m33",
|
||||
"f_cpu": "128000000L",
|
||||
"mcu": "nrf54l15",
|
||||
"zephyr": {
|
||||
"variant": "nrf54l15dk/nrf54l15/cpuapp"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"default_tools": ["jlink"],
|
||||
"jlink_device": "nRF54L15_M33",
|
||||
"svd_path": "nrf54l15.svd"
|
||||
},
|
||||
"frameworks": ["zephyr"],
|
||||
"name": "Nordic nRF54L15-DK (PCA10156)",
|
||||
"upload": {
|
||||
"maximum_ram_size": 262144,
|
||||
"maximum_size": 1572864,
|
||||
"protocol": "jlink",
|
||||
"protocols": ["jlink"]
|
||||
},
|
||||
"url": "https://www.nordicsemi.com/Products/nRF54L15",
|
||||
"vendor": "Nordic Semiconductor"
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=0"
|
||||
],
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DLILYGO_TBEAM_1W",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DLILYGO_TBEAM_S3_CORE",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"extra_flags": [
|
||||
"-DLILYGO_T3S3_V1",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1",
|
||||
"-DBOARD_HAS_PSRAM"
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DUNPHONE_SPIN=9",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
|
||||
Vendored
-1
@@ -14,7 +14,6 @@ Build-Depends: debhelper-compat (= 13),
|
||||
g++,
|
||||
pkg-config,
|
||||
libyaml-cpp-dev,
|
||||
libjsoncpp-dev,
|
||||
libgpiod-dev,
|
||||
libbluetooth-dev,
|
||||
libusb-1.0-0-dev,
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x5000,
|
||||
otadata, data, ota, 0xe000, 0x2000,
|
||||
app0, app, ota_0, 0x10000, 0x640000,
|
||||
app1, app, ota_1, 0x650000,0x640000,
|
||||
spiffs, data, spiffs, 0xc90000,0x360000,
|
||||
coredump, data, coredump,0xFF0000,0x10000,
|
||||
|
@@ -1,7 +0,0 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x5000,
|
||||
otadata, data, ota, 0xe000, 0x2000,
|
||||
app0, app, ota_0, 0x10000, 0x330000,
|
||||
app1, app, ota_1, 0x340000,0x330000,
|
||||
spiffs, data, spiffs, 0x670000,0x180000,
|
||||
coredump, data, coredump,0x7F0000,0x10000,
|
||||
|
@@ -70,6 +70,30 @@ def esp32_create_combined_bin(source, target, env):
|
||||
|
||||
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin)
|
||||
|
||||
# Enable Newlib Nano formatting to save space
|
||||
# ...but allow printf float support (compromise)
|
||||
env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"])
|
||||
esp32_kind = env.GetProjectOption("custom_esp32_kind")
|
||||
if esp32_kind == "esp32":
|
||||
# Free up some IRAM by removing auxiliary SPI flash chip drivers.
|
||||
# Wrapped stub symbols are defined in src/platform/esp32/iram-quirk.c.
|
||||
env.Append(
|
||||
LINKFLAGS=[
|
||||
"-Wl,--wrap=esp_flash_chip_gd",
|
||||
"-Wl,--wrap=esp_flash_chip_issi",
|
||||
"-Wl,--wrap=esp_flash_chip_winbond",
|
||||
]
|
||||
)
|
||||
else:
|
||||
# For newer ESP32 targets, using newlib nano works better. Skip on
|
||||
# variants that explicitly opt out — the IDF 5.1 framework override the
|
||||
# HaLow variant uses already includes nano.specs, so re-adding it triggers
|
||||
# a duplicate spec definition error at link time.
|
||||
cppdefines = env.get("CPPDEFINES", [])
|
||||
if not any(
|
||||
(isinstance(d, str) and d == "MESHTASTIC_SKIP_NANO_SPECS")
|
||||
or (
|
||||
isinstance(d, (list, tuple))
|
||||
and len(d) > 0
|
||||
and d[0] == "MESHTASTIC_SKIP_NANO_SPECS"
|
||||
)
|
||||
for d in cppdefines
|
||||
):
|
||||
env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"])
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# trunk-ignore-all(ruff/F821)
|
||||
# trunk-ignore-all(flake8/F821): For SConstruct imports
|
||||
|
||||
# force linker response file instead of command line arguments
|
||||
|
||||
Import("env")
|
||||
|
||||
|
||||
def wrap_with_tempfile(command_key):
|
||||
command = env.get(command_key)
|
||||
if not command or not isinstance(command, str):
|
||||
return
|
||||
if "TEMPFILE(" in command:
|
||||
return
|
||||
env.Replace(**{command_key: "${TEMPFILE('%s')}" % command})
|
||||
|
||||
|
||||
# Force SCons to spill long commands into response files on this target.
|
||||
env.Replace(MAXLINELENGTH=8192)
|
||||
|
||||
for key in ("LINKCOM", "CXXLINKCOM", "SHLINKCOM", "SHCXXLINKCOM"):
|
||||
wrap_with_tempfile(key)
|
||||
@@ -1,140 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# trunk-ignore-all(ruff/F821)
|
||||
# trunk-ignore-all(flake8/F821): For SConstruct imports
|
||||
#
|
||||
# post:extra_scripts/nrf54l15_linker.py
|
||||
#
|
||||
# Fix for Zephyr two-pass link on nRF54L15:
|
||||
# platformio-build.py registers env.Depends("$PROG_PATH", final_ld_script) but
|
||||
# the SCons dependency chain is broken (final_ld_script Command never runs).
|
||||
# This script adds a PreAction on the final firmware binary that runs the gcc
|
||||
# preprocessing command directly (extracted from build.ninja) to generate
|
||||
# zephyr/linker.cmd before the link step.
|
||||
#
|
||||
# PlatformIO bundles an old Ninja that can't handle multi-output depslog rules,
|
||||
# so we parse the COMMAND line from build.ninja and run just the gcc -E part,
|
||||
# skipping the cmake_transform_depfile step (only needed for Ninja deps tracking).
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
Import("env")
|
||||
|
||||
if env.get("PIOENV") != "nrf54l15dk":
|
||||
pass # Only for the nrf54l15dk environment
|
||||
else:
|
||||
|
||||
def _extract_gcc_command(ninja_build):
|
||||
"""Parse build.ninja to find the gcc -E command that generates linker.cmd.
|
||||
|
||||
The rule format depends on the host:
|
||||
Windows (CMake's RunCMake wraps every command):
|
||||
COMMAND = cmd.exe /C "cd /D DIR && arm-none-eabi-gcc.exe ... -o linker.cmd && cmake.exe -E cmake_transform_depfile ..."
|
||||
POSIX (Linux/macOS — no wrapper):
|
||||
COMMAND = cd DIR && arm-none-eabi-gcc ... -o linker.cmd && cmake -E cmake_transform_depfile ...
|
||||
|
||||
Returns (gcc_cmd_string, cwd_path) or raises RuntimeError.
|
||||
"""
|
||||
in_rule = False
|
||||
with open(ninja_build, "r", encoding="utf-8", errors="replace") as f:
|
||||
for line in f:
|
||||
# Detect start of the linker.cmd custom command rule
|
||||
if not in_rule:
|
||||
if "build zephyr/linker.cmd" in line and "CUSTOM_COMMAND" in line:
|
||||
in_rule = True
|
||||
continue
|
||||
|
||||
stripped = line.strip()
|
||||
if not stripped.startswith("COMMAND = "):
|
||||
continue
|
||||
|
||||
command_val = stripped[len("COMMAND = ") :]
|
||||
|
||||
# On Windows the value is wrapped in `cmd.exe /C "..."` — strip
|
||||
# the wrapper. On POSIX hosts the inner sequence is the value
|
||||
# itself (no quoting layer).
|
||||
m = re.search(r'/C\s+"(.*)"\s*$', command_val)
|
||||
inner = m.group(1) if m else command_val
|
||||
parts = inner.split(" && ")
|
||||
|
||||
cwd = None
|
||||
gcc_cmd = None
|
||||
for part in parts:
|
||||
part = part.strip()
|
||||
if part.startswith("cd /D "): # Windows form
|
||||
cwd = part[len("cd /D ") :]
|
||||
elif part.startswith("cd "): # POSIX form
|
||||
cwd = part[len("cd ") :]
|
||||
elif "arm-none-eabi-gcc" in part:
|
||||
gcc_cmd = part
|
||||
|
||||
if not gcc_cmd:
|
||||
raise RuntimeError(
|
||||
"nRF54L15 linker fix: arm-none-eabi-gcc command not found in:\n%s"
|
||||
% inner[:400]
|
||||
)
|
||||
|
||||
return gcc_cmd, cwd
|
||||
|
||||
raise RuntimeError(
|
||||
"nRF54L15 linker fix: 'build zephyr/linker.cmd' rule not found in build.ninja"
|
||||
)
|
||||
|
||||
def _generate_linker_cmd(target, source, env):
|
||||
"""Generate zephyr/linker.cmd via direct gcc invocation before the final link."""
|
||||
build_dir = env.subst("$BUILD_DIR")
|
||||
zephyr_dir = os.path.join(build_dir, "zephyr")
|
||||
linker_cmd = os.path.join(zephyr_dir, "linker.cmd")
|
||||
|
||||
if os.path.exists(linker_cmd):
|
||||
return # Already present — nothing to do
|
||||
|
||||
ninja_build = os.path.join(build_dir, "build.ninja")
|
||||
if not os.path.exists(ninja_build):
|
||||
raise RuntimeError(
|
||||
"nRF54L15 linker fix: build.ninja not found at %s\n"
|
||||
"Run a full build first so CMake generates the Ninja files."
|
||||
% ninja_build
|
||||
)
|
||||
|
||||
gcc_cmd, cwd = _extract_gcc_command(ninja_build)
|
||||
run_cwd = cwd if cwd else zephyr_dir
|
||||
|
||||
print(
|
||||
"==> nRF54L15: Generating zephyr/linker.cmd (LINKER_ZEPHYR_FINAL) via GCC"
|
||||
)
|
||||
# gcc_cmd comes verbatim from our own build.ninja (never user input) and
|
||||
# contains Windows-style paths with spaces that cannot be safely argv-split
|
||||
# with shlex, so we run it via the platform shell. nosec/nosemgrep below
|
||||
# acknowledge this deliberate, scoped use of shell=True.
|
||||
result = subprocess.run( # nosec B602
|
||||
gcc_cmd,
|
||||
shell=True, # nosemgrep: python.lang.security.audit.subprocess-shell-true.subprocess-shell-true
|
||||
cwd=run_cwd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print("GCC stdout:", result.stdout[:2000])
|
||||
print("GCC stderr:", result.stderr[:2000])
|
||||
raise RuntimeError(
|
||||
"nRF54L15 linker fix: GCC failed to generate linker.cmd (rc=%d)"
|
||||
% result.returncode
|
||||
)
|
||||
if not os.path.exists(linker_cmd):
|
||||
raise RuntimeError(
|
||||
"nRF54L15 linker fix: GCC returned 0 but linker.cmd was not created at %s"
|
||||
% linker_cmd
|
||||
)
|
||||
print("==> linker.cmd generated successfully")
|
||||
|
||||
# Use PIOMAINPROG (set by ZephyrBuildProgram) to get the exact SCons node
|
||||
prog = env.get("PIOMAINPROG")
|
||||
if prog:
|
||||
env.AddPreAction(prog, _generate_linker_cmd)
|
||||
else:
|
||||
print(
|
||||
"[nrf54l15_linker] WARNING: PIOMAINPROG not set, falling back to $PROG_PATH"
|
||||
)
|
||||
env.AddPreAction(env.subst("$PROG_PATH"), _generate_linker_cmd)
|
||||
@@ -1,39 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# trunk-ignore-all(ruff/F821)
|
||||
# trunk-ignore-all(flake8/F821): For SConstruct imports
|
||||
import re
|
||||
|
||||
Import("env")
|
||||
|
||||
# The ZPS module scans BLE through the NimBLE *host* API ble_gap_disc(), which the
|
||||
# ESP-IDF only compiles when CONFIG_BT_NIMBLE_ROLE_OBSERVER=y. The base esp32 config
|
||||
# disables the observer role to save flash (see variants/esp32/esp32-common.ini), so
|
||||
# re-enable it ONLY when the ZPS module is actually built. This keeps a single flag
|
||||
# (-DMESHTASTIC_EXCLUDE_ZPS) driving both the C++ module and the IDF sdkconfig.
|
||||
#
|
||||
# This runs as a pre: script, before the arduino framework builder reads
|
||||
# custom_sdkconfig (PlatformIO core runs pre-scripts before $BUILD_SCRIPT). Appending
|
||||
# to custom_sdkconfig changes its hash and triggers the framework's IDF rebuild path,
|
||||
# but only for ZPS-enabled envs; for every normal build this is a no-op.
|
||||
flags = env.GetProjectOption("build_flags", "")
|
||||
if isinstance(flags, (list, tuple)):
|
||||
flags = " ".join(flags)
|
||||
|
||||
# Mirror the C semantics of `#if !MESHTASTIC_EXCLUDE_ZPS`:
|
||||
# flag absent -> module enabled
|
||||
# -DMESHTASTIC_EXCLUDE_ZPS=0 -> module enabled
|
||||
# -DMESHTASTIC_EXCLUDE_ZPS or =1 (or anything else) -> module excluded
|
||||
match = re.search(r"\bMESHTASTIC_EXCLUDE_ZPS\b(?:=(\S+))?", flags)
|
||||
zps_enabled = (match is None) or (match.group(1) == "0")
|
||||
|
||||
section = "env:" + env["PIOENV"]
|
||||
config = env.GetProjectConfig()
|
||||
if zps_enabled and config.has_option(section, "custom_sdkconfig"):
|
||||
sdkconfig = env.GetProjectOption("custom_sdkconfig")
|
||||
if "CONFIG_BT_NIMBLE_ROLE_OBSERVER" not in sdkconfig:
|
||||
config.set(
|
||||
section,
|
||||
"custom_sdkconfig",
|
||||
sdkconfig.rstrip("\n") + "\n CONFIG_BT_NIMBLE_ROLE_OBSERVER=y\n",
|
||||
)
|
||||
print("[ZPS] module enabled -> CONFIG_BT_NIMBLE_ROLE_OBSERVER=y (IDF rebuild)")
|
||||
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
PlatformIO doesn't natively link .o files vendored inside a library directory.
|
||||
The Morse Micro SDK ships the chip firmware (mm6108.mbin.o) and per-region BCF
|
||||
(bcf_mf08651_us.mbin.o) as pre-built object files containing data sections.
|
||||
This script appends them to LINKFLAGS so they land in the final ELF.
|
||||
|
||||
US region only for now — when we add EU/JP/KR variants, gate the BCF here on a
|
||||
build flag and pick the matching .o file.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
Import("env", "projenv")
|
||||
|
||||
LIB_DIR = os.path.join(env.subst("$PROJECT_DIR"), "lib", "MorseWlan")
|
||||
|
||||
mbin_objects = [
|
||||
os.path.join(LIB_DIR, "src", "mm6108.mbin.o"),
|
||||
os.path.join(LIB_DIR, "src", "bcf_mf08651_us.mbin.o"),
|
||||
]
|
||||
|
||||
# Only add objects that actually exist; missing ones surface as link errors,
|
||||
# not silent corruption.
|
||||
for obj in mbin_objects:
|
||||
if not os.path.isfile(obj):
|
||||
print("warning: mm-iot-esp32 blob missing: %s" % obj)
|
||||
|
||||
env.Append(LINKFLAGS=mbin_objects)
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2023 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup MBIN Morse BINary Loader API
|
||||
*
|
||||
* This file defines the structure of the @c MBIN file.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef PACKED
|
||||
/** Macro for the compiler packed attribute */
|
||||
#define PACKED __attribute__((packed))
|
||||
#endif
|
||||
|
||||
/** Enumeration of TLV field types */
|
||||
enum mbin_tlv_types {
|
||||
FIELD_TYPE_FW_TLV_BCF_ADDR = 0x0001,
|
||||
FIELD_TYPE_MAGIC = 0x8000,
|
||||
FIELD_TYPE_FW_SEGMENT = 0x8001,
|
||||
FIELD_TYPE_FW_SEGMENT_DEFLATED = 0x8002,
|
||||
FIELD_TYPE_BCF_BOARD_CONFIG = 0x8100,
|
||||
FIELD_TYPE_BCF_REGDOM = 0x8101,
|
||||
FIELD_TYPE_BCF_BOARD_DESC = 0x8102,
|
||||
FIELD_TYPE_BCF_BUILD_VER = 0x8103,
|
||||
FIELD_TYPE_SW_SEGMENT = 0x8201,
|
||||
FIELD_TYPE_SW_SEGMENT_DEFLATED = 0x8202,
|
||||
FIELD_TYPE_EOF = 0x8f00,
|
||||
FIELD_TYPE_EOF_WITH_SIGNATURE = 0x8f01,
|
||||
};
|
||||
|
||||
/** TLV header data structure. */
|
||||
struct PACKED mbin_tlv_hdr {
|
||||
/** Type (see mbin_tlv_types). */
|
||||
uint16_t type;
|
||||
/** Length of payload (excludes header). */
|
||||
uint16_t len;
|
||||
};
|
||||
|
||||
/** Data header in a FIELD_TYPE_XX_SEGMENT field. */
|
||||
struct PACKED mbin_segment_hdr {
|
||||
/** Destination base address at which the data should be loaded. */
|
||||
uint32_t base_address;
|
||||
};
|
||||
|
||||
/** Data header in a FIELD_TYPE_XX_SEGMENT_DEFLATED field. */
|
||||
struct PACKED mbin_deflated_segment_hdr {
|
||||
/** Destination base address at which the data should be loaded. */
|
||||
uint32_t base_address;
|
||||
/** Size of deflated data, infer size of compressed data from TLV length */
|
||||
uint16_t chunk_size;
|
||||
/** ZLib header */
|
||||
uint8_t zlib_header[2];
|
||||
};
|
||||
|
||||
/** Data header in a @c FIELD_TYPE_BCF_REGDOM field. */
|
||||
struct PACKED mbin_regdom_hdr {
|
||||
/** Country code that this @c regdom applies to. */
|
||||
uint8_t country_code[2];
|
||||
/** Reserved */
|
||||
uint16_t reserved;
|
||||
};
|
||||
|
||||
/** Expected value of the magic field for a SW image @c MMSW. */
|
||||
#define MBIN_SW_MAGIC_NUMBER (0x57534d4d)
|
||||
/** Expected value of the magic field for a firmware image @c MMFW. */
|
||||
#define MBIN_FW_MAGIC_NUMBER (0x57464d4d)
|
||||
/** Expected value of the magic field for a BCF @c MMBC. */
|
||||
#define MBIN_BCF_MAGIC_NUMBER (0x43424d4d)
|
||||
|
||||
/** @} */
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2024 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file should be included from the application mbedtls_config.h file to ensure that
|
||||
* the mbedTLS features necessary for morselib functionality are enabled.
|
||||
*
|
||||
* It is recommended to include this file at the _end_ of the application mbedtls_config.h file
|
||||
* to avoid redefinition of macros.
|
||||
*/
|
||||
|
||||
/* Cipher modes */
|
||||
#ifndef MBEDTLS_CIPHER_MODE_CBC
|
||||
#define MBEDTLS_CIPHER_MODE_CBC
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_CIPHER_MODE_CTR
|
||||
#define MBEDTLS_CIPHER_MODE_CTR
|
||||
#endif
|
||||
|
||||
/* EC curves */
|
||||
#ifndef MBEDTLS_ECP_DP_SECP256R1_ENABLED
|
||||
#define MBEDTLS_ECP_DP_SECP256R1_ENABLED
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_ECP_DP_SECP384R1_ENABLED
|
||||
#define MBEDTLS_ECP_DP_SECP384R1_ENABLED
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_ECP_DP_SECP521R1_ENABLED
|
||||
#define MBEDTLS_ECP_DP_SECP521R1_ENABLED
|
||||
#endif
|
||||
|
||||
/* Features */
|
||||
#ifndef MBEDTLS_AES_C
|
||||
#define MBEDTLS_AES_C
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_ASN1_PARSE_C
|
||||
#define MBEDTLS_ASN1_PARSE_C
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_ASN1_WRITE_C
|
||||
#define MBEDTLS_ASN1_WRITE_C
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_BIGNUM_C
|
||||
#define MBEDTLS_BIGNUM_C
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_CIPHER_C
|
||||
#define MBEDTLS_CIPHER_C
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_CMAC_C
|
||||
#define MBEDTLS_CMAC_C
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_CTR_DRBG_C
|
||||
#define MBEDTLS_CTR_DRBG_C
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_ECDH_C
|
||||
#define MBEDTLS_ECDH_C
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_ECP_C
|
||||
#define MBEDTLS_ECP_C
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_ENTROPY_C
|
||||
#define MBEDTLS_ENTROPY_C
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_MD_C
|
||||
#define MBEDTLS_MD_C
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_NIST_KW_C
|
||||
#define MBEDTLS_NIST_KW_C
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_OID_C
|
||||
#define MBEDTLS_OID_C
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_PK_C
|
||||
#define MBEDTLS_PK_C
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_PK_PARSE_C
|
||||
#define MBEDTLS_PK_PARSE_C
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_PK_WRITE_C
|
||||
#define MBEDTLS_PK_WRITE_C
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_PKCS5_C
|
||||
#define MBEDTLS_PKCS5_C
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_SHA1_C
|
||||
#define MBEDTLS_SHA1_C
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_SHA224_C
|
||||
#define MBEDTLS_SHA224_C
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_SHA256_C
|
||||
#define MBEDTLS_SHA256_C
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_SHA384_C
|
||||
#define MBEDTLS_SHA384_C
|
||||
#endif
|
||||
|
||||
#ifndef MBEDTLS_SHA512_C
|
||||
#define MBEDTLS_SHA512_C
|
||||
#endif
|
||||
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
* Copyright 2021-2023 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup MMHAL Morse Micro Hardware Abstraction Layer (mmhal) API
|
||||
*
|
||||
* This API provides abstraction from the underlying hardware/BSP.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "mmhal_flash.h"
|
||||
#include "mmhal_wlan.h"
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Initialization before RTOS scheduler starts. */
|
||||
void mmhal_early_init(void);
|
||||
|
||||
/** Initialization after RTOS scheduler started. */
|
||||
void mmhal_init(void);
|
||||
|
||||
/** Enumeration of ISR states (i.e., whether in ISR or not). */
|
||||
enum mmhal_isr_state {
|
||||
MMHAL_NOT_IN_ISR, /**< The function was not executed from ISR context. */
|
||||
MMHAL_IN_ISR, /**< The function was executed from ISR context. */
|
||||
MMHAL_ISR_STATE_UNKNOWN, /**< The HAL does not support checking ISR state. */
|
||||
};
|
||||
|
||||
/**
|
||||
* Enumeration for different LED's on the board.
|
||||
*
|
||||
* @note Some of these LED's may not be available on all boards and some of these values
|
||||
* may refer to the same LED.
|
||||
*/
|
||||
enum mmhal_led_id { LED_RED, LED_GREEN, LED_BLUE, LED_WHITE };
|
||||
|
||||
/** Enumeration of MCU sleep state. */
|
||||
enum mmhal_sleep_state {
|
||||
MMHAL_SLEEP_DISABLED, /**< Disable MCU sleep. */
|
||||
MMHAL_SLEEP_SHALLOW, /**< MCU to enter shallow sleep. */
|
||||
MMHAL_SLEEP_DEEP, /**< MCU can enter deep sleep. */
|
||||
};
|
||||
|
||||
/** A value of 0 turns OFF an LED */
|
||||
#define LED_OFF 0
|
||||
|
||||
/**
|
||||
* A value of 255 turns an LED ON fully.
|
||||
*
|
||||
* Some boards support varying an LED's brightness. For these boards a value between 1 and 255
|
||||
* will result in proportionately varying levels of brightness. LED's that do not have a
|
||||
* brightness control feature will just turn ON fully for any non zero value.
|
||||
*/
|
||||
#define LED_ON 255
|
||||
|
||||
/**
|
||||
* Get the current ISR state (i.e., whether in ISR or not).
|
||||
*
|
||||
* @returns the current ISR state, or @c MMHAL_ISR_STATE_UNKNOWN if the HAL does not support
|
||||
* checking ISR state.
|
||||
*/
|
||||
enum mmhal_isr_state mmhal_get_isr_state(void);
|
||||
|
||||
/**
|
||||
* Write to the debug log.
|
||||
*
|
||||
* It is assumed the caller will have mechanisms in place to prevent concurrent access.
|
||||
*
|
||||
* @param data Buffer containing data to write.
|
||||
* @param len Length of data in buffer.
|
||||
*/
|
||||
void mmhal_log_write(const uint8_t *data, size_t len);
|
||||
|
||||
/**
|
||||
* Flush the debug log before returning.
|
||||
*
|
||||
* @warning Implementations of this function must support being invoked with interrupts disabled.
|
||||
*/
|
||||
void mmhal_log_flush(void);
|
||||
|
||||
/**
|
||||
* Generate a random 32 bit integer within the given range.
|
||||
*
|
||||
* @param min Minimum value (inclusive).
|
||||
* @param max Maximum value (inclusive).
|
||||
*
|
||||
* @returns a randomly generated integer (min <= i <= max).
|
||||
*/
|
||||
uint32_t mmhal_random_u32(uint32_t min, uint32_t max);
|
||||
|
||||
/** Reset the microcontroller. */
|
||||
void mmhal_reset(void);
|
||||
|
||||
/**
|
||||
* Set the specified LED to the requested level. Do nothing if the requested LED does not exist.
|
||||
*
|
||||
* @param led The LED to set, if the platform supports it. See @ref mmhal_led_id
|
||||
* @param level The level to set it to. 0 means OFF and non-zero means ON. If the platform supports
|
||||
* brightness levels then 255 is full. Defines @ref LED_ON and @ref LED_OFF are
|
||||
* provided for ease of use.
|
||||
*/
|
||||
void mmhal_set_led(uint8_t led, uint8_t level);
|
||||
|
||||
/**
|
||||
* Set the error LED to the requested state.
|
||||
*
|
||||
* @note This function is called by the bootloader and so needs to do all the initialization
|
||||
* required to set the LED's as the bootloader does not use the regular BSP initialization
|
||||
* located in main.c for configuring @c GPIO's and setting clock gates as required.
|
||||
*
|
||||
* @param state Set to true if the LED needs to be turned on,
|
||||
* or false if the led needs to be turned off.
|
||||
*/
|
||||
void mmhal_set_error_led(bool state);
|
||||
|
||||
/**
|
||||
* Enumeration for buttons on the board.
|
||||
*
|
||||
* @note The support for each button is platform dependent.
|
||||
*/
|
||||
enum mmhal_button_id { BUTTON_ID_USER0 };
|
||||
|
||||
/**
|
||||
* Enumeration for button states
|
||||
*/
|
||||
enum mmhal_button_state { BUTTON_RELEASED, BUTTON_PRESSED };
|
||||
|
||||
/** Button state callback function prototype. */
|
||||
typedef void (*mmhal_button_state_cb_t)(enum mmhal_button_id button_id, enum mmhal_button_state button_state);
|
||||
|
||||
/**
|
||||
* Registers a callback handler for button state changes.
|
||||
*
|
||||
* @note The callback will be executed in an Interrupt Service Routine context
|
||||
*
|
||||
* @param button_id The button whose state should be reported to the callback
|
||||
* @param button_state_cb The function to call on button state change, or NULL to disable.
|
||||
* @returns True if the callback is registered successfully, False if not supported
|
||||
*/
|
||||
bool mmhal_set_button_callback(enum mmhal_button_id button_id, mmhal_button_state_cb_t button_state_cb);
|
||||
|
||||
/**
|
||||
* Returns the registered callback handler for button state changes.
|
||||
*
|
||||
* @param button_id The button whose callback should be returned
|
||||
* @returns The registered callback or NULL if no callback registered
|
||||
*/
|
||||
mmhal_button_state_cb_t mmhal_get_button_callback(enum mmhal_button_id button_id);
|
||||
|
||||
/**
|
||||
* Reads the state of the specified button.
|
||||
*
|
||||
* @param button_id The button state to read
|
||||
* @returns The current button state, or BUTTON_RELEASED if not supported
|
||||
*/
|
||||
enum mmhal_button_state mmhal_get_button(enum mmhal_button_id button_id);
|
||||
|
||||
/**
|
||||
* Reads information that can be used to identify the hardware platform, such as
|
||||
* hardware ID and version number, in the form of a user readable string.
|
||||
*
|
||||
* This function attempts to detect the correct hardware and version.
|
||||
* The actual means of detecting the correct hardware and version will vary from
|
||||
* implementation to implementation and may use means such as identification
|
||||
* information stored in EEPROM or devices detected on GPIO, SPI or I2C interfaces.
|
||||
* Returns false if the hardware could not be identified correctly.
|
||||
*
|
||||
* @param version_buffer The pre-allocated buffer to return the hardware ID and version in.
|
||||
* @param version_buffer_length The length of the pre-allocated buffer.
|
||||
* @returns True if the hardware was correctly identified and returned.
|
||||
*/
|
||||
bool mmhal_get_hardware_version(char *version_buffer, size_t version_buffer_length);
|
||||
|
||||
/**
|
||||
* Macro to simplify debug pin mask/value definition.
|
||||
*
|
||||
* @param _pin_num The pin number to set in the mask. Must be 0-31 (inclusive).
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* mmhal_set_debug_pins(MMHAL_DEBUG_PIN(0), MMHAL_DEBUG_PIN(0));
|
||||
*/
|
||||
#define MMHAL_DEBUG_PIN(_pin_num) (1ul << (_pin_num))
|
||||
|
||||
/** Bit mask with all debug pins selected. */
|
||||
#define MMHAL_ALL_DEBUG_PINS (UINT32_MAX)
|
||||
|
||||
/**
|
||||
* Set the value one or more debug pins.
|
||||
*
|
||||
* Each platform can define up to 32 GPIOs for application use. If a GPIO is not supported
|
||||
* by a platform then attempts to set it will be silently ignored. These GPIOs are intended
|
||||
* for debug/test purposes.
|
||||
*
|
||||
* @param mask Mask of GPIOs to modify. Each bit in this mask that is set will result in
|
||||
* the corresponding GPIO being being set to the corresponding value given in
|
||||
* @p values.
|
||||
* @param values Bit field, where each bit corresponds to a GPIO, specifying the direction
|
||||
* to drive each GPIO with 1 meaning drive high and 0 meaning drive low.
|
||||
* Only GPIOs with the corresponding bit set in @p mask will be modified.
|
||||
*
|
||||
* @sa MM_DEBUG_PIN_MASK
|
||||
*/
|
||||
void mmhal_set_debug_pins(uint32_t mask, uint32_t values);
|
||||
|
||||
/**
|
||||
* Returns the time of day as set in the RTC.
|
||||
* Time is in UTC.
|
||||
*
|
||||
* @return Epoch time (seconds since 1 Jan 1970) or 0 on failure.
|
||||
*/
|
||||
time_t mmhal_get_time();
|
||||
|
||||
/**
|
||||
* Sets the RTC to the specified time in UTC.
|
||||
*
|
||||
* @note While Unix epoch time supports years from 1970, most Real Time Clock
|
||||
* chips support years from 2000 only as they store the year as years
|
||||
* from 2000. So do not attempt to set any years below 2000 as this could cause
|
||||
* the year to wrap around to an unreasonably high value. Definitely do not do:
|
||||
* @code
|
||||
* mmhal_set_time(0);
|
||||
* @endcode
|
||||
*
|
||||
* @param epoch Time in Unix epoch time (seconds since 1 Jan 1970).
|
||||
*/
|
||||
void mmhal_set_time(time_t epoch);
|
||||
|
||||
/**
|
||||
* Function to prepare MCU to enter sleep.
|
||||
* This will halt the timer that generates the RTOS tick.
|
||||
*
|
||||
* @param expected_idle_time_ms Expected time to sleep in milliseconds.
|
||||
*
|
||||
* @returns the type of sleep permitted by the current system state.
|
||||
*/
|
||||
enum mmhal_sleep_state mmhal_sleep_prepare(uint32_t expected_idle_time_ms);
|
||||
|
||||
/**
|
||||
* Function to enter MCU sleep.
|
||||
*
|
||||
* @param sleep_state Sleep state to enter into.
|
||||
* @param expected_idle_time_ms Expected time to sleep in milliseconds.
|
||||
*
|
||||
* @returns Elapsed sleep time in milliseconds.
|
||||
*/
|
||||
uint32_t mmhal_sleep(enum mmhal_sleep_state sleep_state, uint32_t expected_idle_time_ms);
|
||||
|
||||
/**
|
||||
* Function to abort the MCU sleep state.
|
||||
*
|
||||
* @note This must only be invoked after @ref mmhal_sleep_prepare()
|
||||
* and before @ref mmhal_sleep().
|
||||
*
|
||||
* @param sleep_state Sleep state to abort.
|
||||
*/
|
||||
void mmhal_sleep_abort(enum mmhal_sleep_state sleep_state);
|
||||
|
||||
/**
|
||||
* Function to cleanup on exit from the MCU sleep state.
|
||||
*/
|
||||
void mmhal_sleep_cleanup(void);
|
||||
|
||||
/** Enumeration of veto_id ranges for use with @ref mmhal_set_deep_sleep_veto() and
|
||||
* @ref mmhal_clear_deep_sleep_veto(). */
|
||||
enum mmhal_veto_id {
|
||||
/** Start of deep sleep veto ID range that is available for application use. */
|
||||
MMHAL_VETO_ID_APP_MIN = 0,
|
||||
/** End of deep sleep veto ID range that is available for application use. */
|
||||
MMHAL_VETO_ID_APP_MAX = 7,
|
||||
/** Start of deep sleep veto ID range that is available for HAL use. */
|
||||
MMHAL_VETO_ID_HAL_MIN = 8,
|
||||
/** End of deep sleep veto ID range that is available for HAL use. */
|
||||
MMHAL_VETO_ID_HAL_MAX = 15,
|
||||
/** Start of deep sleep veto ID range that is allocated for morselib use. Note that this
|
||||
* must not be changed as it is built into morselib. */
|
||||
MMHAL_VETO_ID_MORSELIB_MIN = 16,
|
||||
/** End of deep sleep veto ID range that is allocated for morselib use. Note that this must not
|
||||
* be changed as it is built into morselib. */
|
||||
MMHAL_VETO_ID_MORSELIB_MAX = 19,
|
||||
/** Deep sleep veto ID for data-link subsystem. */
|
||||
MMHAL_VETO_ID_DATALINK = 20,
|
||||
/** Deep sleep veto ID allocated to @ref MMCONFIG. */
|
||||
MMHAL_VETO_ID_MMCONFIG = 21,
|
||||
/** Start of deep sleep veto ID range reserved for future use. */
|
||||
MMHAL_VETO_ID_RESERVED_MIN = 22,
|
||||
/** End of deep sleep veto ID range reserved for future use. */
|
||||
MMHAL_VETO_ID_RESERVED_MAX = 31,
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets a deep sleep veto that will prevent the device from entering deep sleep. The device
|
||||
* will not enter deep sleep until there are no vetoes remaining. This veto can be cleared
|
||||
* by a call to @ref mmhal_clear_deep_sleep_veto() with the same veto_id.
|
||||
*
|
||||
* Up to 32 vetoes are supported (ID numbers 0-31). Each veto should be used exclusively by
|
||||
* a given aspect of the system (e.g., to prevent deep sleep when a DMA transfer is in progress,
|
||||
* or to prevent deep sleep when there is log data buffered for transmit, etc.).
|
||||
*
|
||||
* @param veto_id The veto identifier. Valid values are 0-31, and these are split up into ranges
|
||||
* for use by different subsystems -- see @ref mmhal_veto_id.
|
||||
*/
|
||||
void mmhal_set_deep_sleep_veto(uint8_t veto_id);
|
||||
|
||||
/**
|
||||
* Clears a deep sleep veto that was preventing the device from entering deep sleep (see
|
||||
* @ref mmhal_set_deep_sleep_veto()). If the given veto was not already set then this has
|
||||
* no effect.
|
||||
*
|
||||
* @param veto_id The veto identifier. Valid values are 0-31, and these are split up into ranges
|
||||
* for use by different subsystems -- see @ref mmhal_veto_id.
|
||||
*/
|
||||
void mmhal_clear_deep_sleep_veto(uint8_t veto_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2021-2023 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup MMHAL Morse Micro Flash Hardware Abstraction Layer (mmhal_flash) API
|
||||
*
|
||||
* This API provides abstraction from the underlying flash hardware/.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This is the value erased flash bytes are set to. This shall be @c 0xFF as this is the
|
||||
* value that hardware flash erases to.
|
||||
*/
|
||||
#define MMHAL_FLASH_ERASE_VALUE 0xFF
|
||||
|
||||
/** LittleFS configuration structure. Include @c lfs.h for definition. */
|
||||
struct lfs_config;
|
||||
|
||||
/**
|
||||
* Flash partition configuration structure
|
||||
*
|
||||
* This should be initialized using @c MMHAL_FLASH_PARTITION_CONFIG_DEFAULT.
|
||||
* For example:
|
||||
*
|
||||
* @code{.c}
|
||||
* struct mmhal_flash_partition_config partition = MMHAL_FLASH_PARTITION_CONFIG_DEFAULT;
|
||||
* @endcode
|
||||
*/
|
||||
struct mmhal_flash_partition_config {
|
||||
/** The start address of the partition, this may be a physical address
|
||||
* or a relative address depending on implementation
|
||||
*/
|
||||
uint32_t partition_start;
|
||||
|
||||
/** The size of the partition */
|
||||
uint32_t partition_size;
|
||||
|
||||
/**
|
||||
* If true, then the partition is not memory mapped and cannot be directly accessed
|
||||
* at the physical @c partition_start address
|
||||
*/
|
||||
bool not_memory_mapped;
|
||||
};
|
||||
|
||||
/** Initial values for @ref mmhal_flash_partition_config. */
|
||||
#define MMHAL_FLASH_PARTITION_CONFIG_DEFAULT \
|
||||
{ \
|
||||
0, 0, false \
|
||||
}
|
||||
|
||||
/**
|
||||
* Get MMCONFIG flash partition configuration.
|
||||
*
|
||||
* MMCONFIG initialization is done by @c mmconfig_init() in @c mmconfig.c.
|
||||
* which in turn calls this function to fetch the partition configuration for config store
|
||||
* from the HAL layer. If config store is not supported by the platform then we just
|
||||
* return NULL. This function returns a static pointer to
|
||||
* @c struct @c mmhal_flash_partition_config.
|
||||
*
|
||||
* @return A static pointer to the partition config for MMCONFIG, or NULL if not supported.
|
||||
*/
|
||||
const struct mmhal_flash_partition_config *mmhal_get_mmconfig_partition(void);
|
||||
|
||||
/**
|
||||
* Erases a block of Flash pointed to by the block_address.
|
||||
*
|
||||
* The block address may be anywhere within the block to erase. The entire block gets erased.
|
||||
* Once erased all bytes in the block shall be @c MMHAL_FLASH_ERASE_VALUE (@c 0xFF).
|
||||
*
|
||||
* @param block_address The address of the block of Flash to erase.
|
||||
* @return 0 on success, negative number on failure
|
||||
*/
|
||||
int mmhal_flash_erase(uint32_t block_address);
|
||||
|
||||
/**
|
||||
* Returns the size of the Flash block at the specified address.
|
||||
*
|
||||
* @param block_address The address of the Flash block.
|
||||
* @return The size of the Flash block in bytes.
|
||||
* Returns 0 if an invalid address is specified.
|
||||
*/
|
||||
uint32_t mmhal_flash_getblocksize(uint32_t block_address);
|
||||
|
||||
/**
|
||||
* Read a block of data from the specified Flash address into the buffer.
|
||||
*
|
||||
* @param read_address The address in Flash to read from.
|
||||
* @param buf The buffer to read into.
|
||||
* @param size The number of bytes to read.
|
||||
* @return 0 on success, or a negative number on failure.
|
||||
*/
|
||||
int mmhal_flash_read(uint32_t read_address, uint8_t *buf, size_t size);
|
||||
|
||||
/**
|
||||
* Write a block of data to the specified Flash address.
|
||||
*
|
||||
* There is no alignment or minimum size requirement. This function will
|
||||
* take care of aligning the data and merging with existing Flash contents.
|
||||
* The Flash block is not erased, it is up to the application to determine
|
||||
* if the block needs to be erased before programming.
|
||||
*
|
||||
* @param write_address The address in Flash to write to.
|
||||
* @param data A pointer to the block of data to write.
|
||||
* @param size The number of bytes to write.
|
||||
* @return 0 on success, or a negative number on failure.
|
||||
*/
|
||||
int mmhal_flash_write(uint32_t write_address, const uint8_t *data, size_t size);
|
||||
|
||||
/**
|
||||
* Get LittleFS configuration.
|
||||
*
|
||||
* LittleFS initialization is done by @c littlefs_init() in @c mmosal_shim_fileio.c.
|
||||
* which in turn calls this function to fetch the hardware configuration for LittleFS
|
||||
* from the HAL layer. The LittleFS configuration will vary from platform to platform.
|
||||
* If LittleFS is not supported by the platform then we just return NULL. This function
|
||||
* returns a static pointer to @c struct @c lfs_config which is defined in @c lfs.h.
|
||||
*
|
||||
* See @c mmhal_littlefs.c for the full HAL layer implementation for your platform.
|
||||
* See @c mmosal_shim_fileio.c for the @c libc shims for LittleFS.
|
||||
* See @c README.md in the @c src/littlefs folder for detailed information on LittleFS.
|
||||
*
|
||||
* @return A static pointer to the LittleFS config structure, or NULL if not supported.
|
||||
*/
|
||||
const struct lfs_config *mmhal_get_littlefs_config(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2024 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup MMHAL
|
||||
* @defgroup MMHAL_UART Morse Micro Abstraction Layer API for UART
|
||||
*
|
||||
* This provides an abstraction layer for a UART. This is used by MM-IoT-SDK example
|
||||
* applications.
|
||||
*
|
||||
* This is a very simple API and leaves UART configuration to the HAL.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "mmhal.h"
|
||||
#include "mmosal.h"
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Function type for UART RX callback.
|
||||
*
|
||||
* @note The UART HAL must not invoke this function from interrupt context. However, the
|
||||
* implementation should not block for long periods of time or received data may be lost.
|
||||
*
|
||||
* @param data The received data.
|
||||
* @param length Length of the received data.
|
||||
* @param arg Opaque argument (as passed in to @ref mmhal_uart_init()).
|
||||
*/
|
||||
typedef void (*mmhal_uart_rx_cb_t)(const uint8_t *data, size_t length, void *arg);
|
||||
|
||||
/**
|
||||
* Initialize the UART HAL and perform any setup necessary.
|
||||
*
|
||||
* @param rx_cb Optional callback to be invoked on receive (may be NULL).
|
||||
* This callback may be invoked from interrupt context so should return
|
||||
* quickly.
|
||||
* @param rx_cb_arg Optional opaque argument to be passed to the RX callback. May be NULL.
|
||||
*/
|
||||
void mmhal_uart_init(mmhal_uart_rx_cb_t rx_cb, void *rx_cb_arg);
|
||||
|
||||
/**
|
||||
* Deinitialize the UART HAL, and disable the UART.
|
||||
*/
|
||||
void mmhal_uart_deinit(void);
|
||||
|
||||
/**
|
||||
* Transmit data on the UART. This will block until all data is buffered for transmit (but may
|
||||
* return before transmission has completed).
|
||||
*
|
||||
* @param data Data to transmit.
|
||||
* @param length Length of @p data.
|
||||
*/
|
||||
void mmhal_uart_tx(const uint8_t *data, size_t length);
|
||||
|
||||
/** Enumeration of deep sleep modes for the UART HAL. */
|
||||
enum mmhal_uart_deep_sleep_mode {
|
||||
/** Deep sleep mode is disabled. */
|
||||
MMHAL_UART_DEEP_SLEEP_DISABLED,
|
||||
/** Enable deep sleep until activity occurs on data-link transport. */
|
||||
MMHAL_UART_DEEP_SLEEP_ONE_SHOT,
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the deep sleep mode for the UART. See @ref mmhal_uart_deep_sleep_mode for possible deep
|
||||
* sleep modes. Note that a given platform may not support all modes.
|
||||
*
|
||||
* @param mode The deep sleep mode to set.
|
||||
*
|
||||
* @returns true if the mode was set successfully; false on failure (e.g., unsupported mode).
|
||||
*/
|
||||
bool mmhal_uart_set_deep_sleep_mode(enum mmhal_uart_deep_sleep_mode mode);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
@@ -0,0 +1,640 @@
|
||||
/*
|
||||
* Copyright 2023 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup MMHAL
|
||||
* @defgroup MMHAL_WLAN WLAN HAL
|
||||
*
|
||||
* API for communicating with the WLAN transceiver.
|
||||
*
|
||||
* There are different interfaces supported for communicating with the transceiver:
|
||||
*
|
||||
* * @ref MMHAL_WLAN_SDIO
|
||||
* * @ref MMHAL_WLAN_SPI
|
||||
*
|
||||
* @warning These functions shall not be called directly by the end application they are for use
|
||||
* by Morselib.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "mmpkt.h"
|
||||
#include "mmwlan.h"
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Function prototype for interrupt handler callbacks.
|
||||
*/
|
||||
typedef void (*mmhal_irq_handler_t)(void);
|
||||
|
||||
/**
|
||||
* Initialize the WLAN HAL.
|
||||
*
|
||||
* Things to do here may include:
|
||||
* * Enable SPI peripheral
|
||||
* * Configure GPIOs
|
||||
* * Enable power to the Morse Micro transceiver
|
||||
*
|
||||
* @note If enabling power for the Morse Micro transceiver in this function you may need to add a
|
||||
* blocking delay to allow the power rail to stabilize. This is hardware specific so is not
|
||||
* accounted for in the calling function.
|
||||
*/
|
||||
void mmhal_wlan_init(void);
|
||||
|
||||
/**
|
||||
* Deinitialize the WLAN HAL.
|
||||
*
|
||||
* Things to do here may include:
|
||||
* * Disable SPI peripheral
|
||||
* * Disable GPIOs
|
||||
* * Disable power to the Morse Micro transceiver
|
||||
*/
|
||||
void mmhal_wlan_deinit(void);
|
||||
|
||||
/**
|
||||
* Get MAC address override.
|
||||
*
|
||||
* This function allows the HAL to override the MAC address to be used by the device. The
|
||||
* MAC address override should be written to @p mac_addr. If no override is required then
|
||||
* @p mac_addr should be left untouched.
|
||||
*
|
||||
* @param[out] mac_addr Location where the MAC address will be stored. This will be initialized
|
||||
* to zero the first time this function is invoked, and to the previously
|
||||
* configured MAC address on subsequent invocations.
|
||||
*/
|
||||
void mmhal_read_mac_addr(uint8_t *mac_addr);
|
||||
|
||||
/**
|
||||
* Assert the WLAN wake pin.
|
||||
*/
|
||||
void mmhal_wlan_wake_assert(void);
|
||||
|
||||
/**
|
||||
* Deassert the WLAN wake pin.
|
||||
*/
|
||||
void mmhal_wlan_wake_deassert(void);
|
||||
|
||||
/**
|
||||
* Tests whether the busy pin is currently asserted.
|
||||
*
|
||||
* @note This is whether it is logically asserted and does not necessarily
|
||||
* represent the level of the GPIO pin.
|
||||
*
|
||||
* @returns @c true if asserted, else @c false.
|
||||
*/
|
||||
bool mmhal_wlan_busy_is_asserted(void);
|
||||
|
||||
/**
|
||||
* Register a handler for busy interrupts.
|
||||
*
|
||||
* @param handler The handler to register.
|
||||
*/
|
||||
void mmhal_wlan_register_busy_irq_handler(mmhal_irq_handler_t handler);
|
||||
|
||||
/**
|
||||
* Sets whether the busy interrupt is enabled.
|
||||
*
|
||||
* @warning The interrupt handler function must be configured using
|
||||
* mmhal_wlan_register_busy_irq_handler() before enabling the interrupt.
|
||||
*
|
||||
* @param enabled @c true to enable or @c false to disable.
|
||||
*/
|
||||
void mmhal_wlan_set_busy_irq_enabled(bool enabled);
|
||||
|
||||
/**
|
||||
* Read-only buffer data structure.
|
||||
*
|
||||
* The design of this data structure allows the buffer to exist either in statically or
|
||||
* dynamically allocated memory.
|
||||
*
|
||||
* For statically allocated memory, the field @c free_cb may be set to @c NULL and @c free_arg
|
||||
* ignored. For example:
|
||||
*
|
||||
* @code{.c}
|
||||
* const uint8_t some_data[] = { 0x00, 0x01, 0x02 };
|
||||
*
|
||||
* void put_some_data_into_buf(struct mmhal_robuf *robuf)
|
||||
* {
|
||||
* robuf->buf = some_data;
|
||||
* robuf->len = sizeof(some_data);
|
||||
* robuf->free_cb = NULL;
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* For dynamically allocated memory, the field @c free_cb is set to the appropriate function to
|
||||
* free the buffer and @c free_arg is an opaque argument to the free function. This approach might
|
||||
* be used, for example, when reading into a temporary buffer from storage that is not memory
|
||||
* mapped. For example:
|
||||
*
|
||||
* @code{.c}
|
||||
* #define SOME_DATA_MAXLEN (64)
|
||||
*
|
||||
* void put_some_data_into_buf(struct mmhal_robuf *robuf)
|
||||
* {
|
||||
* uint8_t *buf = malloc(SOME_DATA_MAXLEN);
|
||||
* robuf->buf = buf;
|
||||
* if (robuf->buf == NULL)
|
||||
* return;
|
||||
*
|
||||
* // HERE: copy data into buf and set robuf->len as appropriate
|
||||
*
|
||||
* robuf->free_cb = free;
|
||||
* robuf->free_arg = buf;
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
*/
|
||||
struct mmhal_robuf {
|
||||
/** Pointer to the start of the read-only buffer. May be NULL only if @c len is zero. */
|
||||
const uint8_t *buf;
|
||||
/** Length of the buffer contents. */
|
||||
uint32_t len;
|
||||
/**
|
||||
* Optional callback to be invoked by the consumer to release the buffer when it is
|
||||
* no longer required. If not required, set to @c NULL.
|
||||
*
|
||||
* @note The values of @c buf and @c len in this structure may be modified before
|
||||
* @c free_cb() is invoked. However, the value of @c free_arg will be passed
|
||||
* to @c free_cb().
|
||||
*/
|
||||
void (*free_cb)(void *arg);
|
||||
/** Optional argument to @c free_cb. Ignored if @c free_cb is @c NULL. */
|
||||
void *free_arg;
|
||||
};
|
||||
|
||||
/** Minimum length of data to be returned by @ref mmhal_wlan_read_bcf_file() and
|
||||
* @ref mmhal_wlan_read_fw_file(). */
|
||||
#define MMHAL_WLAN_FW_BCF_MIN_READ_LENGTH (4)
|
||||
|
||||
/**
|
||||
* Retrieves the content of the Morse Micro Board Configuration File and places it into the given
|
||||
* buffer.
|
||||
*
|
||||
* @param offset Offset from which to start reading the bcf
|
||||
* @param requested_len Length of data we would like to read. The length of the data returned
|
||||
* by this function may be less than @p requested_len, but must be at
|
||||
* least @ref MMHAL_WLAN_FW_BCF_MIN_READ_LENGTH.
|
||||
* @param robuf Read-only buffer data structure to be filled out by this function.
|
||||
*
|
||||
* @note On error, this function should set @c robuf->buf to @c NULL.
|
||||
* @note The caller must zero @p robuf before invoking the function.
|
||||
* @note The BCF must be in mbin format.
|
||||
*
|
||||
* @warning The caller is responsible for checking @c robuf->free_cb and calling when the buffer is
|
||||
* no longer required. Ignored if @c robuf->free_cb is @c NULL.
|
||||
*/
|
||||
void mmhal_wlan_read_bcf_file(uint32_t offset, uint32_t requested_len, struct mmhal_robuf *robuf);
|
||||
|
||||
/**
|
||||
* Retrieves the content of the Morse Micro Chip Firmware and places it into the given buffer.
|
||||
*
|
||||
* @param offset Offset from which to start reading the bcf
|
||||
* @param requested_len Length of data we would like to read. The length of the data returned
|
||||
* by this function may be less than @p requested_len, but must be at
|
||||
* least @ref MMHAL_WLAN_FW_BCF_MIN_READ_LENGTH.
|
||||
* @param robuf Read-only buffer data structure to be filled out by this function.
|
||||
*
|
||||
* @note On error, this function should set @c robuf->buf to @c NULL.
|
||||
* @note The caller must zero @p robuf before invoking the function.
|
||||
* @note The firmware must be in mbin format.
|
||||
*
|
||||
* @warning The caller is responsible for checking @c robuf->free_cb and calling when the buffer
|
||||
* is no longer required. Ignored if @c robuf->free_cb is @c NULL.
|
||||
*/
|
||||
void mmhal_wlan_read_fw_file(uint32_t offset, uint32_t requested_len, struct mmhal_robuf *robuf);
|
||||
|
||||
/**
|
||||
* @defgroup MMHAL_WLAN_SPI WLAN HAL API for SPI interface
|
||||
*
|
||||
* API for communicating with the WLAN transceiver over an SPI interface.
|
||||
*
|
||||
* @note These functions should only be implemented if using a SPI interface. They are not
|
||||
* required when using the @ref MMHAL_WLAN_SDIO.
|
||||
*
|
||||
* @warning These functions shall not be called directly by the end application they are for use
|
||||
* by Morselib.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Assert the WLAN SPI chip select pin.
|
||||
*/
|
||||
void mmhal_wlan_spi_cs_assert(void);
|
||||
|
||||
/**
|
||||
* Deassert the WLAN SPI chip select pin.
|
||||
*/
|
||||
void mmhal_wlan_spi_cs_deassert(void);
|
||||
|
||||
/**
|
||||
* Simultaneously read and write on the SPI bus.
|
||||
*
|
||||
* @param data Data to be written.
|
||||
*
|
||||
* @return the value that was read.
|
||||
*/
|
||||
uint8_t mmhal_wlan_spi_rw(uint8_t data);
|
||||
|
||||
/**
|
||||
* Receive multiple octets of data from SPI bus.
|
||||
*
|
||||
* @param buf The buffer to receive into.
|
||||
* @param len The number of octets to receive.
|
||||
*/
|
||||
void mmhal_wlan_spi_read_buf(uint8_t *buf, unsigned len);
|
||||
|
||||
/**
|
||||
* Transmit multiple octets of data to SPI bus.
|
||||
*
|
||||
* @param buf The buffer to transmit from.
|
||||
* @param len The number of octets to transmit.
|
||||
*
|
||||
* @note Blocks until transfer complete.
|
||||
*/
|
||||
void mmhal_wlan_spi_write_buf(const uint8_t *buf, unsigned len);
|
||||
|
||||
/**
|
||||
* Hard reset the chip by asserting and then releasing the reset pin.
|
||||
*
|
||||
* @warning This function must return with the chip in a fully booted state. i.e only return once
|
||||
* the reset_n line has been high for at least the boot time specified in the data sheet.
|
||||
* Failure to do so may lead to undefined behavior.
|
||||
*/
|
||||
void mmhal_wlan_hard_reset(void);
|
||||
|
||||
/**
|
||||
* Invoked by the driver to check whether the external crystal initialization sequence is required.
|
||||
*
|
||||
* Implementation of this function is optional if the external crystal initialization sequence
|
||||
* is not required. If this function is not implemented then the external crystal initialization
|
||||
* sequence will be disabled. Refer to the data sheet for your module to check if this initialization
|
||||
* is required.
|
||||
*
|
||||
* @returns true if the external crystal initialization sequence is required else false.
|
||||
*/
|
||||
bool mmhal_wlan_ext_xtal_init_is_required(void);
|
||||
|
||||
/**
|
||||
* Issue the training sequence required to put the transceiver into SPI mode.
|
||||
*/
|
||||
void mmhal_wlan_send_training_seq(void);
|
||||
|
||||
/**
|
||||
* Register a handler for SPI interrupts.
|
||||
*
|
||||
* @param handler The handler to register.
|
||||
*/
|
||||
void mmhal_wlan_register_spi_irq_handler(mmhal_irq_handler_t handler);
|
||||
|
||||
/**
|
||||
* Sets whether the SPI interrupt is enabled.
|
||||
*
|
||||
* @warning The interrupt handler function must be configured using
|
||||
* @ref mmhal_wlan_register_spi_irq_handler() before enabling the interrupt.
|
||||
*
|
||||
* @param enabled @c true to enable or @c false to disable.
|
||||
*/
|
||||
void mmhal_wlan_set_spi_irq_enabled(bool enabled);
|
||||
|
||||
/**
|
||||
* Tests whether the SPI interrupt pin is currently asserted.
|
||||
*
|
||||
* @note This is whether it is logically asserted and does not necessarily
|
||||
* represent the level of the GPIO pin.
|
||||
*
|
||||
* @returns @c true if asserted, else @c false.
|
||||
*/
|
||||
bool mmhal_wlan_spi_irq_is_asserted(void);
|
||||
|
||||
/**
|
||||
* Clear the SPI IRQ.
|
||||
*
|
||||
* @deprecated Do not invoke this function because it is deprecated and will be removed from the
|
||||
* mmhal API in a future release. This function need not be implemented as a weak
|
||||
* stub is used in morselib.
|
||||
*/
|
||||
void mmhal_wlan_clear_spi_irq(void);
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup MMHAL_WLAN_PKT WLAN HAL API for packet memory allocation
|
||||
*
|
||||
* API for allocating and freeing packet memory.
|
||||
*
|
||||
* @warning These functions shall not be called directly by the end application they are for use
|
||||
* by Morselib.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Flow control callback that can be invoked by the transmit packet memory manager to pause
|
||||
* and resume the data path in response to resource availability.
|
||||
*
|
||||
* @param state Current flow control state.
|
||||
*/
|
||||
typedef void (*mmhal_wlan_pktmem_tx_flow_control_cb_t)(enum mmwlan_tx_flow_control_state state);
|
||||
|
||||
/** Initialization arguments passed to @ref mmhal_wlan_pktmem_init(). */
|
||||
struct mmhal_wlan_pktmem_init_args {
|
||||
/** Flow control callback that can be used by the transmit packet memory manager. */
|
||||
mmhal_wlan_pktmem_tx_flow_control_cb_t tx_flow_control_cb;
|
||||
};
|
||||
|
||||
/**
|
||||
* Invoked by the driver to initialize the packet memory in the HAL.
|
||||
*
|
||||
* @param args Initialization arguments.
|
||||
*/
|
||||
void mmhal_wlan_pktmem_init(struct mmhal_wlan_pktmem_init_args *args);
|
||||
|
||||
/**
|
||||
* Invoked by the driver to deinitialize the packet memory in the HAL.
|
||||
*
|
||||
* This can free reserved memory and check for memory leaks.
|
||||
*/
|
||||
void mmhal_wlan_pktmem_deinit(void);
|
||||
|
||||
/**
|
||||
* Enumeration of packet classes used by @ref mmhal_wlan_alloc_mmpkt_for_tx().
|
||||
* These definitions must match the corresponding values in @c mmdrv_pkt_class.
|
||||
*/
|
||||
enum mmhal_wlan_pkt_class {
|
||||
MMHAL_WLAN_PKT_DATA_TID0, /**< Data TID0 */
|
||||
MMHAL_WLAN_PKT_DATA_TID1, /**< Data TID1 */
|
||||
MMHAL_WLAN_PKT_DATA_TID2, /**< Data TID2 */
|
||||
MMHAL_WLAN_PKT_DATA_TID3, /**< Data TID3 */
|
||||
MMHAL_WLAN_PKT_DATA_TID4, /**< Data TID4 */
|
||||
MMHAL_WLAN_PKT_DATA_TID5, /**< Data TID5 */
|
||||
MMHAL_WLAN_PKT_DATA_TID6, /**< Data TID6 */
|
||||
MMHAL_WLAN_PKT_DATA_TID7, /**< Data TID7 */
|
||||
MMHAL_WLAN_PKT_MANAGEMENT, /**< 802.11 Management and other important frames */
|
||||
MMHAL_WLAN_PKT_COMMAND, /**< Commands from driver to chip */
|
||||
};
|
||||
|
||||
/**
|
||||
* Allocates an mmpkt for transmission.
|
||||
*
|
||||
* When the pool of mmpkt buffers available for TX is exhausted, the HAL should pause the TX
|
||||
* path using the flow control callback that was registered when @ref mmhal_wlan_pktmem_init()
|
||||
* was invoked. Similarly, when the buffers become available again (and assuming the TX path is
|
||||
* not otherwise blocked) the driver should unpause the TX path.
|
||||
*
|
||||
* @param pkt_class The class of packet (to allow for prioritization).
|
||||
* @param space_at_start Amount of space to allocate at start of mmpkt (for prepend).
|
||||
* @param space_at_end Amount of space to allocate at end of mmpkt (for append).
|
||||
* @param metadata_length Amount of space to allocate for metadata (used internally by the
|
||||
* Morse driver).
|
||||
*
|
||||
* @returns a pointer to the allocated packet on success or @c NULL on allocation failure.
|
||||
*/
|
||||
struct mmpkt *mmhal_wlan_alloc_mmpkt_for_tx(uint8_t pkt_class, uint32_t space_at_start, uint32_t space_at_end,
|
||||
uint32_t metadata_length);
|
||||
|
||||
/**
|
||||
* Allocates an mmpkt for reception.
|
||||
*
|
||||
* @param capacity Amount of space to allocate for data.
|
||||
* @param metadata_length Amount of space to allocate for metadata (used internally by the
|
||||
* Morse driver).
|
||||
*
|
||||
* @returns a pointer to the allocated packet on success or @c NULL on allocation failure.
|
||||
*/
|
||||
struct mmpkt *mmhal_wlan_alloc_mmpkt_for_rx(uint32_t capacity, uint32_t metadata_length);
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup MMHAL_WLAN_SDIO WLAN HAL API for SDIO interface
|
||||
*
|
||||
* API for communicating with the WLAN transceiver over an SDIO interface
|
||||
*
|
||||
* @warning These functions shall not be called directly by the end application they are for use
|
||||
* by Morselib.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Enumeration of error codes that may be returned from @c mmhal_wlan_sdio_XXX() functions. */
|
||||
enum mmhal_sdio_error_codes {
|
||||
/** Invalid argument given (e.g., incorrect buffer alignment). */
|
||||
MMHAL_SDIO_INVALID_ARGUMENT = -1,
|
||||
/** Local hardware error (e.g., issue with SDIO controller). */
|
||||
MMHAL_SDIO_HW_ERROR = -2,
|
||||
/** Timeout executing SDIO command. */
|
||||
MMHAL_SDIO_CMD_TIMEOUT = -3,
|
||||
/** CRC error executing SDIO command. */
|
||||
MMHAL_SDIO_CMD_CRC_ERROR = -4,
|
||||
/** Timeout transferring data. */
|
||||
MMHAL_SDIO_DATA_TIMEOUT = -5,
|
||||
/** CRC error transferring data. */
|
||||
MMHAL_SDIO_DATA_CRC_ERROR = -6,
|
||||
/** Underflow filling SDIO controller FIFO. */
|
||||
MMHAL_SDIO_DATA_UNDERFLOW = -7,
|
||||
/** Overflow reading from SDIO controller FIFO. */
|
||||
MMHAL_SDIO_DATA_OVERRUN = -8,
|
||||
/** Another error not covered by the above error codes. */
|
||||
MMHAL_SDIO_OTHER_ERROR = -9,
|
||||
};
|
||||
|
||||
/**
|
||||
* Perform transport specific startup.
|
||||
*
|
||||
* @returns 0 on success, an error code from @ref mmhal_sdio_error_codes on failure.
|
||||
*/
|
||||
int mmhal_wlan_sdio_startup(void);
|
||||
|
||||
/**
|
||||
* Execute an SDIO command without data.
|
||||
*
|
||||
* @param[in] cmd_idx The Command Index.
|
||||
* @param[in] arg Command argument. This corresponds to the 32 bits of the command between
|
||||
* the Command Index field and the CRC7 field.
|
||||
* @param[out] rsp The contents of the command response between the Command Index field
|
||||
* and the CRC7 field. May be @c NULL if the response is not required.
|
||||
* The returned value is undefined if the return code is not zero.
|
||||
*
|
||||
* @returns 0 on success, an error code from @ref mmhal_sdio_error_codes on failure.
|
||||
*/
|
||||
int mmhal_wlan_sdio_cmd(uint8_t cmd_idx, uint32_t arg, uint32_t *rsp);
|
||||
|
||||
/**
|
||||
* Arguments structure for @ref mmhal_wlan_sdio_cmd53_write().
|
||||
*/
|
||||
struct mmhal_wlan_sdio_cmd53_write_args {
|
||||
/** The SDIO argument. This corresponds to the 32 bits of the command between
|
||||
* the Command Index field and the CRC7 field. */
|
||||
uint32_t sdio_arg;
|
||||
/** Pointer to the data buffer. 32 bit word aligned. */
|
||||
const uint8_t *data;
|
||||
/** Transfer length measured in blocks if block_size is non-zero otherwise in bytes.
|
||||
* If transfer_length is measured in bytes, it will be a multiple of 4. */
|
||||
uint16_t transfer_length;
|
||||
/**
|
||||
* If non-zero this indicates that the data should be transferred in block mode with
|
||||
* the given block size. If zero then the data should be transferred in byte mode and
|
||||
* @c transfer_length is guaranteed to not exceed the block size of the function.
|
||||
*/
|
||||
uint16_t block_size;
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute an SDIO CMD53 write.
|
||||
*
|
||||
* @param args The write arguments.
|
||||
*
|
||||
* @returns 0 on success, an error code from @ref mmhal_sdio_error_codes on failure.
|
||||
*/
|
||||
int mmhal_wlan_sdio_cmd53_write(const struct mmhal_wlan_sdio_cmd53_write_args *args);
|
||||
|
||||
/**
|
||||
* Arguments structure for @ref mmhal_wlan_sdio_cmd53_read().
|
||||
*/
|
||||
struct mmhal_wlan_sdio_cmd53_read_args {
|
||||
/** The SDIO argument. This corresponds to the 32 bits of the command between
|
||||
* the Command Index field and the CRC7 field. */
|
||||
uint32_t sdio_arg;
|
||||
/** Pointer to the data buffer to receive the data. 32 bit word aligned. */
|
||||
uint8_t *data;
|
||||
/** Transfer length measured in blocks if block_size is non-zero otherwise in bytes.
|
||||
* If transfer_length is measured in bytes, it will be a multiple of 4. */
|
||||
uint16_t transfer_length;
|
||||
/**
|
||||
* If non-zero this indicates that the data should be transferred in block mode with
|
||||
* the given block size. If zero then the data should be transferred in byte mode and
|
||||
* @c transfer_length is guaranteed to not exceed the block size of the function.
|
||||
*/
|
||||
uint16_t block_size;
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute an SDIO CMD53 read.
|
||||
*
|
||||
* @param args The read arguments.
|
||||
*
|
||||
* @returns 0 on success, an error code from @ref mmhal_sdio_error_codes on failure.
|
||||
*/
|
||||
int mmhal_wlan_sdio_cmd53_read(const struct mmhal_wlan_sdio_cmd53_read_args *args);
|
||||
|
||||
/**
|
||||
* @defgroup MMHAL_WLAN_SDIO_UTILS SDIO Utilities
|
||||
*
|
||||
* Useful macros and inline utilities function for use by SDIO and SPI HALs.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/*
|
||||
* SDIO argument definition, per SDIO Specification Version 4.10, Part E1, Section 5.3.
|
||||
*/
|
||||
|
||||
/** SDIO CMD52/CMD53 R/W flag. */
|
||||
enum mmhal_sdio_rw {
|
||||
MMHAL_SDIO_READ = 0, /**< Read operation */
|
||||
MMHAL_SDIO_WRITE = (1ul << 31), /**< Write operation */
|
||||
};
|
||||
|
||||
/** SDIO CMD52/CMD53 function number. */
|
||||
enum mmhal_sdio_function {
|
||||
MMHAL_SDIO_FUNCTION_0 = 0, /** Function 0 */
|
||||
MMHAL_SDIO_FUNCTION_1 = (1ul << 28), /** Function 1 */
|
||||
MMHAL_SDIO_FUNCTION_2 = (2ul << 28), /** Function 2 */
|
||||
};
|
||||
|
||||
/** SDIO CMD53 block mode*/
|
||||
enum mmhal_sdio_mode {
|
||||
MMHAL_SDIO_MODE_BYTE = 0, /** Byte mode */
|
||||
MMHAL_SDIO_MODE_BLOCK = (1ul << 27), /** Block mode */
|
||||
};
|
||||
|
||||
/** SDIO CMD53 OP code */
|
||||
enum mmhal_sdio_opcode {
|
||||
/** Operate on a single, fixed address. */
|
||||
MMHAL_SDIO_OPCODE_FIXED_ADDR = 0,
|
||||
/** Increment address by 1 after each byte. */
|
||||
MMHAL_SDIO_OPCODE_INC_ADDR = (1ul << 26),
|
||||
};
|
||||
|
||||
/** CMD52/53 Register Address (17 bit) offset. */
|
||||
#define MMHAL_SDIO_ADDRESS_OFFSET (9)
|
||||
/** CMD52/53 Register Address maximum value. */
|
||||
#define MMHAL_SDIO_ADDRESS_MAX ((1ul << 18) - 1)
|
||||
|
||||
/** CMD53 Byte/block count offset (9 bit). */
|
||||
#define MMHAL_SDIO_COUNT_OFFSET (0)
|
||||
/**CMD53 Byte/block count maximum value. */
|
||||
#define MMHAL_SDIO_COUNT_MAX ((1ul << 10) - 1)
|
||||
|
||||
/** CMD52 Data (8 bit) offset */
|
||||
#define MMHAL_SDIO_CMD52_DATA_OFFSET (0)
|
||||
|
||||
/**
|
||||
* Construct an SDIO CMD52 argument based on the given arguments.
|
||||
*
|
||||
* @param rw Flag indication direction (read or write).
|
||||
* @param fn The applicable function.
|
||||
* @param address The address to read/write. Must be <= @c MMHAL_SDIO_ADDRESS_MAX.
|
||||
* @param write_data The data to write if this is a write operation. Should be set to zero
|
||||
* for a read operation.
|
||||
*
|
||||
* @return the SDIO CMD52 argument generated based on the given arguments.
|
||||
*/
|
||||
static inline uint32_t mmhal_make_cmd52_arg(enum mmhal_sdio_rw rw, enum mmhal_sdio_function fn, uint32_t address,
|
||||
uint8_t write_data)
|
||||
{
|
||||
uint32_t arg;
|
||||
|
||||
arg = rw | fn;
|
||||
arg |= (address << MMHAL_SDIO_ADDRESS_OFFSET);
|
||||
arg |= (write_data << MMHAL_SDIO_CMD52_DATA_OFFSET);
|
||||
return arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an SDIO CMD53 argument based on the given arguments.
|
||||
*
|
||||
* @param rw Flag indication direction (read or write).
|
||||
* @param fn The applicable function.
|
||||
* @param mode Selects between byte and block mode.
|
||||
* @param address The address to read/write. Must be <= @c MMHAL_SDIO_ADDRESS_MAX.
|
||||
* @param count The count of bytes/blocks (depending on @p mode) to transfer. Must
|
||||
* be <= @c MMHAL_SDIO_COUNT_MAX.
|
||||
*
|
||||
* @note OP Code 1 (incrementing address) is assumed. See also @ref MMHAL_SDIO_OPCODE_INC_ADDR.
|
||||
*
|
||||
* @return the SDIO CMD53 argument generated based on the given arguments.
|
||||
*/
|
||||
static inline uint32_t mmhal_make_cmd53_arg(enum mmhal_sdio_rw rw, enum mmhal_sdio_function fn, enum mmhal_sdio_mode mode,
|
||||
uint32_t address, uint16_t count)
|
||||
{
|
||||
uint32_t arg;
|
||||
|
||||
arg = rw | fn | MMHAL_SDIO_OPCODE_INC_ADDR | mode;
|
||||
arg |= (address << MMHAL_SDIO_ADDRESS_OFFSET);
|
||||
arg |= (count << MMHAL_SDIO_COUNT_OFFSET);
|
||||
return arg;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
@@ -0,0 +1,383 @@
|
||||
/*
|
||||
* Copyright 2023 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup MMIPAL Morse Micro IP Stack Abstraction Layer (MMIPAL) API
|
||||
*
|
||||
* This API provides a layer of abstraction from the underlying IP stack for common operations
|
||||
* such as configuring the link and getting link status.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/** Maximum length of an IP address string, including null-terminator. */
|
||||
#ifndef MMIPAL_IPADDR_STR_MAXLEN
|
||||
#define MMIPAL_IPADDR_STR_MAXLEN (48)
|
||||
#endif
|
||||
|
||||
/** Maximum number of IPv6 addresses supported. */
|
||||
#ifndef MMIPAL_MAX_IPV6_ADDRESSES
|
||||
#define MMIPAL_MAX_IPV6_ADDRESSES (3)
|
||||
#endif
|
||||
|
||||
/** Enumeration of status codes returned by MMIPAL functions. */
|
||||
enum mmipal_status {
|
||||
/** Completed successfully. */
|
||||
MMIPAL_SUCCESS,
|
||||
/** One or more arguments were invalid. */
|
||||
MMIPAL_INVALID_ARGUMENT,
|
||||
/** The operation could not complete because the link is not up. */
|
||||
MMIPAL_NO_LINK,
|
||||
/** Failed due to memory allocation failure. */
|
||||
MMIPAL_NO_MEM,
|
||||
/** This functionality is not supported (e.g., due to build configuration). */
|
||||
MMIPAL_NOT_SUPPORTED,
|
||||
};
|
||||
|
||||
/** Enumeration of link states. */
|
||||
enum mmipal_link_state {
|
||||
/** Link is down. */
|
||||
MMIPAL_LINK_DOWN,
|
||||
/** Link is up. */
|
||||
MMIPAL_LINK_UP,
|
||||
};
|
||||
|
||||
/** Enumeration of IP address allocation modes. */
|
||||
enum mmipal_addr_mode {
|
||||
/** Disabled. */
|
||||
MMIPAL_DISABLED,
|
||||
/** Static IP address. */
|
||||
MMIPAL_STATIC,
|
||||
/** IP address allocated via DHCP. @c LWIP_DHCP must be set to 1 if using LWIP. */
|
||||
MMIPAL_DHCP,
|
||||
/** IP address allocated via AutoIP. @c LWIP_DHCP must be set to 1 if using LWIP. */
|
||||
MMIPAL_AUTOIP,
|
||||
/** DHCP offloaded to chip. */
|
||||
MMIPAL_DHCP_OFFLOAD,
|
||||
};
|
||||
|
||||
/** IP address string type. */
|
||||
typedef char mmipal_ip_addr_t[MMIPAL_IPADDR_STR_MAXLEN];
|
||||
|
||||
/**
|
||||
* IPv4 configuration structure.
|
||||
*
|
||||
* This should be initialized using @c MMIPAL_IP_CONFIG_DEFAULT.
|
||||
* For example:
|
||||
*
|
||||
* @code{.c}
|
||||
* struct mmipal_ip_config config = MMIPAL_IP_CONFIG_DEAFULT;
|
||||
* @endcode
|
||||
*/
|
||||
struct mmipal_ip_config {
|
||||
/** IP address allocation mode. */
|
||||
enum mmipal_addr_mode mode;
|
||||
/** local IP address */
|
||||
mmipal_ip_addr_t ip_addr;
|
||||
/** Netmask address */
|
||||
mmipal_ip_addr_t netmask;
|
||||
/** Gateway address */
|
||||
mmipal_ip_addr_t gateway_addr;
|
||||
};
|
||||
|
||||
/** Initializer for @ref mmipal_ip_config. */
|
||||
#define MMIPAL_IP_CONFIG_DEFAULT \
|
||||
{ \
|
||||
MMIPAL_DHCP, "", "", "", \
|
||||
}
|
||||
|
||||
/** Enumeration of IPv6 address allocation modes. */
|
||||
enum mmipal_ip6_addr_mode {
|
||||
/** Disabled. */
|
||||
MMIPAL_IP6_DISABLED,
|
||||
/** Static IPv6 addresses. */
|
||||
MMIPAL_IP6_STATIC,
|
||||
/** IPv6 address allocated via autoconfiguration.
|
||||
* @c LWIP_IPV6_AUTOCONFIG must be set to 1 if using LWIP. */
|
||||
MMIPAL_IP6_AUTOCONFIG,
|
||||
/** IPv6 address allocated via stateless DHCPv6.
|
||||
* @c LWIP_IPV6_DHCP6_STATELESS must be set to 1 if using LWIP. */
|
||||
MMIPAL_IP6_DHCP6_STATELESS,
|
||||
};
|
||||
|
||||
/**
|
||||
* IPv6 configuration structure.
|
||||
*
|
||||
* This should be initialized using @c MMIPAL_IP6_CONFIG_DEFAULT.
|
||||
* For example:
|
||||
*
|
||||
* @code{.c}
|
||||
* struct mmipal_ip6_config config = MMIPAL_IP6_CONFIG_DEFAULT;
|
||||
* @endcode
|
||||
*/
|
||||
struct mmipal_ip6_config {
|
||||
/** IPv6 addresses allocation mode. */
|
||||
enum mmipal_ip6_addr_mode ip6_mode;
|
||||
/** Array of IPv6 addresses. */
|
||||
mmipal_ip_addr_t ip6_addr[MMIPAL_MAX_IPV6_ADDRESSES];
|
||||
};
|
||||
|
||||
/** Initializer for @ref mmipal_ip6_config. */
|
||||
#define MMIPAL_IP6_CONFIG_DEFAULT \
|
||||
{ \
|
||||
MMIPAL_IP6_AUTOCONFIG \
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize arguments structure.
|
||||
*
|
||||
* This should be initialized using @c MMIPAL_INIT_ARGS_DEFAULT.
|
||||
* For example:
|
||||
*
|
||||
* @code{.c}
|
||||
* struct mmipal_init_args args = MMIPAL_INIT_ARGS_DEFAULT;
|
||||
* @endcode
|
||||
*/
|
||||
struct mmipal_init_args {
|
||||
/** IP address allocation mode to use. */
|
||||
enum mmipal_addr_mode mode;
|
||||
/** IP address to use (if @c mode is @c MMIPAL_STATIC). */
|
||||
mmipal_ip_addr_t ip_addr;
|
||||
/** Netmask to use (if @c mode is @c MMIPAL_STATIC). */
|
||||
mmipal_ip_addr_t netmask;
|
||||
/** Gateway IP address to use (if @c mode is @c MMIPAL_STATIC). */
|
||||
mmipal_ip_addr_t gateway_addr;
|
||||
|
||||
/** IPv6 address allocation mode to use. */
|
||||
enum mmipal_ip6_addr_mode ip6_mode;
|
||||
/** IPv6 address to use (if @c ip6_mode is @c MMIPAL_IP6_STATIC). */
|
||||
mmipal_ip_addr_t ip6_addr;
|
||||
/** Flag requesting ARP response offload feature */
|
||||
bool offload_arp_response;
|
||||
/** ARP refresh offload interval in seconds */
|
||||
uint32_t offload_arp_refresh_s;
|
||||
};
|
||||
|
||||
/**
|
||||
* Default values for @ref mmipal_init_args. This should be used when initializing the
|
||||
* @ref mmipal_init_args structure.
|
||||
*/
|
||||
#define MMIPAL_INIT_ARGS_DEFAULT \
|
||||
{ \
|
||||
MMIPAL_DHCP, {0}, {0}, {0}, MMIPAL_IP6_DISABLED, {0}, false, 0 \
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the IP stack and enable the MMWLAN interface.
|
||||
*
|
||||
* This will implicitly initialize and boot MMWLAN, and will block until this has completed.
|
||||
*
|
||||
* @note This function will boot the Morse Micro transceiver using @ref mmwlan_boot() in order
|
||||
* to read the MAC address. It is the responsibility of the caller to shut down the
|
||||
* transceiver using @ref mmwlan_shutdown() as required.
|
||||
*
|
||||
* @warning @ref mmwlan_init() must be called before invoking this function.
|
||||
*
|
||||
* @param args Initialization arguments.
|
||||
*
|
||||
* @return @c MMIPAL_SUCCESS on success. otherwise a vendor specific error code.
|
||||
*/
|
||||
enum mmipal_status mmipal_init(const struct mmipal_init_args *args);
|
||||
|
||||
/**
|
||||
* Structure representing the current status of the link.
|
||||
*/
|
||||
struct mmipal_link_status {
|
||||
/** State of the link (up/down). */
|
||||
enum mmipal_link_state link_state;
|
||||
/** Current IP address. */
|
||||
mmipal_ip_addr_t ip_addr;
|
||||
/** Current netmask. */
|
||||
mmipal_ip_addr_t netmask;
|
||||
/** Current gateway IP address. */
|
||||
mmipal_ip_addr_t gateway;
|
||||
};
|
||||
|
||||
/**
|
||||
* Prototype for callback function invoked on link status changes.
|
||||
*
|
||||
* @param link_status The current link status.
|
||||
*/
|
||||
typedef void (*mmipal_link_status_cb_fn_t)(const struct mmipal_link_status *link_status);
|
||||
|
||||
/**
|
||||
* Sets the callback function to be invoked on link status changes.
|
||||
*
|
||||
* This will be used when DHCP is enabled.
|
||||
*
|
||||
* @note This is for IPv4 only. To get IPv6 status use @c mmipal_get_ip6_config.
|
||||
* @note If an opaque argument is required then use @ref mmipal_set_ext_link_status_callback()
|
||||
* instead.
|
||||
*
|
||||
* @param fn Function pointer to the callback function.
|
||||
*/
|
||||
void mmipal_set_link_status_callback(mmipal_link_status_cb_fn_t fn);
|
||||
|
||||
/**
|
||||
* Prototype for callback function invoked on link status changes.
|
||||
*
|
||||
* This is similar to @ref mmipal_link_status_cb_fn_t but with the addition of the @p arg
|
||||
* parameter.
|
||||
*
|
||||
* @param link_status The current link status.
|
||||
* @param arg Opaque argument that was provided when the callback was registered.
|
||||
*/
|
||||
typedef void (*mmipal_ext_link_status_cb_fn_t)(const struct mmipal_link_status *link_status, void *arg);
|
||||
|
||||
/**
|
||||
* Sets the extended link status callback function to be invoked on link status changes.
|
||||
* This is similar to @ref mmipal_set_link_status_callback() with the exception that
|
||||
* an opaque argument may also be specified.
|
||||
*
|
||||
* This will be used when DHCP is enabled.
|
||||
*
|
||||
* @note This is for IPv4 only. To get IPv6 status use @c mmipal_get_ip6_config.
|
||||
*
|
||||
* @param fn Function pointer to the callback function.
|
||||
* @param arg Opaque argument to be passed to the callback.
|
||||
*/
|
||||
void mmipal_set_ext_link_status_callback(mmipal_ext_link_status_cb_fn_t fn, void *arg);
|
||||
|
||||
/**
|
||||
* Get the total number of transmitted and received packets on the MMWLAN interface
|
||||
*
|
||||
* @note If using LWIP, this function requires LWIP_STATS to be defined in your application,
|
||||
* otherwise packet counters will always return as 0.
|
||||
*
|
||||
* @param tx_packets Pointer to location to store total tx packets
|
||||
* @param rx_packets Pointer to location to store total rx packets
|
||||
*/
|
||||
void mmipal_get_link_packet_counts(uint32_t *tx_packets, uint32_t *rx_packets);
|
||||
|
||||
/**
|
||||
* Set the QoS Traffic ID to use when transmitting.
|
||||
*
|
||||
* @param tid The QoS TID to use (0 - @ref MMWLAN_MAX_QOS_TID).
|
||||
*/
|
||||
void mmipal_set_tx_qos_tid(uint8_t tid);
|
||||
|
||||
/**
|
||||
* Gets the local address for the MMWLAN interface that is appropriate for a given
|
||||
* destination address.
|
||||
*
|
||||
* The following table shows how the returned @c local_addr is selected:
|
||||
*
|
||||
* | @p dest_addr | @c local_addr returned |
|
||||
* |--------------|---------------------------|
|
||||
* | type is IPv4 | IPv4 address |
|
||||
* | type is IPv6 | An IPv6 source address selected from interface's IPv6 addresses or ERR_CONN |
|
||||
*
|
||||
* (X = don't care)
|
||||
*
|
||||
* If the given parameters would result in a @p local_addr type of IPv4 and IPv4 is not enabled,
|
||||
* or IPv6 and IPv6 is not enabled, then @c MMIPAL_INVALID_ARGUMENT will be returned.
|
||||
*
|
||||
* @param[out] local_addr Output local address for the MMWLAN interface, as noted above.
|
||||
* @param[in] dest_addr Destination address.
|
||||
*
|
||||
* @return @c MMIPAL_SUCESS if @p local_addr successfully set. otherwise an
|
||||
* appropriate error code.
|
||||
*/
|
||||
enum mmipal_status mmipal_get_local_addr(mmipal_ip_addr_t local_addr, const mmipal_ip_addr_t dest_addr);
|
||||
|
||||
/**
|
||||
* Get the IP configurations.
|
||||
*
|
||||
* This can be used to get the local IP configurations.
|
||||
*
|
||||
* @param config Pointer to the IP configurations.
|
||||
*
|
||||
* @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_NOT_SUPPORTED if IPv4 is not supported.
|
||||
*/
|
||||
enum mmipal_status mmipal_get_ip_config(struct mmipal_ip_config *config);
|
||||
|
||||
/**
|
||||
* Set the IP configurations.
|
||||
*
|
||||
* This can be used to set the local IP configurations.
|
||||
*
|
||||
* @param config Pointer to the IP configurations.
|
||||
*
|
||||
* @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_NOT_SUPPORTED if IPv4 is not supported.
|
||||
*/
|
||||
enum mmipal_status mmipal_set_ip_config(const struct mmipal_ip_config *config);
|
||||
|
||||
/**
|
||||
* Gets the current IPv4 broadcast address.
|
||||
*
|
||||
* @param[out] broadcast_addr Buffer to receive the broadcast address as a string.
|
||||
*
|
||||
* @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_NOT_SUPPORTED if IPv4 is not supported.
|
||||
*/
|
||||
enum mmipal_status mmipal_get_ip_broadcast_addr(mmipal_ip_addr_t broadcast_addr);
|
||||
|
||||
/**
|
||||
* Get the IP configurations.
|
||||
*
|
||||
* This can be used to get the local IP configurations.
|
||||
*
|
||||
* @param config Pointer to the IP configurations.
|
||||
*
|
||||
* @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_NOT_SUPPORTED if IPv6 is not supported..
|
||||
*/
|
||||
enum mmipal_status mmipal_get_ip6_config(struct mmipal_ip6_config *config);
|
||||
|
||||
/**
|
||||
* Set the IPv6 configurations.
|
||||
*
|
||||
* This can be used to set the local IPv6 configurations.
|
||||
*
|
||||
* @param config Pointer to the IPv6 configurations.
|
||||
*
|
||||
* @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_NOT_SUPPORTED if IPv6 is not supported.
|
||||
*/
|
||||
enum mmipal_status mmipal_set_ip6_config(const struct mmipal_ip6_config *config);
|
||||
|
||||
/**
|
||||
* Get current IPv4 link state.
|
||||
*
|
||||
* @returns the current IPv4 link state (up or down).
|
||||
*/
|
||||
enum mmipal_link_state mmipal_get_link_state(void);
|
||||
|
||||
/**
|
||||
* Set the DNS server at the given index.
|
||||
*
|
||||
* @warning Depending on IP stack implementation, this setting may be overridden by DHCP.
|
||||
*
|
||||
* @param[in] index Index of the DNS server to set.
|
||||
* @param[out] addr Address of the DNS server to set.
|
||||
*
|
||||
* @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_INVALID_ARGUMENT if an invalid index or IP
|
||||
* address was given.
|
||||
*/
|
||||
enum mmipal_status mmipal_set_dns_server(uint8_t index, const mmipal_ip_addr_t addr);
|
||||
|
||||
/**
|
||||
* Get the DNS server at the given index.
|
||||
*
|
||||
* @param[in] index Index of the DNS server to set.
|
||||
* @param[out] addr IP address buffer to receive the IP address of the DNS server at the given
|
||||
* index. Will be set to empty string if no server at the given index.
|
||||
*
|
||||
* @returns @c MMIPAL_SUCCESS on success.
|
||||
*/
|
||||
enum mmipal_status mmipal_get_dns_server(uint8_t index, mmipal_ip_addr_t addr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Morse logging API
|
||||
*
|
||||
* Copyright 2023 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Macro for printing a @c uint64_t as two separate @c uint32_t values. This is to allow printing of
|
||||
* these values even when the @c printf implementation doesn't support it.
|
||||
*/
|
||||
#define MM_X64_VAL(value) ((uint32_t)(value >> 32)), ((uint32_t)value)
|
||||
|
||||
/** Macro for format specifier to print @ref MM_X64_VAL */
|
||||
#define MM_X64_FMT "%08lx%08lx"
|
||||
|
||||
/**
|
||||
* Macro for printing a MAC address. This saves writing it out by hand.
|
||||
*
|
||||
* Must be used in conjunction with @ref MM_MAC_ADDR_FMT. For example:
|
||||
*
|
||||
* @code
|
||||
* uint8_t mac_addr[] = { 0, 1, 2, 3, 4, 5 };
|
||||
* printf("MAC address: " MM_MAC_ADDR_FMT "\n", MM_MAC_ADDR_VAL(mac_addr));
|
||||
* @endcode
|
||||
*/
|
||||
#define MM_MAC_ADDR_VAL(value) ((value)[0]), ((value)[1]), ((value)[2]), ((value)[3]), ((value)[4]), ((value)[5])
|
||||
|
||||
/** Macro for format specifier to print @ref MM_MAC_ADDR_VAL */
|
||||
#define MM_MAC_ADDR_FMT "%02x:%02x:%02x:%02x:%02x:%02x"
|
||||
|
||||
/**
|
||||
* Initialize Morse logging API.
|
||||
*
|
||||
* This should be invoked after OS initialization since it will create a mutex for
|
||||
* logging.
|
||||
*/
|
||||
void mm_logging_init(void);
|
||||
|
||||
/**
|
||||
* Dumps a binary buffer in hex.
|
||||
*
|
||||
* @param level A single character indicating log level.
|
||||
* @param function Name of function this was invoked from.
|
||||
* @param line_number Line number this was invoked from.
|
||||
* @param title Title of the buffer.
|
||||
* @param buf The buffer to dump.
|
||||
* @param len Length of the buffer.
|
||||
*/
|
||||
void mm_hexdump(char level, const char *function, unsigned line_number, const char *title, const uint8_t *buf, size_t len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,515 @@
|
||||
/*
|
||||
* Copyright 2022-2024 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup MMPKT Morse Micro Packet Buffer (mmpkt) API
|
||||
*
|
||||
* This API provides support for buffers tailored towards packets.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "mmosal.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Round @p x up to the next multiple of @p m (where @p m is a power of 2).
|
||||
*
|
||||
* @warning @p m must be a power of 2.
|
||||
*/
|
||||
#ifndef MM_FAST_ROUND_UP
|
||||
#define MM_FAST_ROUND_UP(x, m) ((((x)-1) | ((m)-1)) + 1)
|
||||
#endif
|
||||
|
||||
struct mmdrv_cmd_metadata;
|
||||
struct mmdrv_tx_metadata;
|
||||
struct mmdrv_rx_metadata;
|
||||
struct mmpkt_ops;
|
||||
|
||||
/**
|
||||
* Union of pointer types for mmpkt metadata.
|
||||
*
|
||||
* The metadata is accessed through one of these pointers, depending on which context the packet
|
||||
* is being used in.
|
||||
*/
|
||||
union mmpkt_metadata_ptr {
|
||||
/** Opaque pointer for contexts which are unaware of the specific metadata structure. */
|
||||
void *opaque;
|
||||
/** Metadata for a packet which is being transmitted. */
|
||||
struct mmdrv_tx_metadata *tx;
|
||||
/** Metadata for a packet which is being received. */
|
||||
struct mmdrv_rx_metadata *rx;
|
||||
/** Control block for a command response sent to the host. */
|
||||
struct mmdrv_cmd_metadata *cmd;
|
||||
};
|
||||
|
||||
/**
|
||||
* Core mmpkt data structure.
|
||||
*
|
||||
* @note The contents of this data structure should never need to be accessed directly. Rather
|
||||
* the various functions provided as part of this API should be used.
|
||||
*
|
||||
* @code
|
||||
* +----------------------------------------------------------+--------------+
|
||||
* | RESERVED | Data | RESERVED | METADATA |
|
||||
* +----------------------------------------------------------+--------------+
|
||||
* ^ ^ ^ ^
|
||||
* | | | |
|
||||
* | |<-----------data_len--------->| |
|
||||
* | start_offset |
|
||||
* | |
|
||||
* |<-----------------------buf_len-------------------------->|
|
||||
* buf
|
||||
* @endcode
|
||||
*/
|
||||
struct mmpkt {
|
||||
/** The buffer where data is stored. */
|
||||
uint8_t *buf;
|
||||
/** Length of the buffer. */
|
||||
uint32_t buf_len;
|
||||
/** Offset where actual data starts in the buffer. */
|
||||
uint32_t start_offset;
|
||||
/** Length of actual data in the buffer. */
|
||||
uint32_t data_len;
|
||||
/** Packet metadata used by driver (context dependent). */
|
||||
union mmpkt_metadata_ptr metadata;
|
||||
/** Reference to operations data structure for this mmpkt. */
|
||||
const struct mmpkt_ops *ops;
|
||||
/** Pointer that can be used to construct linked lists. */
|
||||
struct mmpkt *volatile next;
|
||||
};
|
||||
|
||||
/** Operations data structure for mmpkt. */
|
||||
struct mmpkt_ops {
|
||||
/** Free the given mmpkt. */
|
||||
void (*free_mmpkt)(void *mmpkt);
|
||||
};
|
||||
|
||||
/**
|
||||
* Opened view of an mmpkt.
|
||||
*
|
||||
* In this implementation, this structure does not actually exist. We only use it as a pointer type
|
||||
* which is incompatible with @ref mmpkt, to distinguish between functions which operate on opened
|
||||
* packet views versus functions which can operate on unopened packets.
|
||||
*
|
||||
* In other implementations of this API, packets must be "opened" (mapped into memory) before their
|
||||
* contents can be accessed. Thus the distinction between opened and unopened packets is important
|
||||
* for those implementations.
|
||||
*/
|
||||
struct mmpktview;
|
||||
|
||||
/**
|
||||
* Initialize an mmpkt header with the given values.
|
||||
*
|
||||
* @param mmpkt mmpkt to initialize.
|
||||
* @param buf Pointer to buffer.
|
||||
* @param buf_len Length of @p buf.
|
||||
* @param data_start_offset Initial value for @c start_offset.
|
||||
* @param ops Operations data structure.
|
||||
*/
|
||||
static inline void mmpkt_init(struct mmpkt *mmpkt, uint8_t *buf, uint32_t buf_len, uint32_t data_start_offset,
|
||||
const struct mmpkt_ops *ops)
|
||||
{
|
||||
memset(mmpkt, 0, sizeof(*mmpkt));
|
||||
mmpkt->buf = buf;
|
||||
mmpkt->buf_len = buf_len;
|
||||
mmpkt->start_offset = data_start_offset;
|
||||
mmpkt->ops = ops;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize an mmpkt in a single buffer using the given values.
|
||||
*
|
||||
* @param buf Pointer to buffer.
|
||||
* @param buf_len Length of @p buf.
|
||||
* @param space_at_start Amount of space to reserve at start of buffer.
|
||||
* @param space_at_end Amount of space to reserve at end of buffer.
|
||||
* @param metadata_size Size of metadata (0 for no metadata).
|
||||
*
|
||||
* @param ops Operations data structure.
|
||||
*
|
||||
* @note @p buf_len must be large enough to contain the @c mmkpt header, the data buffer
|
||||
* (rounded up to the nearest 4 bytes) and metadata (rounded up to the nearest 4 bytes).
|
||||
*
|
||||
* @returns a pointer to the initialized @c mmpkt (will be the same address as @p buf) or @c NULL
|
||||
* on error (@p buf length too short).
|
||||
*/
|
||||
static inline struct mmpkt *mmpkt_init_buf(uint8_t *buf, uint32_t buf_len, uint32_t space_at_start, uint32_t space_at_end,
|
||||
uint32_t metadata_size, const struct mmpkt_ops *ops)
|
||||
{
|
||||
struct mmpkt *mmpkt = (struct mmpkt *)buf;
|
||||
|
||||
uint8_t *data_start;
|
||||
uint32_t header_size = MM_FAST_ROUND_UP(sizeof(*mmpkt), 4);
|
||||
uint32_t data_len = MM_FAST_ROUND_UP(space_at_start + space_at_end, 4);
|
||||
metadata_size = MM_FAST_ROUND_UP(metadata_size, 4);
|
||||
|
||||
if (header_size + data_len + metadata_size > buf_len) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
data_start = ((uint8_t *)mmpkt) + header_size;
|
||||
|
||||
mmpkt_init(mmpkt, data_start, data_len, space_at_start, ops);
|
||||
|
||||
if (metadata_size != 0) {
|
||||
mmpkt->metadata.opaque = data_start + data_len;
|
||||
memset(mmpkt->metadata.opaque, 0, metadata_size);
|
||||
}
|
||||
|
||||
return mmpkt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate a new mmpkt on the heap (using @ref mmosal_malloc()).
|
||||
*
|
||||
* @param space_at_start Amount of space to reserve at start of buffer.
|
||||
* @param space_at_end Amount of space to reserve at end of buffer.
|
||||
* @param metadata_size Size of metadata (0 for no metadata).
|
||||
*
|
||||
* @note @c start_offset will be set to @p space_at_start, and @c buf_len will be the sum
|
||||
* of @p space_at_start and @p space_at_end (rounded up to a multiple of 4).
|
||||
*
|
||||
* @returns newly allocated mmpkt on success or @c NULL on failure.
|
||||
*/
|
||||
struct mmpkt *mmpkt_alloc_on_heap(uint32_t space_at_start, uint32_t space_at_end, uint32_t metadata_size);
|
||||
|
||||
/**
|
||||
* Release a reference to the given mmpkt. If this was the last reference (@c addition_ref_cnt
|
||||
* was 0) then the mmpkt will be freed using the appropriate op callback.
|
||||
*
|
||||
* @param mmpkt The mmpkt to release reference to. May be @c NULL.
|
||||
*/
|
||||
void mmpkt_release(struct mmpkt *mmpkt);
|
||||
|
||||
/**
|
||||
* Open a view of the given mmpkt.
|
||||
*
|
||||
* Packets must be opened before the contents of their buffer can be accessed. Most of the
|
||||
* functions below expect to be passed an opened view of a packet to operate on.
|
||||
*
|
||||
* The view must be closed by calling @ref mmpkt_close() before the packet is released by
|
||||
* @ref mmpkt_release().
|
||||
*
|
||||
* @param mmpkt The mmpkt to be opened.
|
||||
*
|
||||
* @returns a pointer representing the opened view.
|
||||
*/
|
||||
static inline struct mmpktview *mmpkt_open(struct mmpkt *mmpkt)
|
||||
{
|
||||
return (struct mmpktview *)mmpkt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the given view.
|
||||
*
|
||||
* @param[in,out] view Pointer to a variable holding the view to be closed.
|
||||
* This will be modified to indicate it is no longer valid.
|
||||
*/
|
||||
static inline void mmpkt_close(struct mmpktview **view)
|
||||
{
|
||||
(void)(view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying mmpkt from an opened view.
|
||||
*
|
||||
* @param view View of an mmpkt.
|
||||
*
|
||||
* @returns the underlying mmpkt.
|
||||
*/
|
||||
static inline struct mmpkt *mmpkt_from_view(struct mmpktview *view)
|
||||
{
|
||||
return (struct mmpkt *)view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a pointer to the start of the data in the mmpkt.
|
||||
*
|
||||
* @param view The opened mmpkt to operate on.
|
||||
*
|
||||
* @returns a pointer to the start of the data in the mmpkt.
|
||||
*/
|
||||
static inline uint8_t *mmpkt_get_data_start(struct mmpktview *view)
|
||||
{
|
||||
struct mmpkt *mmpkt = (struct mmpkt *)view;
|
||||
return mmpkt->buf + mmpkt->start_offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a pointer to the end of the data in the mmpkt.
|
||||
*
|
||||
* @param view The opened mmpkt to operate on.
|
||||
*
|
||||
* @returns a pointer to the end of the data in the mmpkt.
|
||||
*/
|
||||
static inline uint8_t *mmpkt_get_data_end(struct mmpktview *view)
|
||||
{
|
||||
struct mmpkt *mmpkt = (struct mmpkt *)view;
|
||||
return mmpkt->buf + mmpkt->start_offset + mmpkt->data_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Peek the length of the data currently from an unopened mmpkt.
|
||||
*
|
||||
* @param mmpkt The unopened mmpkt to operate on.
|
||||
*
|
||||
* @returns the length of the data currently in the mmpkt (note that this is different from the
|
||||
* length of the available buffer space).
|
||||
*/
|
||||
static inline uint32_t mmpkt_peek_data_length(struct mmpkt *mmpkt)
|
||||
{
|
||||
return mmpkt->data_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the length of the data currently in the mmpkt.
|
||||
*
|
||||
* @param view The opened mmpkt to operate on.
|
||||
*
|
||||
* @returns the length of the data currently in the mmpkt (note that this is different from the
|
||||
* length of the available buffer space).
|
||||
*/
|
||||
static inline uint32_t mmpkt_get_data_length(struct mmpktview *view)
|
||||
{
|
||||
struct mmpkt *mmpkt = (struct mmpkt *)view;
|
||||
return mmpkt->data_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of space available for prepending to the data in the buffer.
|
||||
*
|
||||
* @param view The opened mmpkt to operate on.
|
||||
*
|
||||
* @returns the available space in bytes.
|
||||
*/
|
||||
static inline uint32_t mmpkt_available_space_at_start(struct mmpktview *view)
|
||||
{
|
||||
struct mmpkt *mmpkt = (struct mmpkt *)view;
|
||||
return mmpkt->start_offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of space available for appending to the data in the buffer.
|
||||
*
|
||||
* @param view The opened mmpkt to operate on.
|
||||
*
|
||||
* @returns the available space in bytes.
|
||||
*/
|
||||
static inline uint32_t mmpkt_available_space_at_end(struct mmpktview *view)
|
||||
{
|
||||
struct mmpkt *mmpkt = (struct mmpkt *)view;
|
||||
return mmpkt->buf_len - (mmpkt->start_offset + mmpkt->data_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reserves space immediately before the data currently in the given mmpkt and returns
|
||||
* a pointer to this space.
|
||||
*
|
||||
* For a function that also copies data in, see @ref mmpkt_prepend_data().
|
||||
*
|
||||
* @warning @p len must be less than or equal to @ref mmpkt_available_space_at_start().
|
||||
*
|
||||
* @param view The opened mmpkt to operate on.
|
||||
* @param len Length of data to be prepended.
|
||||
*
|
||||
* @returns a pointer to the place in the buffer where the data should be put.
|
||||
*/
|
||||
static inline uint8_t *mmpkt_prepend(struct mmpktview *view, uint32_t len)
|
||||
{
|
||||
struct mmpkt *mmpkt = (struct mmpkt *)view;
|
||||
MMOSAL_ASSERT(len <= mmpkt_available_space_at_start(view));
|
||||
mmpkt->start_offset -= len;
|
||||
mmpkt->data_len += len;
|
||||
return mmpkt->buf + mmpkt->start_offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends the given data to the data already in the mmpkt.
|
||||
*
|
||||
* @warning @p len must be less than or equal to @ref mmpkt_available_space_at_start().
|
||||
*
|
||||
* @warning The memory area pointed to by data must not overlap with the mmpkt data.
|
||||
*
|
||||
* @param view The opened mmpkt to operate on.
|
||||
* @param data The data to be prepended.
|
||||
* @param len Length of data to be prepended.
|
||||
*/
|
||||
static inline void mmpkt_prepend_data(struct mmpktview *view, const uint8_t *data, uint32_t len)
|
||||
{
|
||||
uint8_t *dest = mmpkt_prepend(view, len);
|
||||
memcpy(dest, data, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reserves space immediately after the data currently in the given mmpkt and returns
|
||||
* a pointer to this space.
|
||||
*
|
||||
* For a function that also copies data in, see @ref mmpkt_append_data().
|
||||
*
|
||||
* @warning @p len must be less than or equal to @ref mmpkt_available_space_at_end().
|
||||
*
|
||||
* @param view The opened mmpkt to operate on.
|
||||
* @param len Length of data to be append.
|
||||
*
|
||||
* @returns a pointer to the place in the buffer where the data should be put.
|
||||
*/
|
||||
static inline uint8_t *mmpkt_append(struct mmpktview *view, uint32_t len)
|
||||
{
|
||||
struct mmpkt *mmpkt = (struct mmpkt *)view;
|
||||
uint8_t *ret = mmpkt_get_data_end(view);
|
||||
MMOSAL_ASSERT(len <= mmpkt_available_space_at_end(view));
|
||||
mmpkt->data_len += len;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given data to the data already in the mmpkt.
|
||||
*
|
||||
* @warning @p len must be less than or equal to @ref mmpkt_available_space_at_start().
|
||||
*
|
||||
* @param view The opened mmpkt to operate on.
|
||||
* @param data The data to be prepended.
|
||||
* @param len Length of data to be prepended.
|
||||
*/
|
||||
static inline void mmpkt_append_data(struct mmpktview *view, const uint8_t *data, uint32_t len)
|
||||
{
|
||||
uint8_t *dest = mmpkt_append(view, len);
|
||||
memcpy(dest, data, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a reference to the metadata associated with the given mmpkt.
|
||||
*
|
||||
* @param mmpkt The mmpkt to operate on.
|
||||
*
|
||||
* @returns a reference to the mmpkt metadata.
|
||||
*/
|
||||
static inline union mmpkt_metadata_ptr mmpkt_get_metadata(struct mmpkt *mmpkt)
|
||||
{
|
||||
return mmpkt->metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove data from the start of the mmpkt.
|
||||
*
|
||||
* @param view The opened mmpkt to operate on.
|
||||
* @param len Length of data to remove.
|
||||
*
|
||||
* @returns a pointer to the removed data or NULL if the mmpkt data length was less than @p len.
|
||||
*/
|
||||
static inline uint8_t *mmpkt_remove_from_start(struct mmpktview *view, uint32_t len)
|
||||
{
|
||||
struct mmpkt *mmpkt = (struct mmpkt *)view;
|
||||
uint8_t *ret;
|
||||
|
||||
if (mmpkt_get_data_length(view) < len) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = mmpkt_get_data_start(view);
|
||||
|
||||
mmpkt->start_offset += len;
|
||||
mmpkt->data_len -= len;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove data from the end of the mmpkt.
|
||||
*
|
||||
* @param view The opened mmpkt to operate on.
|
||||
* @param len Length of data to remove.
|
||||
*
|
||||
* @returns a pointer to the removed data or NULL if the mmpkt data length was less than @p len.
|
||||
*/
|
||||
static inline uint8_t *mmpkt_remove_from_end(struct mmpktview *view, uint32_t len)
|
||||
{
|
||||
struct mmpkt *mmpkt = (struct mmpkt *)view;
|
||||
uint8_t *ret;
|
||||
|
||||
if (mmpkt_get_data_length(view) < len) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = mmpkt_get_data_end(view) - len;
|
||||
|
||||
mmpkt->data_len -= len;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate the mmpkt data to the given length.
|
||||
*
|
||||
* @param mmpkt mmpkt to operate on.
|
||||
* @param len New data length. (Must be less than or equal to the data length
|
||||
* of the mmpkt).
|
||||
*/
|
||||
static inline void mmpkt_truncate(struct mmpkt *mmpkt, uint32_t len)
|
||||
{
|
||||
MMOSAL_ASSERT(len <= mmpkt->data_len);
|
||||
mmpkt->data_len = len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the `next` pointer embedded in the mmpkt.
|
||||
*
|
||||
* Used by the mmpkt_list structure for making linked lists of mmpkts.
|
||||
*
|
||||
* @param mmpkt The mmpkt to operate on.
|
||||
*
|
||||
* @returns pointer to the next mmpkt in the chain (may be NULL).
|
||||
*/
|
||||
static inline struct mmpkt *mmpkt_get_next(struct mmpkt *mmpkt)
|
||||
{
|
||||
return mmpkt->next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the `next` pointer embedded in the mmpkt.
|
||||
*
|
||||
* Used by the mmpkt_list structure for making linked lists of mmpkts.
|
||||
*
|
||||
* @param mmpkt The mmpkt to operate on.
|
||||
* @param next The next mmpkt in the chain.
|
||||
*/
|
||||
static inline void mmpkt_set_next(struct mmpkt *mmpkt, struct mmpkt *next)
|
||||
{
|
||||
mmpkt->next = next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given pointer is pointing inside the mmpkt's buffer.
|
||||
*
|
||||
* @note This checks against the full buffer, which includes any unused regions at the beginning and
|
||||
* end of the buffer which do not contain valid packet data.
|
||||
*
|
||||
* @param view The opened mmpkt to operate on.
|
||||
* @param ptr The pointer to check.
|
||||
*
|
||||
* @returns true if the pointer points into the buffer.
|
||||
*/
|
||||
static inline bool mmpkt_contains_ptr(struct mmpktview *view, const void *ptr)
|
||||
{
|
||||
struct mmpkt *mmpkt = (struct mmpkt *)view;
|
||||
return ((const uint8_t *)ptr >= &mmpkt->buf[0] && (const uint8_t *)ptr < &mmpkt->buf[mmpkt->buf_len]);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright 2022-2024 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup MMPKT
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "mmpkt.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Structure that can be used as the head of a linked list of mmpkts that counts its length. */
|
||||
struct mmpkt_list {
|
||||
/** First mmpkt in the list. */
|
||||
struct mmpkt *volatile head;
|
||||
/** Last mmpkt in the list. */
|
||||
struct mmpkt *volatile tail;
|
||||
/** Length of the list. */
|
||||
volatile uint32_t len;
|
||||
};
|
||||
|
||||
/** Static initializer for @ref mmpkt_list. */
|
||||
#define MMPKT_LIST_INIT \
|
||||
{ \
|
||||
NULL, NULL, 0 \
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization function for @ref mmpkt_list, for cases where @c MMPKT_LIST_INIT
|
||||
* cannot be used.
|
||||
*
|
||||
* @param list The mmpkt_list to init.
|
||||
*/
|
||||
static inline void mmpkt_list_init(struct mmpkt_list *list)
|
||||
{
|
||||
list->head = NULL;
|
||||
list->tail = NULL;
|
||||
list->len = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an mmpkt to the start of an mmpkt list.
|
||||
*
|
||||
* @param list The list to prepend to.
|
||||
* @param mmpkt The mmpkt to prepend.
|
||||
*/
|
||||
void mmpkt_list_prepend(struct mmpkt_list *list, struct mmpkt *mmpkt);
|
||||
|
||||
/**
|
||||
* Add an mmpkt to the end of an mmpkt list.
|
||||
*
|
||||
* @param list The list to append to.
|
||||
* @param mmpkt The mmpkt to append.
|
||||
*/
|
||||
void mmpkt_list_append(struct mmpkt_list *list, struct mmpkt *mmpkt);
|
||||
|
||||
/**
|
||||
* Remove an mmpkt from an mmpkt list.
|
||||
*
|
||||
* @param list The list to remove from.
|
||||
* @param mmpkt The mmpkt to remove.
|
||||
*/
|
||||
void mmpkt_list_remove(struct mmpkt_list *list, struct mmpkt *mmpkt);
|
||||
|
||||
/**
|
||||
* Remove the mmpkt at the head of the list and return it.
|
||||
*
|
||||
* @param list The list to dequeue from.
|
||||
*
|
||||
* @returns the dequeued mmpkt, or @c NULL if the list is empty.
|
||||
*/
|
||||
struct mmpkt *mmpkt_list_dequeue(struct mmpkt_list *list);
|
||||
|
||||
/**
|
||||
* Remove the mmpkt at the tail of the list and return it.
|
||||
*
|
||||
* @param list The list to dequeue from.
|
||||
*
|
||||
* @returns the dequeued mmpkt, or @c NULL if the list is empty.
|
||||
*/
|
||||
struct mmpkt *mmpkt_list_dequeue_tail(struct mmpkt_list *list);
|
||||
|
||||
/**
|
||||
* Remove all mmpkts from the list and return as a linked list.
|
||||
*
|
||||
* @param list The list to dequeue from.
|
||||
*
|
||||
* @returns the dequeued mmpkts, or @c NULL if the list is empty.
|
||||
*/
|
||||
static inline struct mmpkt *mmpkt_list_dequeue_all(struct mmpkt_list *list)
|
||||
{
|
||||
struct mmpkt *head = list->head;
|
||||
list->head = NULL;
|
||||
list->tail = NULL;
|
||||
list->len = 0;
|
||||
return head;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given mmpkt list is empty.
|
||||
*
|
||||
* @param list The list to check.
|
||||
*
|
||||
* @returns @c true if the list is empty, else @c false.
|
||||
*/
|
||||
static inline bool mmpkt_list_is_empty(struct mmpkt_list *list)
|
||||
{
|
||||
return (list->head == NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the head of the mmpkt list.
|
||||
*
|
||||
* @param list The list to peek into.
|
||||
*
|
||||
* @returns the mmpkt at the head of the list.
|
||||
*/
|
||||
static inline struct mmpkt *mmpkt_list_peek(struct mmpkt_list *list)
|
||||
{
|
||||
return list->head;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tail of the mmpkt list.
|
||||
*
|
||||
* @param list The list to peek into.
|
||||
*
|
||||
* @returns the mmpkt at the tail of the list.
|
||||
*/
|
||||
static inline struct mmpkt *mmpkt_list_peek_tail(struct mmpkt_list *list)
|
||||
{
|
||||
return list->tail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Free all the packets in the given list and reset the list to empty state.
|
||||
*
|
||||
* @param list The list to clear.
|
||||
*/
|
||||
void mmpkt_list_clear(struct mmpkt_list *list);
|
||||
|
||||
/**
|
||||
* Safely walk the mmpkt list.
|
||||
*
|
||||
* @warning This macro cannot be used following an if statement with no parentheses if there
|
||||
* is an else clause. For example, do not do:
|
||||
* `if (x) MMPKT_LIST_WALK(a,b,c) else foo();` -- instead:
|
||||
* `if (x) { MMPKT_LIST_WALK(a,b,c) } else foo();`
|
||||
*/
|
||||
#define MMPKT_LIST_WALK(_lst, _wlk, _nxt) \
|
||||
if ((_lst)->head != NULL) /* NOLINT(readability/braces) */ \
|
||||
for (_wlk = (_lst)->head, _nxt = mmpkt_get_next(_wlk); _wlk != NULL; \
|
||||
_wlk = _nxt, _nxt = _wlk ? mmpkt_get_next(_wlk) : NULL)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2022-2023 Morse Micro
|
||||
*/
|
||||
/**
|
||||
* @ingroup MMWLAN_REGDB
|
||||
* @defgroup MMWLAN_REGDB_TEMPLATE Template S1G regulatory database
|
||||
*
|
||||
* \{
|
||||
*
|
||||
* @section MMWLAN_REGDB_TEMPLATE_DISCLAIMER Disclaimer
|
||||
*
|
||||
* While every effort has been made to maintain accuracy of this database, no guarantee is
|
||||
* given as to the accuracy of the information contained herein.
|
||||
*
|
||||
* @section MMWLAN_REGDB_TEMPLATE_COUNTRIES Country code list
|
||||
*
|
||||
* | Country Code | Country |
|
||||
* | ------------ | ------- |
|
||||
* | AU | Australia |
|
||||
* | EU | EU |
|
||||
* | IN | India |
|
||||
* | JP | Japan |
|
||||
* | KR | South Korea |
|
||||
* | NZ | New Zealand |
|
||||
* | SG | Singapore |
|
||||
* | US | USA |
|
||||
*/
|
||||
|
||||
#include "mmwlan.h"
|
||||
|
||||
/** List of valid S1G channels for Australia. */
|
||||
static const struct mmwlan_s1g_channel s1g_channels_AU[] = {
|
||||
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
|
||||
{ 915500000, 10000, false, 68, 22, 27, 1, 30, 0, 0, 0 },
|
||||
{ 916500000, 10000, false, 68, 22, 29, 1, 30, 0, 0, 0 },
|
||||
{ 917500000, 10000, false, 68, 22, 31, 1, 30, 0, 0, 0 },
|
||||
{ 918500000, 10000, false, 68, 22, 33, 1, 30, 0, 0, 0 },
|
||||
{ 919500000, 10000, false, 68, 22, 35, 1, 30, 0, 0, 0 },
|
||||
{ 920500000, 10000, false, 68, 22, 37, 1, 30, 0, 0, 0 },
|
||||
{ 921500000, 10000, false, 68, 22, 39, 1, 30, 0, 0, 0 },
|
||||
{ 922500000, 10000, false, 68, 22, 41, 1, 30, 0, 0, 0 },
|
||||
{ 923500000, 10000, false, 68, 22, 43, 1, 30, 0, 0, 0 },
|
||||
{ 924500000, 10000, false, 68, 22, 45, 1, 30, 0, 0, 0 },
|
||||
{ 925500000, 10000, false, 68, 22, 47, 1, 30, 0, 0, 0 },
|
||||
{ 926500000, 10000, false, 68, 22, 49, 1, 30, 0, 0, 0 },
|
||||
{ 927500000, 10000, false, 68, 22, 51, 1, 30, 0, 0, 0 },
|
||||
{ 917000000, 10000, false, 69, 23, 30, 2, 30, 0, 0, 0 },
|
||||
{ 919000000, 10000, false, 69, 23, 34, 2, 30, 0, 0, 0 },
|
||||
{ 921000000, 10000, false, 69, 23, 38, 2, 30, 0, 0, 0 },
|
||||
{ 923000000, 10000, false, 69, 23, 42, 2, 30, 0, 0, 0 },
|
||||
{ 925000000, 10000, false, 69, 23, 46, 2, 30, 0, 0, 0 },
|
||||
{ 927000000, 10000, false, 69, 23, 50, 2, 30, 0, 0, 0 },
|
||||
{ 918000000, 10000, false, 70, 24, 32, 4, 30, 0, 0, 0 },
|
||||
{ 922000000, 10000, false, 70, 24, 40, 4, 30, 0, 0, 0 },
|
||||
{ 926000000, 10000, false, 70, 24, 48, 4, 30, 0, 0, 0 },
|
||||
{ 924000000, 10000, false, 71, 25, 44, 8, 30, 0, 0, 0 },
|
||||
};
|
||||
|
||||
/** Channel list structure for Australia. */
|
||||
static const struct mmwlan_s1g_channel_list s1g_channel_list_AU = {
|
||||
.country_code = "AU",
|
||||
.num_channels = (sizeof(s1g_channels_AU)/sizeof(s1g_channels_AU[0])),
|
||||
.channels = s1g_channels_AU,
|
||||
};
|
||||
|
||||
/** List of valid S1G channels for EU. */
|
||||
static const struct mmwlan_s1g_channel s1g_channels_EU[] = {
|
||||
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
|
||||
{ 863500000, 280, false, 66, 6, 1, 1, 16, 0, 0, 0 },
|
||||
{ 864500000, 280, false, 66, 6, 3, 1, 16, 0, 0, 0 },
|
||||
{ 865500000, 280, false, 66, 6, 5, 1, 16, 0, 0, 0 },
|
||||
{ 866500000, 280, false, 66, 6, 7, 1, 16, 0, 0, 0 },
|
||||
{ 867500000, 280, false, 66, 6, 9, 1, 16, 0, 0, 0 },
|
||||
};
|
||||
|
||||
/** Channel list structure for EU. */
|
||||
static const struct mmwlan_s1g_channel_list s1g_channel_list_EU = {
|
||||
.country_code = "EU",
|
||||
.num_channels = (sizeof(s1g_channels_EU)/sizeof(s1g_channels_EU[0])),
|
||||
.channels = s1g_channels_EU,
|
||||
};
|
||||
|
||||
/** List of valid S1G channels for India. */
|
||||
static const struct mmwlan_s1g_channel s1g_channels_IN[] = {
|
||||
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
|
||||
{ 865500000, 280, false, 66, 6, 5, 1, 16, 0, 0, 0 },
|
||||
{ 866500000, 280, false, 66, 6, 7, 1, 16, 0, 0, 0 },
|
||||
{ 867500000, 280, false, 66, 6, 9, 1, 16, 0, 0, 0 },
|
||||
};
|
||||
|
||||
/** Channel list structure for India. */
|
||||
static const struct mmwlan_s1g_channel_list s1g_channel_list_IN = {
|
||||
.country_code = "IN",
|
||||
.num_channels = (sizeof(s1g_channels_IN)/sizeof(s1g_channels_IN[0])),
|
||||
.channels = s1g_channels_IN,
|
||||
};
|
||||
|
||||
/** List of valid S1G channels for Japan. */
|
||||
static const struct mmwlan_s1g_channel s1g_channels_JP[] = {
|
||||
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
|
||||
{ 921000000, 1000, true, 73, 8, 9, 1, 16, 2000, 2000, 100000 },
|
||||
{ 923000000, 1000, true, 73, 8, 13, 1, 16, 2000, 2000, 100000 },
|
||||
{ 924000000, 1000, true, 73, 8, 15, 1, 16, 2000, 2000, 100000 },
|
||||
{ 925000000, 1000, true, 73, 8, 17, 1, 16, 2000, 2000, 100000 },
|
||||
{ 926000000, 1000, true, 73, 8, 19, 1, 16, 2000, 2000, 100000 },
|
||||
{ 927000000, 1000, true, 73, 8, 21, 1, 16, 2000, 2000, 100000 },
|
||||
{ 923500000, 1000, true, 64, 9, 2, 2, 16, 2000, 2000, 100000 },
|
||||
{ 924500000, 1000, true, 64, 10, 4, 2, 16, 2000, 2000, 100000 },
|
||||
{ 925500000, 1000, true, 64, 9, 6, 2, 16, 2000, 2000, 100000 },
|
||||
{ 926500000, 1000, true, 64, 10, 8, 2, 16, 2000, 2000, 100000 },
|
||||
{ 924500000, 1000, true, 65, 11, 36, 4, 16, 2000, 2000, 100000 },
|
||||
{ 925500000, 1000, true, 65, 12, 38, 4, 16, 2000, 2000, 100000 },
|
||||
};
|
||||
|
||||
/** Channel list structure for Japan. */
|
||||
static const struct mmwlan_s1g_channel_list s1g_channel_list_JP = {
|
||||
.country_code = "JP",
|
||||
.num_channels = (sizeof(s1g_channels_JP)/sizeof(s1g_channels_JP[0])),
|
||||
.channels = s1g_channels_JP,
|
||||
};
|
||||
|
||||
/** List of valid S1G channels for South Korea. */
|
||||
static const struct mmwlan_s1g_channel s1g_channels_KR[] = {
|
||||
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
|
||||
{ 918000000, 10000, false, 74, 14, 1, 1, 4, 50000, 0, 4000000 },
|
||||
{ 919000000, 10000, false, 74, 14, 3, 1, 4, 50000, 0, 4000000 },
|
||||
{ 920000000, 10000, false, 74, 14, 5, 1, 4, 50000, 0, 4000000 },
|
||||
{ 921000000, 10000, false, 74, 14, 7, 1, 4, 50000, 0, 4000000 },
|
||||
{ 922000000, 10000, false, 74, 14, 9, 1, 10, 50000, 0, 4000000 },
|
||||
{ 923000000, 10000, false, 74, 14, 11, 1, 10, 50000, 0, 4000000 },
|
||||
{ 918500000, 10000, false, 75, 15, 2, 2, 4, 50000, 0, 4000000 },
|
||||
{ 920500000, 10000, false, 75, 15, 6, 2, 4, 50000, 0, 4000000 },
|
||||
{ 922500000, 10000, false, 75, 15, 10, 2, 10, 50000, 0, 4000000 },
|
||||
{ 921500000, 10000, false, 76, 16, 8, 4, 4, 50000, 0, 4000000 },
|
||||
{ 926500000, 10000, false, 74, 14, 18, 1, 17, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */
|
||||
{ 927500000, 10000, false, 74, 14, 20, 1, 17, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */
|
||||
{ 928500000, 10000, false, 74, 14, 22, 1, 17, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */
|
||||
{ 929500000, 10000, false, 74, 14, 24, 1, 17, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */
|
||||
{ 927000000, 10000, false, 75, 15, 19, 2, 20, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */
|
||||
{ 929000000, 10000, false, 75, 15, 23, 2, 20, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */
|
||||
};
|
||||
|
||||
/** Channel list structure for South Korea. */
|
||||
static const struct mmwlan_s1g_channel_list s1g_channel_list_KR = {
|
||||
.country_code = "KR",
|
||||
.num_channels = (sizeof(s1g_channels_KR)/sizeof(s1g_channels_KR[0])),
|
||||
.channels = s1g_channels_KR,
|
||||
};
|
||||
|
||||
/** List of valid S1G channels for New Zealand. */
|
||||
static const struct mmwlan_s1g_channel s1g_channels_NZ[] = {
|
||||
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
|
||||
{ 915500000, 10000, false, 68, 26, 27, 1, 30, 0, 0, 0 },
|
||||
{ 916500000, 10000, false, 68, 26, 29, 1, 30, 0, 0, 0 },
|
||||
{ 917500000, 10000, false, 68, 26, 31, 1, 30, 0, 0, 0 },
|
||||
{ 918500000, 10000, false, 68, 26, 33, 1, 30, 0, 0, 0 },
|
||||
{ 919500000, 10000, false, 68, 26, 35, 1, 30, 0, 0, 0 },
|
||||
{ 920500000, 10000, false, 68, 26, 37, 1, 36, 0, 0, 0 },
|
||||
{ 921500000, 10000, false, 68, 26, 39, 1, 36, 0, 0, 0 },
|
||||
{ 922500000, 10000, false, 68, 26, 41, 1, 36, 0, 0, 0 },
|
||||
{ 923500000, 10000, false, 68, 26, 43, 1, 36, 0, 0, 0 },
|
||||
{ 924500000, 10000, false, 68, 26, 45, 1, 36, 0, 0, 0 },
|
||||
{ 925500000, 10000, false, 68, 26, 47, 1, 36, 0, 0, 0 },
|
||||
{ 926500000, 10000, false, 68, 26, 49, 1, 36, 0, 0, 0 },
|
||||
{ 927500000, 10000, false, 68, 26, 51, 1, 36, 0, 0, 0 },
|
||||
{ 917000000, 10000, false, 69, 27, 30, 2, 30, 0, 0, 0 },
|
||||
{ 919000000, 10000, false, 69, 27, 34, 2, 30, 0, 0, 0 },
|
||||
{ 921000000, 10000, false, 69, 27, 38, 2, 36, 0, 0, 0 },
|
||||
{ 923000000, 10000, false, 69, 27, 42, 2, 36, 0, 0, 0 },
|
||||
{ 925000000, 10000, false, 69, 27, 46, 2, 36, 0, 0, 0 },
|
||||
{ 927000000, 10000, false, 69, 27, 50, 2, 36, 0, 0, 0 },
|
||||
{ 918000000, 10000, false, 70, 28, 32, 4, 30, 0, 0, 0 },
|
||||
{ 922000000, 10000, false, 70, 28, 40, 4, 36, 0, 0, 0 },
|
||||
{ 926000000, 10000, false, 70, 28, 48, 4, 36, 0, 0, 0 },
|
||||
{ 924000000, 10000, false, 71, 29, 44, 8, 36, 0, 0, 0 },
|
||||
};
|
||||
|
||||
/** Channel list structure for New Zealand. */
|
||||
static const struct mmwlan_s1g_channel_list s1g_channel_list_NZ = {
|
||||
.country_code = "NZ",
|
||||
.num_channels = (sizeof(s1g_channels_NZ)/sizeof(s1g_channels_NZ[0])),
|
||||
.channels = s1g_channels_NZ,
|
||||
};
|
||||
|
||||
/** List of valid S1G channels for Singapore. */
|
||||
static const struct mmwlan_s1g_channel s1g_channels_SG[] = {
|
||||
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
|
||||
{ 866500000, 277, false, 66, 17, 7, 1, 29, 100000, 0, 1000000 },
|
||||
{ 867500000, 277, false, 66, 17, 9, 1, 29, 100000, 0, 1000000 },
|
||||
{ 868500000, 277, false, 66, 17, 11, 1, 29, 100000, 0, 1000000 },
|
||||
{ 868000000, 277, false, 67, 19, 10, 2, 29, 100000, 0, 1000000 },
|
||||
{ 920500000, 10000, false, 68, 18, 37, 1, 22, 0, 0, 0 },
|
||||
{ 921500000, 10000, false, 68, 18, 39, 1, 22, 0, 0, 0 },
|
||||
{ 922500000, 10000, false, 68, 18, 41, 1, 22, 0, 0, 0 },
|
||||
{ 923500000, 10000, false, 68, 18, 43, 1, 22, 0, 0, 0 },
|
||||
{ 924500000, 10000, false, 68, 18, 45, 1, 22, 0, 0, 0 },
|
||||
{ 921000000, 10000, false, 69, 20, 38, 2, 22, 0, 0, 0 },
|
||||
{ 923000000, 10000, false, 69, 20, 42, 2, 22, 0, 0, 0 },
|
||||
{ 922000000, 10000, false, 70, 21, 40, 4, 22, 0, 0, 0 },
|
||||
};
|
||||
|
||||
/** Channel list structure for Singapore. */
|
||||
static const struct mmwlan_s1g_channel_list s1g_channel_list_SG = {
|
||||
.country_code = "SG",
|
||||
.num_channels = (sizeof(s1g_channels_SG)/sizeof(s1g_channels_SG[0])),
|
||||
.channels = s1g_channels_SG,
|
||||
};
|
||||
|
||||
/** List of valid S1G channels for USA. */
|
||||
static const struct mmwlan_s1g_channel s1g_channels_US[] = {
|
||||
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
|
||||
{ 902500000, 10000, false, 68, 1, 1, 1, 36, 0, 0, 0 }, /* Warning: regulatory requirements may not be met */
|
||||
{ 903500000, 10000, false, 68, 1, 3, 1, 36, 0, 0, 0 },
|
||||
{ 904500000, 10000, false, 68, 1, 5, 1, 36, 0, 0, 0 },
|
||||
{ 905500000, 10000, false, 68, 1, 7, 1, 36, 0, 0, 0 },
|
||||
{ 906500000, 10000, false, 68, 1, 9, 1, 36, 0, 0, 0 },
|
||||
{ 907500000, 10000, false, 68, 1, 11, 1, 36, 0, 0, 0 },
|
||||
{ 908500000, 10000, false, 68, 1, 13, 1, 36, 0, 0, 0 },
|
||||
{ 909500000, 10000, false, 68, 1, 15, 1, 36, 0, 0, 0 },
|
||||
{ 910500000, 10000, false, 68, 1, 17, 1, 36, 0, 0, 0 },
|
||||
{ 911500000, 10000, false, 68, 1, 19, 1, 36, 0, 0, 0 },
|
||||
{ 912500000, 10000, false, 68, 1, 21, 1, 36, 0, 0, 0 },
|
||||
{ 913500000, 10000, false, 68, 1, 23, 1, 36, 0, 0, 0 },
|
||||
{ 914500000, 10000, false, 68, 1, 25, 1, 36, 0, 0, 0 },
|
||||
{ 915500000, 10000, false, 68, 1, 27, 1, 36, 0, 0, 0 },
|
||||
{ 916500000, 10000, false, 68, 1, 29, 1, 36, 0, 0, 0 },
|
||||
{ 917500000, 10000, false, 68, 1, 31, 1, 36, 0, 0, 0 },
|
||||
{ 918500000, 10000, false, 68, 1, 33, 1, 36, 0, 0, 0 },
|
||||
{ 919500000, 10000, false, 68, 1, 35, 1, 36, 0, 0, 0 },
|
||||
{ 920500000, 10000, false, 68, 1, 37, 1, 36, 0, 0, 0 },
|
||||
{ 921500000, 10000, false, 68, 1, 39, 1, 36, 0, 0, 0 },
|
||||
{ 922500000, 10000, false, 68, 1, 41, 1, 36, 0, 0, 0 },
|
||||
{ 923500000, 10000, false, 68, 1, 43, 1, 36, 0, 0, 0 },
|
||||
{ 924500000, 10000, false, 68, 1, 45, 1, 36, 0, 0, 0 },
|
||||
{ 925500000, 10000, false, 68, 1, 47, 1, 36, 0, 0, 0 },
|
||||
{ 926500000, 10000, false, 68, 1, 49, 1, 36, 0, 0, 0 },
|
||||
{ 927500000, 10000, false, 68, 1, 51, 1, 36, 0, 0, 0 },
|
||||
{ 903000000, 10000, false, 69, 2, 2, 2, 36, 0, 0, 0 }, /* Warning: regulatory requirements may not be met */
|
||||
{ 905000000, 10000, false, 69, 2, 6, 2, 36, 0, 0, 0 },
|
||||
{ 907000000, 10000, false, 69, 2, 10, 2, 36, 0, 0, 0 },
|
||||
{ 909000000, 10000, false, 69, 2, 14, 2, 36, 0, 0, 0 },
|
||||
{ 911000000, 10000, false, 69, 2, 18, 2, 36, 0, 0, 0 },
|
||||
{ 913000000, 10000, false, 69, 2, 22, 2, 36, 0, 0, 0 },
|
||||
{ 915000000, 10000, false, 69, 2, 26, 2, 36, 0, 0, 0 },
|
||||
{ 917000000, 10000, false, 69, 2, 30, 2, 36, 0, 0, 0 },
|
||||
{ 919000000, 10000, false, 69, 2, 34, 2, 36, 0, 0, 0 },
|
||||
{ 921000000, 10000, false, 69, 2, 38, 2, 36, 0, 0, 0 },
|
||||
{ 923000000, 10000, false, 69, 2, 42, 2, 36, 0, 0, 0 },
|
||||
{ 925000000, 10000, false, 69, 2, 46, 2, 36, 0, 0, 0 },
|
||||
{ 927000000, 10000, false, 69, 2, 50, 2, 36, 0, 0, 0 },
|
||||
{ 906000000, 10000, false, 70, 3, 8, 4, 36, 0, 0, 0 },
|
||||
{ 910000000, 10000, false, 70, 3, 16, 4, 36, 0, 0, 0 },
|
||||
{ 914000000, 10000, false, 70, 3, 24, 4, 36, 0, 0, 0 },
|
||||
{ 918000000, 10000, false, 70, 3, 32, 4, 36, 0, 0, 0 },
|
||||
{ 922000000, 10000, false, 70, 3, 40, 4, 36, 0, 0, 0 },
|
||||
{ 926000000, 10000, false, 70, 3, 48, 4, 36, 0, 0, 0 },
|
||||
{ 908000000, 10000, false, 71, 4, 12, 8, 36, 0, 0, 0 },
|
||||
{ 916000000, 10000, false, 71, 4, 28, 8, 36, 0, 0, 0 },
|
||||
{ 924000000, 10000, false, 71, 4, 44, 8, 36, 0, 0, 0 },
|
||||
};
|
||||
|
||||
/** Channel list structure for USA. */
|
||||
static const struct mmwlan_s1g_channel_list s1g_channel_list_US = {
|
||||
.country_code = "US",
|
||||
.num_channels = (sizeof(s1g_channels_US)/sizeof(s1g_channels_US[0])),
|
||||
.channels = s1g_channels_US,
|
||||
};
|
||||
|
||||
/** Array of all channel list structs used for the regulatory database. */
|
||||
|
||||
static const struct mmwlan_s1g_channel_list *regulatory_db_domains[] = {
|
||||
&s1g_channel_list_AU,
|
||||
&s1g_channel_list_EU,
|
||||
&s1g_channel_list_IN,
|
||||
&s1g_channel_list_JP,
|
||||
&s1g_channel_list_KR,
|
||||
&s1g_channel_list_NZ,
|
||||
&s1g_channel_list_SG,
|
||||
&s1g_channel_list_US,
|
||||
};
|
||||
|
||||
/** Regulatory database. */
|
||||
static const struct mmwlan_regulatory_db regulatory_db = {
|
||||
.num_domains = (sizeof(regulatory_db_domains)/sizeof(regulatory_db_domains[0])),
|
||||
.domains = regulatory_db_domains,
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a pointer to regulatory_db. This function isn't strictly necessary, since regulatory_db
|
||||
* can be accessed directly, but will prevent the compiler from generated warnings about
|
||||
* regulatory_db being unused.
|
||||
*
|
||||
* @return Reference to the regulatory database
|
||||
*/
|
||||
static inline const struct mmwlan_regulatory_db *get_regulatory_db(void)
|
||||
{
|
||||
return ®ulatory_db;
|
||||
}
|
||||
|
||||
|
||||
/** \} */
|
||||
Binary file not shown.
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "MorseWlan",
|
||||
"version": "0.1.0",
|
||||
"description": "Morse Micro mm-iot-esp32 SDK vendored for Meshtastic HaLow transport. Static morselib (Apache-2.0) + open-source shims.",
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"platforms": ["espressif32"],
|
||||
"build": {
|
||||
"srcDir": "src",
|
||||
"srcFilter": ["+<*.c>"],
|
||||
"includeDir": "include",
|
||||
"libArchive": false,
|
||||
"extraScript": "extra_script.py"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2022-2024 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/*
|
||||
* ----
|
||||
* Endianness operations
|
||||
* ----
|
||||
*/
|
||||
|
||||
#ifdef __big_endian__
|
||||
#define __BYTE_ORDER __BIG_ENDIAN
|
||||
#else
|
||||
#define __BYTE_ORDER __LITTLE_ENDIAN
|
||||
#endif
|
||||
|
||||
#define bswap_16(x) __builtin_bswap16(x)
|
||||
#define bswap_32(x) __builtin_bswap32(x)
|
||||
|
||||
#define INET_ADDRSTRLEN 16
|
||||
#define INET6_ADDRSTRLEN 46
|
||||
|
||||
/* Protocol families. */
|
||||
#define PF_INET 2 /* IP protocol family. */
|
||||
#define PF_INET6 10 /* IP version 6. */
|
||||
|
||||
/* Address families. */
|
||||
#define AF_INET PF_INET
|
||||
#define AF_INET6 PF_INET6
|
||||
|
||||
/*
|
||||
* ----
|
||||
* Type definitions
|
||||
* ----
|
||||
*/
|
||||
typedef signed char __s8;
|
||||
typedef unsigned char __u8;
|
||||
typedef signed short __s16;
|
||||
typedef unsigned short __u16;
|
||||
typedef signed int __s32;
|
||||
typedef unsigned int __u32;
|
||||
|
||||
struct in_addr {
|
||||
__u32 s_addr;
|
||||
};
|
||||
|
||||
struct in6_addr {
|
||||
union {
|
||||
__u32 u32_addr[4];
|
||||
__u8 u8_addr[16];
|
||||
} un;
|
||||
#define s6_addr un.u8_addr
|
||||
};
|
||||
|
||||
/* Stub function declaration for inet_ntop which is called from write_ipv4_info
|
||||
* function in robust_av.c. Since they are not used we will create a dummy
|
||||
* function declarion here.
|
||||
*/
|
||||
const char *inet_ntop(int __af, const void *__cp, char *__buf, int __len);
|
||||
|
||||
/* Rename crypto functions to match symbol name mangling in morselib for avoidance of namespace
|
||||
* collisions. */
|
||||
#define aes_decrypt mmint_aes_decrypt
|
||||
#define aes_decrypt_deinit mmint_aes_decrypt_deinit
|
||||
#define aes_decrypt_init mmint_aes_decrypt_init
|
||||
#define crypto_bignum_add mmint_crypto_bignum_add
|
||||
#define crypto_bignum_addmod mmint_crypto_bignum_addmod
|
||||
#define crypto_bignum_cmp mmint_crypto_bignum_cmp
|
||||
#define crypto_bignum_deinit mmint_crypto_bignum_deinit
|
||||
#define crypto_bignum_div mmint_crypto_bignum_div
|
||||
#define crypto_bignum_exptmod mmint_crypto_bignum_exptmod
|
||||
#define crypto_bignum_init mmint_crypto_bignum_init
|
||||
#define crypto_bignum_init_set mmint_crypto_bignum_init_set
|
||||
#define crypto_bignum_init_uint mmint_crypto_bignum_init_uint
|
||||
#define crypto_bignum_inverse mmint_crypto_bignum_inverse
|
||||
#define crypto_bignum_is_odd mmint_crypto_bignum_is_odd
|
||||
#define crypto_bignum_is_one mmint_crypto_bignum_is_one
|
||||
#define crypto_bignum_is_zero mmint_crypto_bignum_is_zero
|
||||
#define crypto_bignum_legendre mmint_crypto_bignum_legendre
|
||||
#define crypto_bignum_mod mmint_crypto_bignum_mod
|
||||
#define crypto_bignum_mulmod mmint_crypto_bignum_mulmod
|
||||
#define crypto_bignum_rand mmint_crypto_bignum_rand
|
||||
#define crypto_bignum_rshift mmint_crypto_bignum_rshift
|
||||
#define crypto_bignum_sqrmod mmint_crypto_bignum_sqrmod
|
||||
#define crypto_bignum_sub mmint_crypto_bignum_sub
|
||||
#define crypto_bignum_to_bin mmint_crypto_bignum_to_bin
|
||||
#define crypto_ec_deinit mmint_crypto_ec_deinit
|
||||
#define crypto_ec_get_a mmint_crypto_ec_get_a
|
||||
#define crypto_ec_get_b mmint_crypto_ec_get_b
|
||||
#define crypto_ec_get_generator mmint_crypto_ec_get_generator
|
||||
#define crypto_ec_get_order mmint_crypto_ec_get_order
|
||||
#define crypto_ec_get_prime mmint_crypto_ec_get_prime
|
||||
#define crypto_ec_init mmint_crypto_ec_init
|
||||
#define crypto_ec_order_len mmint_crypto_ec_order_len
|
||||
#define crypto_ec_point_add mmint_crypto_ec_point_add
|
||||
#define crypto_ec_point_cmp mmint_crypto_ec_point_cmp
|
||||
#define crypto_ec_point_x mmint_crypto_ec_point_x
|
||||
#define crypto_ec_point_compute_y_sqr mmint_crypto_ec_point_compute_y_sqr
|
||||
#define crypto_ec_point_deinit mmint_crypto_ec_point_deinit
|
||||
#define crypto_ec_point_from_bin mmint_crypto_ec_point_from_bin
|
||||
#define crypto_ec_point_init mmint_crypto_ec_point_init
|
||||
#define crypto_ec_point_invert mmint_crypto_ec_point_invert
|
||||
#define crypto_ec_point_is_at_infinity mmint_crypto_ec_point_is_at_infinity
|
||||
#define crypto_ec_point_is_on_curve mmint_crypto_ec_point_is_on_curve
|
||||
#define crypto_ec_point_mul mmint_crypto_ec_point_mul
|
||||
#define crypto_ec_point_to_bin mmint_crypto_ec_point_to_bin
|
||||
#define crypto_ec_prime_len mmint_crypto_ec_prime_len
|
||||
#define crypto_ec_prime_len_bits mmint_crypto_ec_prime_len_bits
|
||||
#define crypto_ecdh_deinit mmint_crypto_ecdh_deinit
|
||||
#define crypto_ecdh_get_pubkey mmint_crypto_ecdh_get_pubkey
|
||||
#define crypto_ecdh_init mmint_crypto_ecdh_init
|
||||
#define crypto_ecdh_init2 mmint_crypto_ecdh_init2
|
||||
#define crypto_ecdh_set_peerkey mmint_crypto_ecdh_set_peerkey
|
||||
#define crypto_ecdh_prime_len mmint_crypto_ecdh_prime_len
|
||||
#define crypto_get_random mmint_crypto_get_random
|
||||
#define crypto_unload mmint_crypto_unload
|
||||
#define hmac_md5 mmint_hmac_md5
|
||||
#define hmac_sha1 mmint_hmac_sha1
|
||||
#define hmac_sha1_vector mmint_hmac_sha1_vector
|
||||
#define hmac_sha256 mmint_hmac_sha256
|
||||
#define hmac_sha256_vector mmint_hmac_sha256_vector
|
||||
#define hmac_sha384 mmint_hmac_sha384
|
||||
#define hmac_sha384_vector mmint_hmac_sha384_vector
|
||||
#define hmac_sha512 mmint_hmac_sha512
|
||||
#define hmac_sha512_vector mmint_hmac_sha512_vector
|
||||
#define omac1_aes_vector mmint_omac1_aes_vector
|
||||
#define omac1_aes_128 mmint_omac1_aes_128
|
||||
#define pbkdf2_sha1 mmint_pbkdf2_sha1
|
||||
#define sha1_prf mmint_sha1_prf
|
||||
#define sha1_vector mmint_sha1_vector
|
||||
#define sha256_prf mmint_sha256_prf
|
||||
#define sha256_prf_bits mmint_sha256_prf_bits
|
||||
#define sha256_vector mmint_sha256_vector
|
||||
#define sha384_prf mmint_sha384_prf
|
||||
#define sha384_vector mmint_sha384_vector
|
||||
#define sha512_prf mmint_sha512_prf
|
||||
#define sha512_vector mmint_sha512_vector
|
||||
|
||||
#define wpabuf_alloc mmint_wpabuf_alloc
|
||||
#define wpabuf_alloc_copy mmint_wpabuf_alloc_copy
|
||||
#define wpabuf_clear_free mmint_wpabuf_clear_free
|
||||
#define wpabuf_put mmint_wpabuf_put
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifdef USE_MM_IOT_ESP32
|
||||
/*
|
||||
* Stubs for LWIP netif callback API that Arduino-ESP32's prebuilt LWIP omits
|
||||
* (CONFIG_LWIP_NETIF_STATUS_CALLBACK / _LINK_CALLBACK are disabled in its
|
||||
* sdkconfig, so the real functions are absent from the static library).
|
||||
*
|
||||
* mmipal calls these once during init to register a single callback for
|
||||
* link-up / IP-configured events. With these stubs, the callback never fires —
|
||||
* mmwlan_register_link_state_cb (driven by the radio firmware) remains the
|
||||
* authoritative source of link state, so this only costs us LWIP-level
|
||||
* notifications (e.g. "DHCP got an address"). HaLowInterface polls
|
||||
* mmipal_get_ip_config when it needs to know.
|
||||
*/
|
||||
#include "lwip/netif.h"
|
||||
|
||||
void __attribute__((weak)) netif_set_link_callback(struct netif *netif, netif_status_callback_fn link_callback)
|
||||
{
|
||||
(void)netif;
|
||||
(void)link_callback;
|
||||
}
|
||||
|
||||
void __attribute__((weak)) netif_set_status_callback(struct netif *netif, netif_status_callback_fn status_callback)
|
||||
{
|
||||
(void)netif;
|
||||
(void)status_callback;
|
||||
}
|
||||
#endif
|
||||
Binary file not shown.
@@ -0,0 +1,216 @@
|
||||
#ifdef USE_MM_IOT_ESP32
|
||||
/*
|
||||
* Copyright 2024 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
*/
|
||||
|
||||
#include "mmbuf.h"
|
||||
#include "mmosal.h"
|
||||
#include "mmutils.h"
|
||||
|
||||
static const struct mmbuf_ops mmbuf_heap_ops = {.free_mmbuf = mmosal_free};
|
||||
|
||||
struct mmbuf *mmbuf_alloc_on_heap(uint32_t space_at_start, uint32_t space_at_end)
|
||||
{
|
||||
struct mmbuf *mmbuf;
|
||||
uint8_t *buf;
|
||||
uint32_t alloc_len = MM_FAST_ROUND_UP(sizeof(*mmbuf), 4) + MM_FAST_ROUND_UP(space_at_start + space_at_end, 4);
|
||||
mmbuf = (struct mmbuf *)mmosal_malloc(alloc_len);
|
||||
if (mmbuf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* We zero the buffer as a defensive measure to reduce the likelihood of unintentionally
|
||||
* leaking information. */
|
||||
memset((uint8_t *)mmbuf, 0, alloc_len);
|
||||
|
||||
buf = ((uint8_t *)mmbuf) + MM_FAST_ROUND_UP(sizeof(*mmbuf), 4);
|
||||
|
||||
mmbuf_init(mmbuf, buf, MM_FAST_ROUND_UP(space_at_start + space_at_end, 4), space_at_start, &mmbuf_heap_ops);
|
||||
|
||||
return mmbuf;
|
||||
}
|
||||
|
||||
struct mmbuf *mmbuf_make_copy_on_heap(struct mmbuf *original)
|
||||
{
|
||||
struct mmbuf *mmbuf;
|
||||
uint8_t *buf;
|
||||
uint32_t alloc_len = MM_FAST_ROUND_UP(sizeof(*original), 4) + original->buf_len;
|
||||
mmbuf = (struct mmbuf *)mmosal_malloc(alloc_len);
|
||||
if (mmbuf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
buf = ((uint8_t *)mmbuf) + MM_FAST_ROUND_UP(sizeof(*mmbuf), 4);
|
||||
mmbuf_init(mmbuf, buf, original->buf_len, original->start_offset, &mmbuf_heap_ops);
|
||||
mmbuf->data_len = original->data_len;
|
||||
|
||||
if (original->data_len) {
|
||||
memcpy(mmbuf_get_data_start(mmbuf), mmbuf_get_data_start(original), mmbuf_get_data_length(original));
|
||||
}
|
||||
|
||||
return mmbuf;
|
||||
}
|
||||
|
||||
void mmbuf_release(struct mmbuf *mmbuf)
|
||||
{
|
||||
if (mmbuf == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
MMOSAL_ASSERT(mmbuf->ops != NULL && mmbuf->ops->free_mmbuf != NULL);
|
||||
mmbuf->ops->free_mmbuf(mmbuf);
|
||||
}
|
||||
|
||||
#ifdef MMBUF_SANITY
|
||||
static void mmbuf_list_sanity_check(struct mmbuf_list *list)
|
||||
{
|
||||
unsigned cnt = 0;
|
||||
struct mmbuf *walk;
|
||||
struct mmbuf *prev = NULL;
|
||||
|
||||
for (walk = list->head; walk != NULL; walk = walk->next) {
|
||||
cnt++;
|
||||
prev = walk;
|
||||
}
|
||||
|
||||
MMOSAL_ASSERT(cnt == list->len);
|
||||
MMOSAL_ASSERT(prev == list->tail);
|
||||
}
|
||||
#endif
|
||||
|
||||
void mmbuf_list_prepend(struct mmbuf_list *list, struct mmbuf *mmbuf)
|
||||
{
|
||||
mmbuf->next = list->head;
|
||||
list->head = mmbuf;
|
||||
list->len++;
|
||||
|
||||
if (list->tail == NULL) {
|
||||
list->tail = list->head;
|
||||
}
|
||||
|
||||
#ifdef MMBUF_SANITY
|
||||
mmbuf_list_sanity_check(list);
|
||||
#endif
|
||||
}
|
||||
|
||||
void mmbuf_list_append(struct mmbuf_list *list, struct mmbuf *mmbuf)
|
||||
{
|
||||
mmbuf->next = NULL;
|
||||
if (list->head == NULL) {
|
||||
list->head = mmbuf;
|
||||
list->tail = mmbuf;
|
||||
} else {
|
||||
list->tail->next = mmbuf;
|
||||
list->tail = mmbuf;
|
||||
}
|
||||
list->len++;
|
||||
|
||||
#ifdef MMBUF_SANITY
|
||||
mmbuf_list_sanity_check(list);
|
||||
#endif
|
||||
}
|
||||
|
||||
static struct mmbuf *mmbuf_find_prev(struct mmbuf_list *list, struct mmbuf *mmbuf)
|
||||
{
|
||||
struct mmbuf *walk, *next;
|
||||
for (walk = list->head, next = walk->next; next != NULL; walk = next, next = walk->next) {
|
||||
if (next == mmbuf) {
|
||||
return walk;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool mmbuf_list_remove(struct mmbuf_list *list, struct mmbuf *mmbuf)
|
||||
{
|
||||
struct mmbuf *prev = NULL;
|
||||
|
||||
if (list->head == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (list->head == mmbuf) {
|
||||
list->head = mmbuf->next;
|
||||
} else {
|
||||
prev = mmbuf_find_prev(list, mmbuf);
|
||||
if (prev == NULL) {
|
||||
return false;
|
||||
}
|
||||
prev->next = mmbuf->next;
|
||||
}
|
||||
|
||||
if (list->tail == mmbuf) {
|
||||
list->tail = prev;
|
||||
}
|
||||
|
||||
list->len--;
|
||||
mmbuf->next = NULL;
|
||||
|
||||
#ifdef MMBUF_SANITY
|
||||
mmbuf_list_sanity_check(list);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct mmbuf *mmbuf_list_dequeue(struct mmbuf_list *list)
|
||||
{
|
||||
if (list->head == NULL) {
|
||||
return NULL;
|
||||
} else {
|
||||
struct mmbuf *mmbuf = list->head;
|
||||
list->head = mmbuf->next;
|
||||
list->len--;
|
||||
|
||||
if (list->tail == mmbuf) {
|
||||
list->tail = NULL;
|
||||
}
|
||||
|
||||
#ifdef MMBUF_SANITY
|
||||
mmbuf_list_sanity_check(list);
|
||||
#endif
|
||||
if (mmbuf != NULL) {
|
||||
mmbuf->next = NULL;
|
||||
}
|
||||
return mmbuf;
|
||||
}
|
||||
}
|
||||
|
||||
struct mmbuf *mmbuf_list_dequeue_tail(struct mmbuf_list *list)
|
||||
{
|
||||
if (list->tail == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct mmbuf *mmbuf = list->tail;
|
||||
mmbuf_list_remove(list, mmbuf);
|
||||
|
||||
return mmbuf;
|
||||
}
|
||||
|
||||
void mmbuf_list_clear(struct mmbuf_list *list)
|
||||
{
|
||||
struct mmbuf *walk;
|
||||
struct mmbuf *next;
|
||||
|
||||
#ifdef MMBUF_SANITY
|
||||
mmbuf_list_sanity_check(list);
|
||||
#endif
|
||||
|
||||
if (list->head == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (walk = list->head, next = walk->next; walk != NULL; walk = next, next = walk ? walk->next : NULL) {
|
||||
mmbuf_release(walk);
|
||||
}
|
||||
|
||||
list->len = 0;
|
||||
list->head = NULL;
|
||||
list->tail = NULL;
|
||||
}
|
||||
|
||||
#endif /* USE_MM_IOT_ESP32 */
|
||||
@@ -0,0 +1,457 @@
|
||||
/*
|
||||
* Copyright 2024 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup MMBUF Morse Micro Buffer (mmbuf) API
|
||||
*
|
||||
* This API provides support for buffers tailored towards packet-like data that has
|
||||
* headers and trailers that are applied at subsequent layers.
|
||||
*
|
||||
* It is designed to support various backends for memory allocation. The default
|
||||
* is allocation on the heap, but other methods could be used due to the flexible API.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "mmosal.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct mmbuf_ops;
|
||||
|
||||
/**
|
||||
* Core mmbuf data structure.
|
||||
*
|
||||
* @note The contents of this data structure should never need to be accessed directly. Rather
|
||||
* the various functions provided as part of this API should be used.
|
||||
*
|
||||
* @code
|
||||
* +----------------------------------------------------------+
|
||||
* | RESERVED | Data | RESERVED |
|
||||
* +----------------------------------------------------------+
|
||||
* ^ ^ ^ ^
|
||||
* | | | |
|
||||
* | |<-----------data_len--------->| |
|
||||
* | start_offset |
|
||||
* | |
|
||||
* |<-----------------------buf_len-------------------------->|
|
||||
* buf
|
||||
* @endcode
|
||||
*/
|
||||
struct mmbuf {
|
||||
/** The buffer where data is stored. */
|
||||
uint8_t *buf;
|
||||
/** Length of the buffer. */
|
||||
uint32_t buf_len;
|
||||
/** Offset where actual data starts in the buffer. */
|
||||
uint32_t start_offset;
|
||||
/** Length of actual data in the buffer. */
|
||||
uint32_t data_len;
|
||||
/** Reference to operations data structure for this mmbuf. */
|
||||
const struct mmbuf_ops *ops;
|
||||
/** Pointer that can be used to construct linked lists. */
|
||||
struct mmbuf *volatile next;
|
||||
};
|
||||
|
||||
/** Operations data structure for mmbuf. */
|
||||
struct mmbuf_ops {
|
||||
/** Free the given mmbuf. */
|
||||
void (*free_mmbuf)(void *mmbuf);
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize an mmbuf header with the given values.
|
||||
*
|
||||
* @param mmbuf mmbuf to initialize.
|
||||
* @param buf Pointer to buffer.
|
||||
* @param buf_len Length of @p buf.
|
||||
* @param data_start_offset Initial value for @c start_offset.
|
||||
* @param ops Operations data structure.
|
||||
*/
|
||||
static inline void mmbuf_init(struct mmbuf *mmbuf, uint8_t *buf, uint32_t buf_len, uint32_t data_start_offset,
|
||||
const struct mmbuf_ops *ops)
|
||||
{
|
||||
memset(mmbuf, 0, sizeof(*mmbuf));
|
||||
mmbuf->buf = buf;
|
||||
mmbuf->buf_len = buf_len;
|
||||
mmbuf->start_offset = data_start_offset;
|
||||
mmbuf->ops = ops;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate a new mmbuf on the heap (using @ref mmosal_malloc()).
|
||||
*
|
||||
* @param space_at_start Amount of space to reserve at start of buffer.
|
||||
* @param space_at_end Amount of space to reserve at end of buffer.
|
||||
*
|
||||
* @note @c start_offset will be set to @p space_at_start, and @c buf_len will be the sum
|
||||
* of @p space_at_start and @p space_at_end (rounded up to a multiple of 4).
|
||||
*
|
||||
* @returns newly allocated mmbuf on success or @c NULL on failure.
|
||||
*/
|
||||
struct mmbuf *mmbuf_alloc_on_heap(uint32_t space_at_start, uint32_t space_at_end);
|
||||
|
||||
/**
|
||||
* Make a copy of the given mmbuf. Note that regardless of the backend that allocated
|
||||
* @p original, the newly allocated mmbuf will be allocated on the heap using
|
||||
* @ref mmbuf_alloc_on_heap().
|
||||
*
|
||||
* @param original mmbuf to copy.
|
||||
*
|
||||
* @returns newly allocated mmbuf on success or @c NULL on failure.
|
||||
*/
|
||||
struct mmbuf *mmbuf_make_copy_on_heap(struct mmbuf *original);
|
||||
|
||||
/**
|
||||
* Release a reference to the given mmbuf. If this was the last reference (@c addition_ref_cnt
|
||||
* was 0) then the mmbuf will be freed using the appropriate op callback.
|
||||
*
|
||||
* @param mmbuf The mmbuf to release reference to. May be @c NULL.
|
||||
*/
|
||||
void mmbuf_release(struct mmbuf *mmbuf);
|
||||
|
||||
/**
|
||||
* Gets a pointer to the start of the data in the mmbuf.
|
||||
*
|
||||
* @param mmbuf The mmbuf to operate on.
|
||||
*
|
||||
* @returns a pointer to the start of the data in the mmbuf.
|
||||
*/
|
||||
static inline uint8_t *mmbuf_get_data_start(struct mmbuf *mmbuf)
|
||||
{
|
||||
return mmbuf->buf + mmbuf->start_offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a pointer to the end of the data in the mmbuf.
|
||||
*
|
||||
* @param mmbuf The mmbuf to operate on.
|
||||
*
|
||||
* @returns a pointer to the end of the data in the mmbuf.
|
||||
*/
|
||||
static inline uint8_t *mmbuf_get_data_end(struct mmbuf *mmbuf)
|
||||
{
|
||||
return mmbuf->buf + mmbuf->start_offset + mmbuf->data_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the length of the data currently in the mmbuf.
|
||||
*
|
||||
* @param mmbuf The mmbuf to operate on.
|
||||
*
|
||||
* @returns the length of the data currently in the mmbuf (note that this is different from the
|
||||
* length of the available buffer space).
|
||||
*/
|
||||
static inline uint32_t mmbuf_get_data_length(struct mmbuf *mmbuf)
|
||||
{
|
||||
return mmbuf->data_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of space available for prepending to the data in the buffer.
|
||||
*
|
||||
* @param mmbuf The mmbuf to operate on.
|
||||
*
|
||||
* @returns the available space in bytes.
|
||||
*/
|
||||
static inline uint32_t mmbuf_available_space_at_start(struct mmbuf *mmbuf)
|
||||
{
|
||||
return mmbuf->start_offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of space available for appending to the data in the buffer.
|
||||
*
|
||||
* @param mmbuf The mmbuf to operate on.
|
||||
*
|
||||
* @returns the available space in bytes.
|
||||
*/
|
||||
static inline uint32_t mmbuf_available_space_at_end(struct mmbuf *mmbuf)
|
||||
{
|
||||
return mmbuf->buf_len - (mmbuf->start_offset + mmbuf->data_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reserves space immediately before the data currently in the given mmbuf and returns
|
||||
* a pointer to this space.
|
||||
*
|
||||
* For a function that also copies data in, see @ref mmbuf_prepend_data().
|
||||
*
|
||||
* @warning @p len must be less than or equal to @ref mmbuf_available_space_at_start().
|
||||
*
|
||||
* @param mmbuf The mmbuf to operate on.
|
||||
* @param len Length of data to be prepended.
|
||||
*
|
||||
* @returns a pointer to the place in the buffer where the data should be put.
|
||||
*/
|
||||
static inline uint8_t *mmbuf_prepend(struct mmbuf *mmbuf, uint32_t len)
|
||||
{
|
||||
MMOSAL_ASSERT(len <= mmbuf_available_space_at_start(mmbuf));
|
||||
mmbuf->start_offset -= len;
|
||||
mmbuf->data_len += len;
|
||||
return mmbuf->buf + mmbuf->start_offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends the given data to the data already in the mmbuf.
|
||||
*
|
||||
* @warning @p len must be less than or equal to @ref mmbuf_available_space_at_start().
|
||||
*
|
||||
* @warning The memory area pointed to by data must not overlap with the mmbuf data.
|
||||
*
|
||||
* @param mmbuf The mmbuf to operate on.
|
||||
* @param data The data to be prepended.
|
||||
* @param len Length of data to be prepended.
|
||||
*/
|
||||
static inline void mmbuf_prepend_data(struct mmbuf *mmbuf, const uint8_t *data, uint32_t len)
|
||||
{
|
||||
uint8_t *dest = mmbuf_prepend(mmbuf, len);
|
||||
memcpy(dest, data, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reserves space immediately after the data currently in the given mmbuf and returns
|
||||
* a pointer to this space.
|
||||
*
|
||||
* For a function that also copies data in, see @ref mmbuf_append_data().
|
||||
*
|
||||
* @warning @p len must be less than or equal to @ref mmbuf_available_space_at_end().
|
||||
*
|
||||
* @param mmbuf The mmbuf to operate on.
|
||||
* @param len Length of data to be append.
|
||||
*
|
||||
* @returns a pointer to the place in the buffer where the data should be put.
|
||||
*/
|
||||
static inline uint8_t *mmbuf_append(struct mmbuf *mmbuf, uint32_t len)
|
||||
{
|
||||
uint8_t *ret = mmbuf_get_data_end(mmbuf);
|
||||
MMOSAL_ASSERT(len <= mmbuf_available_space_at_end(mmbuf));
|
||||
mmbuf->data_len += len;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given data to the data already in the mmbuf.
|
||||
*
|
||||
* @warning @p len must be less than or equal to @ref mmbuf_available_space_at_start().
|
||||
*
|
||||
* @param mmbuf The mmbuf to operate on.
|
||||
* @param data The data to be prepended.
|
||||
* @param len Length of data to be prepended.
|
||||
*/
|
||||
static inline void mmbuf_append_data(struct mmbuf *mmbuf, const uint8_t *data, uint32_t len)
|
||||
{
|
||||
uint8_t *dest = mmbuf_append(mmbuf, len);
|
||||
memcpy(dest, data, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove data from the start of the mmbuf.
|
||||
*
|
||||
* @param mmbuf mmbuf to operate on.
|
||||
* @param len Length of data to remove.
|
||||
*
|
||||
* @returns a pointer to the removed data or NULL if the mmbuf data length was less than @p len.
|
||||
*/
|
||||
static inline uint8_t *mmbuf_remove_from_start(struct mmbuf *mmbuf, uint32_t len)
|
||||
{
|
||||
uint8_t *ret;
|
||||
|
||||
if (mmbuf_get_data_length(mmbuf) < len) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = mmbuf_get_data_start(mmbuf);
|
||||
|
||||
mmbuf->start_offset += len;
|
||||
mmbuf->data_len -= len;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove data from the end of the mmbuf.
|
||||
*
|
||||
* @param mmbuf mmbuf to operate on.
|
||||
* @param len Length of data to remove.
|
||||
*
|
||||
* @returns a pointer to the removed data or NULL if the mmbuf data length was less than @p len.
|
||||
*/
|
||||
static inline uint8_t *mmbuf_remove_from_end(struct mmbuf *mmbuf, uint32_t len)
|
||||
{
|
||||
uint8_t *ret;
|
||||
|
||||
if (mmbuf_get_data_length(mmbuf) < len) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = mmbuf_get_data_end(mmbuf) - len;
|
||||
|
||||
mmbuf->data_len -= len;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate the mmbuf data to the given length.
|
||||
*
|
||||
* @param mmbuf mmbuf to operate on.
|
||||
* @param len New data length. (Must be less than or equal to the data length
|
||||
* of the mmbuf).
|
||||
*/
|
||||
static inline void mmbuf_truncate(struct mmbuf *mmbuf, uint32_t len)
|
||||
{
|
||||
MMOSAL_ASSERT(len <= mmbuf->data_len);
|
||||
mmbuf->data_len = len;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
/** Structure that can be used as the head of a linked list of mmbufs that counts its length. */
|
||||
struct mmbuf_list {
|
||||
/** First mmbuf in the list. */
|
||||
struct mmbuf *volatile head;
|
||||
/** Last mmbuf in the list. */
|
||||
struct mmbuf *volatile tail;
|
||||
/** Length of the list. */
|
||||
volatile uint32_t len;
|
||||
};
|
||||
|
||||
/** Static initializer for @ref mmbuf_list. */
|
||||
#define MMBUF_LIST_INIT \
|
||||
{ \
|
||||
NULL, NULL, 0 \
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialization function for @ref mmbuf_list, for cases where @c MMBUF_LIST_INIT
|
||||
* cannot be used.
|
||||
*
|
||||
* @param list The mmbuf_list to init.
|
||||
*/
|
||||
static inline void mmbuf_list_init(struct mmbuf_list *list)
|
||||
{
|
||||
list->head = NULL;
|
||||
list->tail = NULL;
|
||||
list->len = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an mmbuf to the start of an mmbuf list.
|
||||
*
|
||||
* @param list The list to prepend to.
|
||||
* @param mmbuf The mmbuf to prepend.
|
||||
*/
|
||||
void mmbuf_list_prepend(struct mmbuf_list *list, struct mmbuf *mmbuf);
|
||||
|
||||
/**
|
||||
* Add an mmbuf to the end of an mmbuf list.
|
||||
*
|
||||
* @param list The list to append to.
|
||||
* @param mmbuf The mmbuf to append.
|
||||
*/
|
||||
void mmbuf_list_append(struct mmbuf_list *list, struct mmbuf *mmbuf);
|
||||
|
||||
/**
|
||||
* Remove an mmbuf from an mmbuf list.
|
||||
*
|
||||
* @param list The list to remove from.
|
||||
* @param mmbuf The mmbuf to remove.
|
||||
*
|
||||
* @returns @c true if the given @c mmbuf was present in @c list, else @c fase.
|
||||
*/
|
||||
bool mmbuf_list_remove(struct mmbuf_list *list, struct mmbuf *mmbuf);
|
||||
|
||||
/**
|
||||
* Remove the mmbuf at the head of the list and return it.
|
||||
*
|
||||
* @param list The list to dequeue from.
|
||||
*
|
||||
* @returns the dequeued mmbuf, or @c NULL if the list is empty.
|
||||
*/
|
||||
struct mmbuf *mmbuf_list_dequeue(struct mmbuf_list *list);
|
||||
|
||||
/**
|
||||
* Remove the mmbuf at the tail of the list and return it.
|
||||
*
|
||||
* @param list The list to dequeue from.
|
||||
*
|
||||
* @returns the dequeued mmbuf, or @c NULL if the list is empty.
|
||||
*/
|
||||
struct mmbuf *mmbuf_list_dequeue_tail(struct mmbuf_list *list);
|
||||
|
||||
/**
|
||||
* Remove all mmbufs from the list and return as a linked list.
|
||||
*
|
||||
* @param list The list to dequeue from.
|
||||
*
|
||||
* @returns the dequeued mmbufs, or @c NULL if the list is empty.
|
||||
*/
|
||||
static inline struct mmbuf *mmbuf_list_dequeue_all(struct mmbuf_list *list)
|
||||
{
|
||||
struct mmbuf *head = list->head;
|
||||
list->head = NULL;
|
||||
list->tail = NULL;
|
||||
list->len = 0;
|
||||
return head;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given mmbuf list is empty.
|
||||
*
|
||||
* @param list The list to check.
|
||||
*
|
||||
* @returns @c true if the list is empty, else @c false.
|
||||
*/
|
||||
static inline bool mmbuf_list_is_empty(struct mmbuf_list *list)
|
||||
{
|
||||
return (list->head == NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the head of the mmbuf list.
|
||||
*
|
||||
* @param list The list to peek into.
|
||||
*
|
||||
* @returns the mmbuf at the head of the list.
|
||||
*/
|
||||
static inline struct mmbuf *mmbuf_list_peek(struct mmbuf_list *list)
|
||||
{
|
||||
return list->head;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tail of the mmbuf list.
|
||||
*
|
||||
* @param list The list to peek into.
|
||||
*
|
||||
* @returns the mmbuf at the tail of the list.
|
||||
*/
|
||||
static inline struct mmbuf *mmbuf_list_peek_tail(struct mmbuf_list *list)
|
||||
{
|
||||
return list->tail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Free all the packets in the given list and reset the list to empty state.
|
||||
*
|
||||
* @param list The list to clear.
|
||||
*/
|
||||
void mmbuf_list_clear(struct mmbuf_list *list);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
@@ -0,0 +1,50 @@
|
||||
#ifdef USE_MM_IOT_ESP32
|
||||
/*
|
||||
* Copyright 2024 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "mmcrc.h"
|
||||
|
||||
/**
|
||||
* Static table used for the table_driven implementation.
|
||||
*/
|
||||
static const uint16_t crc16_xmodem_lookup_table[256] = {
|
||||
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce,
|
||||
0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c,
|
||||
0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee,
|
||||
0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738,
|
||||
0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e,
|
||||
0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc,
|
||||
0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae,
|
||||
0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
|
||||
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e,
|
||||
0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c,
|
||||
0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e,
|
||||
0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8,
|
||||
0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e,
|
||||
0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c,
|
||||
0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e,
|
||||
0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
|
||||
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1,
|
||||
0x1ef0};
|
||||
|
||||
/**
|
||||
* @note This implementation(with a few modifications) and corresponding table was generated using
|
||||
* pycrc v0.9.2 (MIT) using the XMODEM model. https://pycrc.org/. The code generated by pycrc
|
||||
* is not considered a substantial portion of the software, therefore the licence does not
|
||||
* cover the generated code, and the author of pycrc will not claim any copyright on the
|
||||
* generated code (https://pypi.org/project/pycrc/0.9.2/).
|
||||
*/
|
||||
uint16_t mmcrc_16_xmodem(uint16_t crc, const void *data, size_t data_len)
|
||||
{
|
||||
const uint8_t *d = (const uint8_t *)data;
|
||||
|
||||
while (data_len--) {
|
||||
crc = (crc16_xmodem_lookup_table[((crc >> 8) ^ *d++)] ^ (crc << 8));
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
#endif /* USE_MM_IOT_ESP32 */
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2024 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup MMCRC Morse Micro Cyclic Redundancy Check (mmcrc) API
|
||||
*
|
||||
* This API provides support for CRC algorithms used by Morse Micro code.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Compute the CRC-16 for the data buffer using the XMODEM model.
|
||||
*
|
||||
* @param crc Seed for CRC calc, zero in most cases this is zero (0). If chaining calls
|
||||
* then this is the output from the previous invocation.
|
||||
* @param data Pointer to the start of the data to calculate the crc over.
|
||||
* @param data_len Length of the data array in bytes.
|
||||
*
|
||||
* @return Returns the CRC value.
|
||||
*/
|
||||
uint16_t mmcrc_16_xmodem(uint16_t crc, const void *data, size_t data_len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
@@ -0,0 +1,100 @@
|
||||
#ifdef USE_MM_IOT_ESP32
|
||||
/*
|
||||
* Copyright 2021-2023 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "mmhal.h"
|
||||
#include "mmosal.h"
|
||||
#include "mmutils.h"
|
||||
#include "mmwlan.h"
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_random.h"
|
||||
#include "esp_system.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
void mmhal_init(void)
|
||||
{
|
||||
/* We initialise the MM_RESET_N Pin here so that we can hold the MM6108 in reset regardless of
|
||||
* whether the mmhal_wlan_init/deinit function have been called. This allows us to ensure the
|
||||
* chip is in its lowest power state. You may want to revise this depending on your particular
|
||||
* hardware configuration. */
|
||||
gpio_config_t io_conf = {};
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pin_bit_mask = (1 << CONFIG_MM_RESET_N);
|
||||
io_conf.pull_down_en = 0;
|
||||
io_conf.pull_up_en = 0;
|
||||
gpio_config(&io_conf);
|
||||
|
||||
gpio_set_level(CONFIG_MM_RESET_N, 0);
|
||||
|
||||
/* Initialise the gpio ISR handler service. This allows per-pin GPIO interrupt handlers and is
|
||||
* what is used to register all the wlan related interrupt. */
|
||||
gpio_install_isr_service(0);
|
||||
}
|
||||
|
||||
void mmhal_log_write(const uint8_t *data, size_t length)
|
||||
{
|
||||
while (length--) {
|
||||
putc(*data++, stdout);
|
||||
}
|
||||
}
|
||||
|
||||
void mmhal_log_flush(void) {}
|
||||
|
||||
void mmhal_read_mac_addr(uint8_t *mac_addr)
|
||||
{
|
||||
/* We do not override the MAC address here. Therefore the driver will attempt to read it from
|
||||
* the chip and failing that will assign a randomly generated address. */
|
||||
(void)(mac_addr);
|
||||
}
|
||||
|
||||
uint32_t mmhal_random_u32(uint32_t min, uint32_t max)
|
||||
{
|
||||
/* Note: the below implementation does not guarantee a uniform distribution. */
|
||||
|
||||
uint32_t random_value = esp_random();
|
||||
|
||||
if (min == 0 && max == UINT32_MAX) {
|
||||
return random_value;
|
||||
} else {
|
||||
/* Calculate the range and shift required to fit within [min, max] */
|
||||
return (random_value % (max - min + 1)) + min;
|
||||
}
|
||||
}
|
||||
|
||||
void mmhal_reset(void)
|
||||
{
|
||||
esp_restart();
|
||||
while (1) {
|
||||
}
|
||||
}
|
||||
|
||||
void mmhal_set_deep_sleep_veto(uint8_t veto_id)
|
||||
{
|
||||
MM_UNUSED(veto_id);
|
||||
}
|
||||
|
||||
void mmhal_clear_deep_sleep_veto(uint8_t veto_id)
|
||||
{
|
||||
MM_UNUSED(veto_id);
|
||||
}
|
||||
|
||||
void mmhal_set_led(uint8_t led, uint8_t level)
|
||||
{
|
||||
MM_UNUSED(led);
|
||||
MM_UNUSED(level);
|
||||
}
|
||||
|
||||
bool mmhal_get_hardware_version(char *version_buffer, size_t version_buffer_length)
|
||||
{
|
||||
/* Note: You need to identify the correct hardware and or version
|
||||
* here using whatever means available (GPIO's, version number stored in EEPROM, etc)
|
||||
* and return the correct string here. */
|
||||
return !mmosal_safer_strcpy(version_buffer, "MM-ESP32S3 V1.0", version_buffer_length);
|
||||
}
|
||||
|
||||
#endif /* USE_MM_IOT_ESP32 */
|
||||
@@ -0,0 +1,72 @@
|
||||
#ifdef USE_MM_IOT_ESP32
|
||||
/*
|
||||
* Copyright 2021-2023 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "mmhal_wlan.h"
|
||||
#include "mmosal.h"
|
||||
|
||||
/*
|
||||
* ---------------------------------------------------------------------------------------------
|
||||
* BCF Retrieval
|
||||
* ---------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/*
|
||||
* The following implementation reads the BCF File from the config store.
|
||||
*/
|
||||
|
||||
void mmhal_wlan_read_bcf_file(uint32_t offset, uint32_t requested_len, struct mmhal_robuf *robuf)
|
||||
{
|
||||
/** Points to the start of the BCF binary image. Defined as part of the Makefile */
|
||||
extern uint8_t bcf_binary_start;
|
||||
/** Points to the end of the BCF binary image. Defined as part of the Makefile */
|
||||
extern uint8_t bcf_binary_end;
|
||||
|
||||
size_t bcf_len = &bcf_binary_end - &bcf_binary_start;
|
||||
|
||||
/* Initialise robuf */
|
||||
robuf->buf = NULL;
|
||||
robuf->len = 0;
|
||||
robuf->free_arg = NULL;
|
||||
robuf->free_cb = NULL;
|
||||
|
||||
/* Sanity check */
|
||||
if (bcf_len < offset) {
|
||||
printf("Detected an attempt to start reading off the end of the bcf file.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
robuf->buf = (uint8_t *)&bcf_binary_start + offset;
|
||||
robuf->len = bcf_len - offset;
|
||||
robuf->len = (robuf->len < requested_len) ? robuf->len : requested_len;
|
||||
}
|
||||
|
||||
/*
|
||||
* ---------------------------------------------------------------------------------------------
|
||||
* Firmware Retrieval
|
||||
* ---------------------------------------------------------------------------------------------
|
||||
*/
|
||||
/** Points to the start of the firmware binary image. Defined as part of the Makefile */
|
||||
extern uint8_t firmware_binary_start;
|
||||
/** Points to the end of the firmware binary image. Defined as part of the Makefile */
|
||||
extern uint8_t firmware_binary_end;
|
||||
|
||||
void mmhal_wlan_read_fw_file(uint32_t offset, uint32_t requested_len, struct mmhal_robuf *robuf)
|
||||
{
|
||||
uint32_t firmware_len = &firmware_binary_end - &firmware_binary_start;
|
||||
if (offset > firmware_len) {
|
||||
printf("Detected an attempt to start read off the end of the firmware file.\n");
|
||||
robuf->buf = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
robuf->buf = (&firmware_binary_start + offset);
|
||||
firmware_len -= offset;
|
||||
|
||||
robuf->len = (firmware_len < requested_len) ? firmware_len : requested_len;
|
||||
}
|
||||
|
||||
#endif /* USE_MM_IOT_ESP32 */
|
||||
@@ -0,0 +1,726 @@
|
||||
#ifdef USE_MM_IOT_ESP32
|
||||
/*
|
||||
* Copyright 2021-2023 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "mmipal.h"
|
||||
#include "mmnetif.h"
|
||||
#include "mmosal.h"
|
||||
#include "mmutils.h"
|
||||
#include "mmwlan.h"
|
||||
|
||||
#include "lwip/api.h"
|
||||
#include "lwip/autoip.h"
|
||||
#include "lwip/def.h"
|
||||
#include "lwip/dhcp.h"
|
||||
#include "lwip/dhcp6.h"
|
||||
#include "lwip/dns.h"
|
||||
#include "lwip/etharp.h"
|
||||
#include "lwip/ethip6.h"
|
||||
#include "lwip/igmp.h"
|
||||
#include "lwip/inet.h"
|
||||
#include "lwip/ip4_frag.h"
|
||||
#include "lwip/ip6_frag.h"
|
||||
#include "lwip/ip_addr.h"
|
||||
#include "lwip/mem.h"
|
||||
#include "lwip/sockets.h"
|
||||
#include "lwip/stats.h"
|
||||
#include "lwip/tcp.h"
|
||||
#include "lwip/tcpip.h"
|
||||
#include "lwip/udp.h"
|
||||
|
||||
static struct mmipal_data {
|
||||
struct netif lwip_mmnetif;
|
||||
/** This stores the IPv4 link state for the IP stack. I.e., do we have an IP address or not. */
|
||||
enum mmipal_link_state ip_link_state;
|
||||
/** Flag requesting ARP response offload feature */
|
||||
bool offload_arp_response;
|
||||
/** ARP refresh offload interval in seconds */
|
||||
uint32_t offload_arp_refresh_s;
|
||||
bool dhcp_offload_init_complete;
|
||||
/** The link status callback function that has been registered. */
|
||||
mmipal_link_status_cb_fn_t link_status_callback;
|
||||
/** The extended link status callback function that has been registered. */
|
||||
mmipal_ext_link_status_cb_fn_t ext_link_status_callback;
|
||||
/** Argument for the extended link status callback function that has been registered. */
|
||||
void *ext_link_status_callback_arg;
|
||||
#if LWIP_IPV4
|
||||
enum mmipal_addr_mode ip4_mode;
|
||||
#endif
|
||||
#if LWIP_IPV6
|
||||
enum mmipal_ip6_addr_mode ip6_mode;
|
||||
#endif
|
||||
} mmipal_data = {};
|
||||
|
||||
/** Getter function to retrieve the global mmipal data structure.*/
|
||||
static inline struct mmipal_data *mmipal_get_data(void)
|
||||
{
|
||||
return &mmipal_data;
|
||||
}
|
||||
|
||||
static void netif_status_callback(struct netif *netif);
|
||||
|
||||
#if LWIP_IPV4
|
||||
|
||||
/**
|
||||
* DHCP Lease update callback, invoked when we get a new DHCP lease.
|
||||
*
|
||||
* @param lease_info The new DHCP lease.
|
||||
*/
|
||||
static void mmipal_dhcp_lease_updated(const struct mmwlan_dhcp_lease_info *lease_info, void *arg)
|
||||
{
|
||||
struct mmipal_data *data = mmipal_get_data();
|
||||
|
||||
ip4_addr_t ip_addr, netmask, gateway;
|
||||
ip_addr_t dns_addr = ip_addr_any;
|
||||
|
||||
MM_UNUSED(arg);
|
||||
|
||||
data->dhcp_offload_init_complete = true;
|
||||
|
||||
ip4_addr_set_u32(&ip_addr, lease_info->ip4_addr);
|
||||
ip4_addr_set_u32(&netmask, lease_info->mask4_addr);
|
||||
ip4_addr_set_u32(&gateway, lease_info->gw4_addr);
|
||||
ip4_addr_set_u32(ip_2_ip4(&dns_addr), lease_info->dns4_addr);
|
||||
|
||||
LOCK_TCPIP_CORE();
|
||||
netif_set_addr(&data->lwip_mmnetif, &ip_addr, &netmask, &gateway);
|
||||
dns_setserver(0, &dns_addr);
|
||||
UNLOCK_TCPIP_CORE();
|
||||
|
||||
netif_status_callback(&data->lwip_mmnetif);
|
||||
}
|
||||
|
||||
enum mmipal_status mmipal_get_ip_config(struct mmipal_ip_config *config)
|
||||
{
|
||||
struct mmipal_data *data = mmipal_get_data();
|
||||
char *result;
|
||||
|
||||
config->mode = data->ip4_mode;
|
||||
|
||||
result = ipaddr_ntoa_r(&data->lwip_mmnetif.ip_addr, config->ip_addr, sizeof(config->ip_addr));
|
||||
LWIP_ASSERT("IP buf too short", result != NULL);
|
||||
|
||||
result = ipaddr_ntoa_r(&data->lwip_mmnetif.netmask, config->netmask, sizeof(config->netmask));
|
||||
LWIP_ASSERT("IP buf too short", result != NULL);
|
||||
|
||||
result = ipaddr_ntoa_r(&data->lwip_mmnetif.gw, config->gateway_addr, sizeof(config->gateway_addr));
|
||||
LWIP_ASSERT("IP buf too short", result != NULL);
|
||||
|
||||
return MMIPAL_SUCCESS;
|
||||
}
|
||||
|
||||
enum mmipal_status mmipal_set_ip_config(const struct mmipal_ip_config *config)
|
||||
{
|
||||
struct mmipal_data *data = mmipal_get_data();
|
||||
int result;
|
||||
ip_addr_t ip_addr = ip_addr_any;
|
||||
ip_addr_t netmask = ip_addr_any;
|
||||
ip_addr_t gateway = ip_addr_any;
|
||||
struct netif *netif = &data->lwip_mmnetif;
|
||||
|
||||
if (config->mode != MMIPAL_DHCP_OFFLOAD && data->ip4_mode == MMIPAL_DHCP_OFFLOAD) {
|
||||
printf("Once enabled DHCP offload mode cannot be disabled\n");
|
||||
return MMIPAL_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
switch (config->mode) {
|
||||
case MMIPAL_DISABLED:
|
||||
printf("%s mode not supported\n", "DISABLED");
|
||||
return MMIPAL_INVALID_ARGUMENT;
|
||||
|
||||
case MMIPAL_AUTOIP:
|
||||
printf("%s mode not supported\n", "AutoIP");
|
||||
return MMIPAL_INVALID_ARGUMENT;
|
||||
|
||||
case MMIPAL_DHCP_OFFLOAD:
|
||||
/* Currently we only support enabling DHCP offload when initialising */
|
||||
printf("%s mode not supported\n", "DHCP_OFFLOAD");
|
||||
return MMIPAL_INVALID_ARGUMENT;
|
||||
|
||||
case MMIPAL_STATIC:
|
||||
result = ipaddr_aton(config->ip_addr, &ip_addr);
|
||||
if (!result) {
|
||||
return MMIPAL_INVALID_ARGUMENT;
|
||||
}
|
||||
result = ipaddr_aton(config->netmask, &netmask);
|
||||
if (!result) {
|
||||
return MMIPAL_INVALID_ARGUMENT;
|
||||
}
|
||||
result = ipaddr_aton(config->gateway_addr, &gateway);
|
||||
if (!result) {
|
||||
return MMIPAL_INVALID_ARGUMENT;
|
||||
}
|
||||
break;
|
||||
|
||||
case MMIPAL_DHCP:
|
||||
break;
|
||||
}
|
||||
|
||||
LOCK_TCPIP_CORE();
|
||||
|
||||
if (config->mode != MMIPAL_DHCP && data->ip4_mode == MMIPAL_DHCP) {
|
||||
/* Stop DHCP if it was started earlier before setting static IP */
|
||||
dhcp_stop(netif);
|
||||
}
|
||||
|
||||
data->ip4_mode = config->mode;
|
||||
|
||||
netif_set_addr(netif, ip_2_ip4(&ip_addr), ip_2_ip4(&netmask), ip_2_ip4(&gateway));
|
||||
if (data->ip4_mode == MMIPAL_DHCP) {
|
||||
result = dhcp_start(netif);
|
||||
LWIP_ASSERT("DHCP start error", result == ERR_OK);
|
||||
}
|
||||
|
||||
UNLOCK_TCPIP_CORE();
|
||||
return MMIPAL_SUCCESS;
|
||||
}
|
||||
|
||||
enum mmipal_status mmipal_get_ip_broadcast_addr(mmipal_ip_addr_t broadcast_addr)
|
||||
{
|
||||
struct mmipal_data *data = mmipal_get_data();
|
||||
char *result;
|
||||
|
||||
uint32_t ip_addr = ip_addr_get_ip4_u32(&data->lwip_mmnetif.ip_addr);
|
||||
uint32_t netmask = ip_addr_get_ip4_u32(&data->lwip_mmnetif.netmask);
|
||||
uint32_t broadcast_u32 = (ip_addr & netmask) | (0xffffffff & ~netmask);
|
||||
ip_addr_t broadcast_ip_addr;
|
||||
ip_addr_t *_broadcast_ip_addr = &broadcast_ip_addr;
|
||||
ip_addr_set_ip4_u32(_broadcast_ip_addr, broadcast_u32);
|
||||
|
||||
result = ipaddr_ntoa_r(&broadcast_ip_addr, broadcast_addr, MMIPAL_IPADDR_STR_MAXLEN);
|
||||
LWIP_ASSERT("IP buf too short", result != NULL);
|
||||
|
||||
return MMIPAL_SUCCESS;
|
||||
}
|
||||
|
||||
#else
|
||||
enum mmipal_status mmipal_get_ip_config(struct mmipal_ip_config *config)
|
||||
{
|
||||
MM_UNUSED(config);
|
||||
LWIP_ASSERT("IPv4 not enabled", false);
|
||||
return MMIPAL_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
enum mmipal_status mmipal_set_ip_config(const struct mmipal_ip_config *config)
|
||||
{
|
||||
MM_UNUSED(config);
|
||||
LWIP_ASSERT("IPv4 not enabled", false);
|
||||
return MMIPAL_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
enum mmipal_status mmipal_get_ip_broadcast_addr(mmipal_ip_addr_t broadcast_addr)
|
||||
{
|
||||
MM_UNUSED(broadcast_addr);
|
||||
LWIP_ASSERT("IPv4 not enabled", false);
|
||||
return MMIPAL_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if LWIP_IPV6
|
||||
|
||||
enum mmipal_status mmipal_get_ip6_config(struct mmipal_ip6_config *config)
|
||||
{
|
||||
struct mmipal_data *data = mmipal_get_data();
|
||||
unsigned ii;
|
||||
struct netif *netif = &data->lwip_mmnetif;
|
||||
|
||||
if (config == NULL) {
|
||||
return MMIPAL_INVALID_ARGUMENT;
|
||||
}
|
||||
config->ip6_mode = data->ip6_mode;
|
||||
for (ii = 0; ii < LWIP_IPV6_NUM_ADDRESSES; ii++) {
|
||||
char *result;
|
||||
const ip_addr_t *addr = &ip6_addr_any;
|
||||
|
||||
if (ip6_addr_isvalid(netif_ip6_addr_state(netif, ii))) {
|
||||
addr = &data->lwip_mmnetif.ip6_addr[ii];
|
||||
}
|
||||
|
||||
result = ipaddr_ntoa_r(addr, config->ip6_addr[ii], sizeof(config->ip6_addr[ii]));
|
||||
LWIP_ASSERT("IP buf too short", result != NULL);
|
||||
}
|
||||
return MMIPAL_SUCCESS;
|
||||
}
|
||||
|
||||
enum mmipal_status mmipal_set_ip6_config(const struct mmipal_ip6_config *config)
|
||||
{
|
||||
struct mmipal_data *data = mmipal_get_data();
|
||||
struct netif *netif = &data->lwip_mmnetif;
|
||||
err_t result;
|
||||
unsigned ii;
|
||||
ip_addr_t ip6_addr[LWIP_IPV6_NUM_ADDRESSES];
|
||||
|
||||
for (ii = 0; ii < LWIP_IPV6_NUM_ADDRESSES; ii++) {
|
||||
int result = ipaddr_aton(config->ip6_addr[ii], &ip6_addr[ii]);
|
||||
if (!result) {
|
||||
return MMIPAL_INVALID_ARGUMENT;
|
||||
}
|
||||
}
|
||||
|
||||
LOCK_TCPIP_CORE();
|
||||
|
||||
if (config->ip6_mode == MMIPAL_IP6_STATIC) {
|
||||
if (data->ip6_mode != MMIPAL_IP6_STATIC) {
|
||||
#if LWIP_IPV6_DHCP6
|
||||
dhcp6_disable(netif);
|
||||
#endif
|
||||
netif_set_ip6_autoconfig_enabled(netif, 0);
|
||||
data->ip6_mode = MMIPAL_IP6_STATIC;
|
||||
}
|
||||
|
||||
if (!ip6_addr_islinklocal(ip_2_ip6(&(ip6_addr[0])))) {
|
||||
printf("First address must be linklocal address (address start with fe80)\n");
|
||||
}
|
||||
|
||||
for (ii = 0; ii < LWIP_IPV6_NUM_ADDRESSES; ii++) {
|
||||
if (ip_addr_isany_val(ip6_addr[ii])) {
|
||||
netif_ip6_addr_set(netif, ii, IP6_ADDR_ANY6);
|
||||
netif_ip6_addr_set_state(netif, ii, IP6_ADDR_INVALID);
|
||||
} else {
|
||||
netif_ip6_addr_set(netif, ii, ip_2_ip6(&(ip6_addr[ii])));
|
||||
netif_ip6_addr_set_state(netif, ii, IP6_ADDR_TENTATIVE);
|
||||
netif_ip6_addr_set_valid_life(netif, ii, IP6_ADDR_LIFE_STATIC);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (data->ip6_mode == MMIPAL_IP6_STATIC) {
|
||||
for (ii = 0; ii < LWIP_IPV6_NUM_ADDRESSES; ii++) {
|
||||
netif_ip6_addr_set(netif, ii, IP6_ADDR_ANY6);
|
||||
netif_ip6_addr_set_state(netif, ii, IP6_ADDR_INVALID);
|
||||
}
|
||||
}
|
||||
netif_set_ip6_autoconfig_enabled(netif, 1);
|
||||
netif_create_ip6_linklocal_address(netif, 1);
|
||||
data->ip6_mode = MMIPAL_IP6_AUTOCONFIG;
|
||||
}
|
||||
|
||||
if (config->ip6_mode == MMIPAL_IP6_DHCP6_STATELESS)
|
||||
#if LWIP_IPV6_DHCP6
|
||||
{
|
||||
result = dhcp6_enable_stateless(netif);
|
||||
LWIP_ASSERT("Stateless DHCP6 start error", result == ERR_OK);
|
||||
data->ip6_mode = MMIPAL_IP6_DHCP6_STATELESS;
|
||||
} else {
|
||||
dhcp6_disable(netif);
|
||||
}
|
||||
#else
|
||||
{
|
||||
printf("LWIP_IPV6_DHCP6 is not enabled\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
UNLOCK_TCPIP_CORE();
|
||||
return MMIPAL_SUCCESS;
|
||||
}
|
||||
|
||||
#else
|
||||
enum mmipal_status mmipal_get_ip6_config(struct mmipal_ip6_config *config)
|
||||
{
|
||||
MM_UNUSED(config);
|
||||
LWIP_ASSERT("IPv6 not enabled", false);
|
||||
return MMIPAL_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
enum mmipal_status mmipal_set_ip6_config(const struct mmipal_ip6_config *config)
|
||||
{
|
||||
MM_UNUSED(config);
|
||||
LWIP_ASSERT("IPv6 not enabled", false);
|
||||
return MMIPAL_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static bool mmipal_link_status_check(struct netif *netif)
|
||||
{
|
||||
bool ip4_addr_check = true;
|
||||
|
||||
#if LWIP_IPV4
|
||||
ip4_addr_check = !ip_addr_isany(&(netif->ip_addr));
|
||||
#endif
|
||||
|
||||
return ip4_addr_check && netif_is_link_up(netif);
|
||||
}
|
||||
|
||||
/** Handler for @c netif status callbacks from LWIP. */
|
||||
static void netif_status_callback(struct netif *netif)
|
||||
{
|
||||
struct mmipal_data *data = mmipal_get_data();
|
||||
enum mmipal_link_state new_link_state = MMIPAL_LINK_DOWN;
|
||||
|
||||
#if LWIP_IPV4
|
||||
if (data->ip4_mode == MMIPAL_DHCP_OFFLOAD) {
|
||||
/* Initialize DHCP offload on link up */
|
||||
if (mmwlan_enable_dhcp_offload(mmipal_dhcp_lease_updated, NULL) != MMWLAN_SUCCESS) {
|
||||
printf("Failed to enable DHCP offload!\n");
|
||||
}
|
||||
|
||||
if (!data->dhcp_offload_init_complete) {
|
||||
/* This just prevents a spurious 'Link Up' message on very first call */
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (mmipal_link_status_check(netif)) {
|
||||
new_link_state = MMIPAL_LINK_UP;
|
||||
}
|
||||
|
||||
if (data->ip_link_state != new_link_state) {
|
||||
data->ip_link_state = new_link_state;
|
||||
|
||||
if (data->link_status_callback || data->ext_link_status_callback) {
|
||||
struct mmipal_link_status link_status;
|
||||
memset(&link_status, 0, sizeof(link_status));
|
||||
link_status.link_state = data->ip_link_state;
|
||||
|
||||
#if LWIP_IPV4
|
||||
char *result = ipaddr_ntoa_r(&netif->ip_addr, link_status.ip_addr, sizeof(link_status.ip_addr));
|
||||
LWIP_ASSERT("IP buf too short", result != NULL);
|
||||
|
||||
result = ipaddr_ntoa_r(&netif->netmask, link_status.netmask, sizeof(link_status.netmask));
|
||||
LWIP_ASSERT("IP buf too short", result != NULL);
|
||||
|
||||
result = ipaddr_ntoa_r(&netif->gw, link_status.gateway, sizeof(link_status.gateway));
|
||||
LWIP_ASSERT("IP buf too short", result != NULL);
|
||||
|
||||
if (data->ip_link_state == MMIPAL_LINK_UP) {
|
||||
/* Check if ARP response offload feature is enabled */
|
||||
if (data->offload_arp_response) {
|
||||
mmwlan_enable_arp_response_offload(ip4_addr_get_u32(netif_ip4_addr(netif)));
|
||||
}
|
||||
|
||||
/* Check if ARP refresh offload feature is enabled */
|
||||
if (data->offload_arp_refresh_s > 0) {
|
||||
mmwlan_enable_arp_refresh_offload(data->offload_arp_refresh_s, ip4_addr_get_u32(netif_ip4_gw(netif)), true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (data->link_status_callback) {
|
||||
data->link_status_callback(&link_status);
|
||||
}
|
||||
if (data->ext_link_status_callback) {
|
||||
data->ext_link_status_callback(&link_status, data->ext_link_status_callback_arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mmipal_set_link_status_callback(mmipal_link_status_cb_fn_t fn)
|
||||
{
|
||||
struct mmipal_data *data = mmipal_get_data();
|
||||
data->link_status_callback = fn;
|
||||
}
|
||||
|
||||
void mmipal_set_ext_link_status_callback(mmipal_ext_link_status_cb_fn_t fn, void *arg)
|
||||
{
|
||||
struct mmipal_data *data = mmipal_get_data();
|
||||
data->ext_link_status_callback = fn;
|
||||
data->ext_link_status_callback_arg = arg;
|
||||
}
|
||||
|
||||
static volatile bool tcpip_init_done = false;
|
||||
|
||||
struct lwip_init_args {
|
||||
enum mmipal_addr_mode mode;
|
||||
enum mmipal_ip6_addr_mode ip6_mode;
|
||||
ip_addr_t ip_addr;
|
||||
ip_addr_t netmask;
|
||||
ip_addr_t gateway_addr;
|
||||
ip_addr_t ip6_addr;
|
||||
};
|
||||
|
||||
static void tcpip_init_done_handler(void *arg)
|
||||
{
|
||||
struct mmipal_data *data = mmipal_get_data();
|
||||
struct netif *netif = &data->lwip_mmnetif;
|
||||
struct lwip_init_args *args = (struct lwip_init_args *)arg;
|
||||
|
||||
netif_add_noaddr(netif, NULL, mmnetif_init, tcpip_input);
|
||||
netif_set_default(netif);
|
||||
netif_set_up(netif);
|
||||
|
||||
#if LWIP_IPV4
|
||||
err_t result;
|
||||
data->ip4_mode = args->mode;
|
||||
if (args->mode == MMIPAL_DHCP) {
|
||||
result = dhcp_start(netif);
|
||||
LWIP_ASSERT("DHCP start error", result == ERR_OK);
|
||||
} else if (args->mode == MMIPAL_STATIC) {
|
||||
netif_set_addr(netif, ip_2_ip4(&(args->ip_addr)), ip_2_ip4(&(args->netmask)), ip_2_ip4(&(args->gateway_addr)));
|
||||
}
|
||||
#endif
|
||||
|
||||
netif_set_link_callback(netif, netif_status_callback);
|
||||
netif_set_status_callback(netif, netif_status_callback);
|
||||
|
||||
#if LWIP_IPV6
|
||||
err_t result6;
|
||||
data->ip6_mode = args->ip6_mode;
|
||||
if (args->ip6_mode == MMIPAL_IP6_STATIC) {
|
||||
netif_ip6_addr_set(netif, 0, ip_2_ip6(&(args->ip6_addr)));
|
||||
netif_ip6_addr_set_state(netif, 0, IP6_ADDR_TENTATIVE);
|
||||
} else if (data->ip6_mode == MMIPAL_IP6_AUTOCONFIG) {
|
||||
netif_set_ip6_autoconfig_enabled(netif, 1);
|
||||
netif_create_ip6_linklocal_address(netif, 1);
|
||||
} else if (data->ip6_mode == MMIPAL_IP6_DHCP6_STATELESS)
|
||||
#if LWIP_IPV6_DHCP6
|
||||
{
|
||||
result6 = dhcp6_enable_stateless(netif);
|
||||
LWIP_ASSERT("Stateless DHCP6 start error", result6 == ERR_OK);
|
||||
}
|
||||
#else
|
||||
{
|
||||
printf("LWIP_IPV6_DHCP6 is not enabled\n");
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
mmosal_free(args);
|
||||
|
||||
tcpip_init_done = true;
|
||||
}
|
||||
|
||||
enum mmipal_status mmipal_init(const struct mmipal_init_args *args)
|
||||
{
|
||||
struct mmipal_data *data = mmipal_get_data();
|
||||
enum mmipal_status status = MMIPAL_INVALID_ARGUMENT;
|
||||
int result;
|
||||
|
||||
struct lwip_init_args *lwip_args = (struct lwip_init_args *)mmosal_malloc(sizeof(*lwip_args));
|
||||
if (lwip_args == NULL) {
|
||||
printf("malloc failure\n");
|
||||
return MMIPAL_NO_MEM;
|
||||
}
|
||||
|
||||
memset(lwip_args, 0, sizeof(*lwip_args));
|
||||
lwip_args->mode = args->mode;
|
||||
lwip_args->ip6_mode = args->ip6_mode;
|
||||
|
||||
data->link_status_callback = NULL;
|
||||
|
||||
data->offload_arp_response = args->offload_arp_response;
|
||||
data->offload_arp_refresh_s = args->offload_arp_refresh_s;
|
||||
|
||||
/* Validate arguments */
|
||||
#if LWIP_IPV4
|
||||
switch (args->mode) {
|
||||
case MMIPAL_DISABLED:
|
||||
printf("%s mode not supported\n", "DISABLED");
|
||||
goto exit;
|
||||
|
||||
case MMIPAL_DHCP_OFFLOAD:
|
||||
case MMIPAL_STATIC:
|
||||
result = ipaddr_aton(args->ip_addr, &lwip_args->ip_addr);
|
||||
if (!result) {
|
||||
goto exit;
|
||||
}
|
||||
result = ipaddr_aton(args->netmask, &lwip_args->netmask);
|
||||
if (!result) {
|
||||
goto exit;
|
||||
}
|
||||
result = ipaddr_aton(args->gateway_addr, &lwip_args->gateway_addr);
|
||||
if (!result) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (ip_addr_isany_val(lwip_args->ip_addr)) {
|
||||
printf("IP address not specified\n");
|
||||
goto exit;
|
||||
}
|
||||
break;
|
||||
|
||||
case MMIPAL_DHCP:
|
||||
if (LWIP_DHCP == 0) {
|
||||
printf("DHCP not compiled in\n");
|
||||
goto exit;
|
||||
}
|
||||
break;
|
||||
|
||||
case MMIPAL_AUTOIP:
|
||||
printf("%s mode not supported\n", "AutoIP");
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if LWIP_IPV6
|
||||
switch (args->ip6_mode) {
|
||||
case MMIPAL_IP6_DISABLED:
|
||||
break;
|
||||
|
||||
case MMIPAL_IP6_STATIC:
|
||||
result = ipaddr_aton(args->ip6_addr, &lwip_args->ip6_addr);
|
||||
if (!result) {
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (ip_addr_isany_val(lwip_args->ip6_addr)) {
|
||||
printf("IP address not specified\n");
|
||||
goto exit;
|
||||
}
|
||||
break;
|
||||
|
||||
case MMIPAL_IP6_AUTOCONFIG:
|
||||
if (LWIP_IPV6_AUTOCONFIG == 0) {
|
||||
printf("AUTOCONFIG not compiled in\n");
|
||||
goto exit;
|
||||
}
|
||||
break;
|
||||
|
||||
case MMIPAL_IP6_DHCP6_STATELESS:
|
||||
if (LWIP_IPV6_DHCP6_STATELESS == 0) {
|
||||
printf("DHCP6_STATELESS not compiled in\n");
|
||||
goto exit;
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
tcpip_init(tcpip_init_done_handler, lwip_args);
|
||||
|
||||
/* Block until initialisation is complete */
|
||||
while (!tcpip_init_done) {
|
||||
mmosal_task_sleep(10);
|
||||
}
|
||||
|
||||
return MMIPAL_SUCCESS;
|
||||
|
||||
exit:
|
||||
mmosal_free(lwip_args);
|
||||
return status;
|
||||
}
|
||||
|
||||
void mmipal_get_link_packet_counts(uint32_t *tx_packets, uint32_t *rx_packets)
|
||||
{
|
||||
#if LWIP_STATS
|
||||
*tx_packets = lwip_stats.link.xmit;
|
||||
*rx_packets = lwip_stats.link.recv;
|
||||
#else
|
||||
*tx_packets = 0;
|
||||
*rx_packets = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void mmipal_set_tx_qos_tid(uint8_t tid)
|
||||
{
|
||||
struct mmipal_data *data = mmipal_get_data();
|
||||
bool ok = tcpip_init_done;
|
||||
MMOSAL_ASSERT(ok);
|
||||
mmnetif_set_tx_qos_tid(&data->lwip_mmnetif, tid);
|
||||
}
|
||||
|
||||
enum mmipal_link_state mmipal_get_link_state(void)
|
||||
{
|
||||
struct mmipal_data *data = mmipal_get_data();
|
||||
return data->ip_link_state;
|
||||
}
|
||||
|
||||
static enum mmipal_status mmipal_get_local_addr_(ip_addr_t *local_addr, const ip_addr_t *dest_addr)
|
||||
{
|
||||
struct mmipal_data *data = mmipal_get_data();
|
||||
struct netif *netif = &data->lwip_mmnetif;
|
||||
#if LWIP_IPV6
|
||||
if (IP_IS_V6(dest_addr)) {
|
||||
const ip_addr_t *src_addr = ip6_select_source_address(netif, ip_2_ip6(dest_addr));
|
||||
if (src_addr == NULL) {
|
||||
return MMIPAL_NO_LINK;
|
||||
}
|
||||
ip_addr_copy(*local_addr, *src_addr);
|
||||
return MMIPAL_SUCCESS;
|
||||
} else {
|
||||
MM_UNUSED(dest_addr);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if LWIP_IPV4
|
||||
if (IP_IS_V4(dest_addr)) {
|
||||
ip_addr_copy(*local_addr, netif->ip_addr);
|
||||
return MMIPAL_SUCCESS;
|
||||
} else {
|
||||
MM_UNUSED(dest_addr);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !LWIP_IPV4 && !LWIP_IPV6
|
||||
MM_UNUSED(local_addr);
|
||||
MM_UNUSED(dest_addr);
|
||||
#endif
|
||||
|
||||
return MMIPAL_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
enum mmipal_status mmipal_get_local_addr(mmipal_ip_addr_t local_addr, const mmipal_ip_addr_t dest_addr)
|
||||
{
|
||||
ip_addr_t lwip_dest_addr;
|
||||
ip_addr_t lwip_local_addr;
|
||||
int ok;
|
||||
enum mmipal_status status;
|
||||
|
||||
if (dest_addr == NULL) {
|
||||
return MMIPAL_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
ok = ipaddr_aton(dest_addr, &lwip_dest_addr);
|
||||
if (!ok) {
|
||||
return MMIPAL_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
status = mmipal_get_local_addr_(&lwip_local_addr, &lwip_dest_addr);
|
||||
if (status != 0) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if (ipaddr_ntoa_r(&lwip_local_addr, local_addr, MMIPAL_IPADDR_STR_MAXLEN) == NULL) {
|
||||
return MMIPAL_NO_MEM;
|
||||
} else {
|
||||
return MMIPAL_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
enum mmipal_status mmipal_set_dns_server(uint8_t index, const mmipal_ip_addr_t addr)
|
||||
{
|
||||
ip_addr_t dns_addr;
|
||||
int ok;
|
||||
|
||||
if (index >= DNS_MAX_SERVERS) {
|
||||
return MMIPAL_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
ok = ipaddr_aton(addr, &dns_addr);
|
||||
if (!ok) {
|
||||
return MMIPAL_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
dns_setserver(index, &dns_addr);
|
||||
return MMIPAL_SUCCESS;
|
||||
}
|
||||
|
||||
enum mmipal_status mmipal_get_dns_server(uint8_t index, mmipal_ip_addr_t addr)
|
||||
{
|
||||
const ip_addr_t *dns_addr;
|
||||
|
||||
if (index >= DNS_MAX_SERVERS) {
|
||||
return MMIPAL_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
dns_addr = dns_getserver(index);
|
||||
|
||||
#if LWIP_IPV4
|
||||
/* dns_getserver() returns ip_addr_any if no address configured. */
|
||||
if (!memcmp(dns_addr, &ip_addr_any, sizeof(*dns_addr))) {
|
||||
addr[0] = '\0';
|
||||
return MMIPAL_SUCCESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ipaddr_ntoa_r(dns_addr, addr, MMIPAL_IPADDR_STR_MAXLEN) == NULL) {
|
||||
return MMIPAL_NO_MEM;
|
||||
} else {
|
||||
return MMIPAL_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,218 @@
|
||||
#ifdef USE_MM_IOT_ESP32
|
||||
/*
|
||||
* Copyright 2021-2023 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "mmnetif.h"
|
||||
#include "mmosal.h"
|
||||
#include "mmwlan.h"
|
||||
|
||||
#include "lwip/etharp.h"
|
||||
#include "lwip/ethip6.h"
|
||||
#include "lwip/tcpip.h"
|
||||
#if LWIP_SNMP
|
||||
#include "lwip/snmp.h"
|
||||
#endif
|
||||
|
||||
struct netif_state {
|
||||
volatile uint8_t tx_qos_tid;
|
||||
};
|
||||
|
||||
static struct netif_state *get_netif_state(struct netif *netif)
|
||||
{
|
||||
MMOSAL_ASSERT(netif->state != NULL);
|
||||
return (struct netif_state *)netif->state;
|
||||
}
|
||||
|
||||
/** pbuf wrapper around an mmpkt. */
|
||||
struct mmpkt_pbuf_wrapper {
|
||||
struct pbuf_custom p;
|
||||
struct mmpkt *pkt;
|
||||
struct mmpktview *pktview;
|
||||
};
|
||||
|
||||
LWIP_MEMPOOL_DECLARE(RX_POOL, MMPKTMEM_RX_POOL_N_BLOCKS, sizeof(struct mmpkt_pbuf_wrapper), "mmpkt_rx");
|
||||
|
||||
static void mmpkt_pbuf_wrapper_free(struct pbuf *p)
|
||||
{
|
||||
struct mmpkt_pbuf_wrapper *pbuf = (struct mmpkt_pbuf_wrapper *)p;
|
||||
if (p == NULL) {
|
||||
return;
|
||||
}
|
||||
mmpkt_close(&pbuf->pktview);
|
||||
mmpkt_release(pbuf->pkt);
|
||||
LWIP_MEMPOOL_FREE(RX_POOL, pbuf);
|
||||
}
|
||||
|
||||
static void mmnetif_rx(struct mmpkt *rxpkt, void *arg)
|
||||
{
|
||||
struct netif *netif = (struct netif *)arg;
|
||||
LWIP_ASSERT("arg NULL", netif != NULL);
|
||||
|
||||
LWIP_DEBUGF(NETIF_DEBUG, ("mmnetif: packet received\n"));
|
||||
|
||||
struct mmpkt_pbuf_wrapper *pbuf = (struct mmpkt_pbuf_wrapper *)LWIP_MEMPOOL_ALLOC(RX_POOL);
|
||||
if (pbuf != NULL) {
|
||||
struct pbuf *p;
|
||||
|
||||
pbuf->p.custom_free_function = mmpkt_pbuf_wrapper_free;
|
||||
pbuf->pkt = rxpkt;
|
||||
pbuf->pktview = mmpkt_open(pbuf->pkt);
|
||||
|
||||
p = pbuf_alloced_custom(PBUF_RAW, mmpkt_get_data_length(pbuf->pktview), PBUF_REF, &pbuf->p,
|
||||
mmpkt_get_data_start(pbuf->pktview), mmpkt_get_data_length(pbuf->pktview));
|
||||
int ret = tcpip_input(p, netif);
|
||||
if (ret == ERR_OK) {
|
||||
LINK_STATS_INC(link.recv);
|
||||
} else {
|
||||
LWIP_DEBUGF(NETIF_DEBUG, ("mmnetif: input error\n"));
|
||||
pbuf_free(p);
|
||||
LINK_STATS_INC(link.memerr);
|
||||
LINK_STATS_INC(link.drop);
|
||||
}
|
||||
} else {
|
||||
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("mmnetif: alloc error\n"));
|
||||
LINK_STATS_INC(link.memerr);
|
||||
mmpkt_release(rxpkt);
|
||||
}
|
||||
}
|
||||
|
||||
static void mmnetif_link_state(enum mmwlan_link_state link_state, void *arg)
|
||||
{
|
||||
struct netif *netif = (struct netif *)arg;
|
||||
LWIP_ASSERT("arg NULL", netif != NULL);
|
||||
|
||||
LOCK_TCPIP_CORE();
|
||||
if (link_state == MMWLAN_LINK_DOWN) {
|
||||
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_ALL, ("mmnetif: link down\n"));
|
||||
/* Note: we cast netif_set_link_down to tcpip_callback_fn since the tcpip_callback_fn
|
||||
* has a "void *" parameter and netif_set_link_down has "struct netif *". */
|
||||
err_t err = tcpip_callback_with_block((tcpip_callback_fn)netif_set_link_down, netif, 0);
|
||||
LWIP_ASSERT("sched callback failed", err == ERR_OK);
|
||||
} else {
|
||||
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_ALL, ("mmnetif: link up\n"));
|
||||
/* Note: we cast netif_set_link_down to tcpip_callback_fn since the tcpip_callback_fn
|
||||
* has a "void *" parameter and netif_set_link_down has "struct netif *". */
|
||||
err_t err = tcpip_callback_with_block((tcpip_callback_fn)netif_set_link_up, netif, 0);
|
||||
LWIP_ASSERT("sched callback failed", err == ERR_OK);
|
||||
}
|
||||
UNLOCK_TCPIP_CORE();
|
||||
}
|
||||
|
||||
static err_t mmnetif_tx(struct netif *netif, struct pbuf *p)
|
||||
{
|
||||
struct mmpkt *pkt;
|
||||
struct mmpktview *pktview;
|
||||
enum mmwlan_status status;
|
||||
struct pbuf *walk;
|
||||
struct mmwlan_tx_metadata metadata = {
|
||||
.tid = get_netif_state(netif)->tx_qos_tid,
|
||||
};
|
||||
|
||||
status = mmwlan_tx_wait_until_ready(1000);
|
||||
if (status != MMWLAN_SUCCESS) {
|
||||
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("mmnetif: transmit blocked\n"));
|
||||
LINK_STATS_INC(link.drop);
|
||||
return ERR_BUF;
|
||||
}
|
||||
|
||||
pkt = mmwlan_alloc_mmpkt_for_tx(p->tot_len, metadata.tid);
|
||||
if (pkt == NULL) {
|
||||
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("mmnetif: allocation failure\n"));
|
||||
LINK_STATS_INC(link.memerr);
|
||||
return ERR_MEM;
|
||||
}
|
||||
pktview = mmpkt_open(pkt);
|
||||
for (walk = p; walk != NULL; walk = walk->next) {
|
||||
mmpkt_append_data(pktview, (const uint8_t *)walk->payload, walk->len);
|
||||
}
|
||||
mmpkt_close(&pktview);
|
||||
|
||||
status = mmwlan_tx_pkt(pkt, &metadata);
|
||||
if (status != MMWLAN_SUCCESS) {
|
||||
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("mmnetif: error sending packet\n"));
|
||||
LINK_STATS_INC(link.drop);
|
||||
return ERR_BUF;
|
||||
}
|
||||
|
||||
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_ALL, ("mmnetif: packet sent\n"));
|
||||
LINK_STATS_INC(link.xmit);
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
err_t mmnetif_init(struct netif *netif)
|
||||
{
|
||||
static bool initialised = false;
|
||||
if (initialised) {
|
||||
return ERR_IF;
|
||||
}
|
||||
|
||||
LWIP_MEMPOOL_INIT(RX_POOL);
|
||||
|
||||
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_ALL, ("mmnetif: initialising mmnetif\n"));
|
||||
|
||||
#if LWIP_SNMP
|
||||
NETIF_INIT_SNMP(netif, snmp_ifType_ethernet_csmacd, 1000000UL);
|
||||
#endif
|
||||
|
||||
enum mmwlan_status status;
|
||||
|
||||
/* Boot the transceiver so that we can read the MAC address. */
|
||||
struct mmwlan_boot_args boot_args = MMWLAN_BOOT_ARGS_INIT;
|
||||
status = mmwlan_boot(&boot_args);
|
||||
if (status != MMWLAN_SUCCESS) {
|
||||
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_SEVERE, ("mmwlan_boot failed with code %d\n", status));
|
||||
}
|
||||
MMOSAL_ASSERT(status == MMWLAN_SUCCESS);
|
||||
|
||||
/* Set MAC hardware address */
|
||||
netif->hwaddr_len = MMWLAN_MAC_ADDR_LEN;
|
||||
status = mmwlan_get_mac_addr(netif->hwaddr);
|
||||
MMOSAL_ASSERT(status == MMWLAN_SUCCESS);
|
||||
|
||||
netif->mtu = 1500;
|
||||
#if LWIP_IPV4 && !LWIP_IPV6
|
||||
netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_IGMP;
|
||||
#else
|
||||
netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_IGMP | NETIF_FLAG_MLD6;
|
||||
#endif
|
||||
|
||||
netif->state = NULL;
|
||||
netif->name[0] = 'M';
|
||||
netif->name[1] = 'M';
|
||||
|
||||
#if LWIP_IPV4
|
||||
netif->output = etharp_output;
|
||||
#endif
|
||||
#if LWIP_IPV6
|
||||
netif->output_ip6 = ethip6_output;
|
||||
#endif
|
||||
netif->linkoutput = mmnetif_tx;
|
||||
|
||||
struct netif_state *state = (struct netif_state *)mmosal_malloc(sizeof(*state));
|
||||
MMOSAL_ASSERT(state != NULL);
|
||||
state->tx_qos_tid = MMWLAN_TX_DEFAULT_QOS_TID;
|
||||
netif->state = state;
|
||||
|
||||
status = mmwlan_register_rx_pkt_cb(mmnetif_rx, netif);
|
||||
MMOSAL_ASSERT(status == MMWLAN_SUCCESS);
|
||||
status = mmwlan_register_link_state_cb(mmnetif_link_state, netif);
|
||||
MMOSAL_ASSERT(status == MMWLAN_SUCCESS);
|
||||
|
||||
printf("Morse LwIP interface initialised. MAC address %02x:%02x:%02x:%02x:%02x:%02x\n", netif->hwaddr[0], netif->hwaddr[1],
|
||||
netif->hwaddr[2], netif->hwaddr[3], netif->hwaddr[4], netif->hwaddr[5]);
|
||||
|
||||
initialised = true;
|
||||
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
void mmnetif_set_tx_qos_tid(struct netif *netif, uint8_t tid)
|
||||
{
|
||||
MMOSAL_ASSERT(tid <= MMWLAN_MAX_QOS_TID);
|
||||
get_netif_state(netif)->tx_qos_tid = tid;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2021-2023 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "lwip/err.h"
|
||||
#include "lwip/netif.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Initializer for the Morse Micro network interface */
|
||||
err_t mmnetif_init(struct netif *netif);
|
||||
|
||||
/**
|
||||
* Configure the QoS TID for the @c netif. QoS data will be sent using this TID.
|
||||
*
|
||||
* @param netif The @c netif to configure.
|
||||
* @param tid The TID value to set.
|
||||
*/
|
||||
void mmnetif_set_tx_qos_tid(struct netif *netif, uint8_t tid);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,563 @@
|
||||
#ifdef USE_MM_IOT_ESP32
|
||||
/*
|
||||
* Copyright 2021-2023 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "esp_debug_helpers.h"
|
||||
#include "esp_private/startup_internal.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/timers.h"
|
||||
#include "rom/ets_sys.h"
|
||||
|
||||
#include "mmhal.h"
|
||||
#include "mmosal.h"
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
/** Maximum number of failure records to store (must be a power of 2). */
|
||||
#define MAX_FAILURE_RECORDS 4
|
||||
|
||||
/** Fast implementation of _x % _m where _m is a power of 2. */
|
||||
#define FAST_MOD(_x, _m) ((_x) & ((_m)-1))
|
||||
|
||||
/** Duration to delay before resetting the device on assert. */
|
||||
#define DELAY_BEFORE_RESET_MS 1000
|
||||
|
||||
/** Data structure for assertion information to be preserved. */
|
||||
struct mmosal_preserved_failure_info {
|
||||
/** Magic number, to check if the info is valid. */
|
||||
uint32_t magic;
|
||||
|
||||
/** Number of failures recorded. */
|
||||
uint32_t failure_count;
|
||||
|
||||
/** Number of most recently displayed failure. */
|
||||
uint32_t displayed_failure_count;
|
||||
|
||||
/** Preserved information from the most recent failure(s). */
|
||||
struct mmosal_failure_info info[MAX_FAILURE_RECORDS];
|
||||
};
|
||||
|
||||
/** Magic number to put in @c mmosal_assert_info.magic to indicate that the assertion info
|
||||
* is valid. */
|
||||
#define ASSERT_INFO_MAGIC (0xabcd1234)
|
||||
|
||||
/* Persistent assertion info. Linker script should put this into memory that is not
|
||||
* zeroed on boot. Be careful to update linker script if renaming. */
|
||||
struct mmosal_preserved_failure_info preserved_failure_info __attribute__((section(".noinit")));
|
||||
|
||||
void mmosal_log_failure_info(const struct mmosal_failure_info *info)
|
||||
{
|
||||
uint32_t record_num;
|
||||
|
||||
if (preserved_failure_info.magic != ASSERT_INFO_MAGIC) {
|
||||
preserved_failure_info.failure_count = 0;
|
||||
preserved_failure_info.displayed_failure_count = 0;
|
||||
}
|
||||
|
||||
preserved_failure_info.magic = ASSERT_INFO_MAGIC;
|
||||
record_num = FAST_MOD(preserved_failure_info.failure_count, MAX_FAILURE_RECORDS);
|
||||
preserved_failure_info.failure_count++;
|
||||
memcpy(&preserved_failure_info.info[record_num], info, sizeof(*info));
|
||||
}
|
||||
|
||||
static void mmosal_dump_failure_info(void)
|
||||
{
|
||||
unsigned first_failure_num = preserved_failure_info.displayed_failure_count;
|
||||
unsigned new_failure_count = preserved_failure_info.failure_count - preserved_failure_info.displayed_failure_count;
|
||||
unsigned failure_offset;
|
||||
|
||||
if (new_failure_count >= MAX_FAILURE_RECORDS) {
|
||||
first_failure_num = FAST_MOD(preserved_failure_info.failure_count, MAX_FAILURE_RECORDS);
|
||||
new_failure_count = MAX_FAILURE_RECORDS;
|
||||
}
|
||||
|
||||
for (failure_offset = 0; failure_offset < new_failure_count; failure_offset++) {
|
||||
unsigned ii;
|
||||
unsigned idx = FAST_MOD(first_failure_num + failure_offset, MAX_FAILURE_RECORDS);
|
||||
struct mmosal_failure_info *info = &preserved_failure_info.info[idx];
|
||||
|
||||
ets_printf("Failure %u logged at pc 0x%08lx, lr 0x%08lx, line %ld in %08lx\n", first_failure_num + failure_offset,
|
||||
info->pc, info->lr, info->line, info->fileid);
|
||||
|
||||
for (ii = 0; ii < sizeof(info->platform_info) / sizeof(info->platform_info[0]); ii++) {
|
||||
ets_printf(" 0x%08lx\n", info->platform_info[ii]);
|
||||
}
|
||||
}
|
||||
|
||||
preserved_failure_info.displayed_failure_count = preserved_failure_info.failure_count;
|
||||
}
|
||||
|
||||
void mmosal_impl_assert(void)
|
||||
{
|
||||
ets_printf("MMOSAL Assert, CPU %d (current core) backtrace", xPortGetCoreID());
|
||||
(void)esp_backtrace_print(100);
|
||||
#ifdef HALT_ON_ASSERT
|
||||
if (preserved_failure_info.magic == ASSERT_INFO_MAGIC) {
|
||||
mmosal_dump_failure_info();
|
||||
}
|
||||
mmosal_disable_interrupts();
|
||||
mmhal_log_flush();
|
||||
MMPORT_BREAKPOINT();
|
||||
#else
|
||||
mmosal_task_sleep(DELAY_BEFORE_RESET_MS);
|
||||
mmhal_reset();
|
||||
#endif
|
||||
while (1) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Function to be called as part of the secondary initialization. See [System
|
||||
* Initialization](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-guides/startup.html#system-initialization)
|
||||
* for more information. */
|
||||
ESP_SYSTEM_INIT_FN(mmosal_dump_failure_info, BIT(0), 999)
|
||||
{
|
||||
if (preserved_failure_info.magic == ASSERT_INFO_MAGIC) {
|
||||
mmosal_dump_failure_info();
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
void *mmosal_malloc_(size_t size)
|
||||
{
|
||||
return pvPortMalloc(size);
|
||||
}
|
||||
|
||||
#ifdef MMOSAL_TRACK_ALLOCATIONS
|
||||
void *mmosal_malloc_dbg(size_t size, const char *name, unsigned line_number)
|
||||
{
|
||||
return pvPortMalloc_dbg(size, name, line_number);
|
||||
}
|
||||
#else
|
||||
void *mmosal_malloc_dbg(size_t size, const char *name, unsigned line_number)
|
||||
{
|
||||
(void)name;
|
||||
(void)line_number;
|
||||
return pvPortMalloc(size);
|
||||
}
|
||||
#endif
|
||||
|
||||
void mmosal_free(void *p)
|
||||
{
|
||||
vPortFree(p);
|
||||
}
|
||||
|
||||
void *mmosal_realloc(void *ptr, size_t size)
|
||||
{
|
||||
return realloc(ptr, size);
|
||||
}
|
||||
|
||||
void *mmosal_calloc(size_t nitems, size_t size)
|
||||
{
|
||||
void *ptr = pvPortMalloc(nitems * size);
|
||||
if (ptr == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memset(ptr, 0, nitems * size);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
struct mmosal_task_arg {
|
||||
mmosal_task_fn_t task_fn;
|
||||
void *task_fn_arg;
|
||||
};
|
||||
|
||||
void mmosal_task_main(void *arg)
|
||||
{
|
||||
struct mmosal_task_arg task_arg = *(struct mmosal_task_arg *)arg;
|
||||
mmosal_free(arg);
|
||||
task_arg.task_fn(task_arg.task_fn_arg);
|
||||
mmosal_task_delete(NULL);
|
||||
}
|
||||
|
||||
struct mmosal_task *mmosal_task_create(mmosal_task_fn_t task_fn, void *argument, enum mmosal_task_priority priority,
|
||||
unsigned stack_size_u32, const char *name)
|
||||
{
|
||||
TaskHandle_t handle;
|
||||
UBaseType_t freertos_priority = tskIDLE_PRIORITY + priority;
|
||||
|
||||
struct mmosal_task_arg *task_arg = (struct mmosal_task_arg *)mmosal_malloc(sizeof(*task_arg));
|
||||
if (task_arg == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
task_arg->task_fn = task_fn;
|
||||
task_arg->task_fn_arg = argument;
|
||||
|
||||
BaseType_t result = xTaskCreate(mmosal_task_main, name, stack_size_u32 * 4, task_arg, freertos_priority, &handle);
|
||||
if (result == pdFAIL) {
|
||||
mmosal_free(task_arg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (struct mmosal_task *)handle;
|
||||
}
|
||||
|
||||
void mmosal_task_delete(struct mmosal_task *task)
|
||||
{
|
||||
vTaskDelete((TaskHandle_t)task);
|
||||
}
|
||||
|
||||
/*
|
||||
* Warning: this function should not be used since eTaskGetState() is not a reliable
|
||||
* means of testing whether a task has completed.
|
||||
*
|
||||
* This function will be removed in future.
|
||||
*/
|
||||
void mmosal_task_join(struct mmosal_task *task)
|
||||
{
|
||||
while (eTaskGetState((TaskHandle_t)task) != eDeleted) {
|
||||
mmosal_task_sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
struct mmosal_task *mmosal_task_get_active(void)
|
||||
{
|
||||
return (struct mmosal_task *)xTaskGetCurrentTaskHandle();
|
||||
}
|
||||
|
||||
void mmosal_task_yield(void)
|
||||
{
|
||||
taskYIELD();
|
||||
}
|
||||
|
||||
void mmosal_task_sleep(uint32_t duration_ms)
|
||||
{
|
||||
vTaskDelay(duration_ms / portTICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
static portMUX_TYPE task_spinlock = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
void mmosal_task_enter_critical(void)
|
||||
{
|
||||
taskENTER_CRITICAL(&task_spinlock);
|
||||
}
|
||||
|
||||
void mmosal_task_exit_critical(void)
|
||||
{
|
||||
taskEXIT_CRITICAL(&task_spinlock);
|
||||
}
|
||||
|
||||
void mmosal_disable_interrupts(void)
|
||||
{
|
||||
taskDISABLE_INTERRUPTS();
|
||||
}
|
||||
|
||||
void mmosal_enable_interrupts(void)
|
||||
{
|
||||
taskENABLE_INTERRUPTS();
|
||||
}
|
||||
|
||||
const char *mmosal_task_name(void)
|
||||
{
|
||||
TaskHandle_t t = xTaskGetCurrentTaskHandle();
|
||||
return pcTaskGetName(t);
|
||||
}
|
||||
|
||||
bool mmosal_task_wait_for_notification(uint32_t timeout_ms)
|
||||
{
|
||||
TickType_t wait = portMAX_DELAY;
|
||||
if (timeout_ms < UINT32_MAX) {
|
||||
wait = pdMS_TO_TICKS(timeout_ms);
|
||||
}
|
||||
uint32_t ret = ulTaskNotifyTake(pdTRUE, /* Act as binary semaphore */
|
||||
wait);
|
||||
return (ret != 0);
|
||||
}
|
||||
|
||||
void mmosal_task_notify(struct mmosal_task *task)
|
||||
{
|
||||
xTaskNotifyGive((TaskHandle_t)task);
|
||||
}
|
||||
|
||||
void mmosal_task_notify_from_isr(struct mmosal_task *task)
|
||||
{
|
||||
BaseType_t higher_priority_task_woken = pdFALSE;
|
||||
vTaskNotifyGiveFromISR((TaskHandle_t)task, &higher_priority_task_woken);
|
||||
portYIELD_FROM_ISR(higher_priority_task_woken);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
struct mmosal_mutex *mmosal_mutex_create(const char *name)
|
||||
{
|
||||
struct mmosal_mutex *mutex = (struct mmosal_mutex *)xSemaphoreCreateMutex();
|
||||
#if (configUSE_TRACE_FACILITY == 1) && defined(ENABLE_TRACEALYZER) && ENABLE_TRACEALYZER
|
||||
if (name != NULL) {
|
||||
vTraceSetMutexName(mutex, name);
|
||||
}
|
||||
#else
|
||||
(void)name;
|
||||
#endif
|
||||
return mutex;
|
||||
}
|
||||
|
||||
void mmosal_mutex_delete(struct mmosal_mutex *mutex)
|
||||
{
|
||||
if (mutex != NULL) {
|
||||
vQueueDelete((SemaphoreHandle_t)mutex);
|
||||
}
|
||||
}
|
||||
|
||||
bool mmosal_mutex_get(struct mmosal_mutex *mutex, uint32_t timeout_ms)
|
||||
{
|
||||
uint32_t timeout_ticks = portMAX_DELAY;
|
||||
if (timeout_ms != UINT32_MAX) {
|
||||
timeout_ticks = timeout_ms / portTICK_PERIOD_MS;
|
||||
}
|
||||
return (xSemaphoreTake((SemaphoreHandle_t)mutex, timeout_ticks) == pdPASS);
|
||||
}
|
||||
|
||||
bool mmosal_mutex_release(struct mmosal_mutex *mutex)
|
||||
{
|
||||
return (xSemaphoreGive((SemaphoreHandle_t)mutex) == pdPASS);
|
||||
}
|
||||
|
||||
bool mmosal_mutex_is_held_by_active_task(struct mmosal_mutex *mutex)
|
||||
{
|
||||
return xSemaphoreGetMutexHolder((SemaphoreHandle_t)mutex) == xTaskGetCurrentTaskHandle();
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
struct mmosal_sem *mmosal_sem_create(unsigned max_count, unsigned initial_count, const char *name)
|
||||
{
|
||||
struct mmosal_sem *sem = (struct mmosal_sem *)xSemaphoreCreateCounting(max_count, initial_count);
|
||||
#if (configUSE_TRACE_FACILITY == 1) && defined(ENABLE_TRACEALYZER) && ENABLE_TRACEALYZER
|
||||
if (name != NULL) {
|
||||
vTraceSetSemaphoreName(sem, name);
|
||||
}
|
||||
#else
|
||||
(void)name;
|
||||
#endif
|
||||
return sem;
|
||||
}
|
||||
|
||||
void mmosal_sem_delete(struct mmosal_sem *sem)
|
||||
{
|
||||
vQueueDelete((SemaphoreHandle_t)sem);
|
||||
}
|
||||
|
||||
bool mmosal_sem_give(struct mmosal_sem *sem)
|
||||
{
|
||||
return xSemaphoreGive((SemaphoreHandle_t)sem);
|
||||
}
|
||||
|
||||
bool mmosal_sem_give_from_isr(struct mmosal_sem *sem)
|
||||
{
|
||||
BaseType_t task_woken = false;
|
||||
BaseType_t ret = xSemaphoreGiveFromISR((SemaphoreHandle_t)sem, &task_woken);
|
||||
if (ret == pdPASS) {
|
||||
portYIELD_FROM_ISR(task_woken);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool mmosal_sem_wait(struct mmosal_sem *sem, uint32_t timeout_ms)
|
||||
{
|
||||
uint32_t timeout_ticks = portMAX_DELAY;
|
||||
if (timeout_ms != UINT32_MAX) {
|
||||
timeout_ticks = timeout_ms / portTICK_PERIOD_MS;
|
||||
}
|
||||
return (xSemaphoreTake((SemaphoreHandle_t)sem, timeout_ticks) == pdPASS);
|
||||
}
|
||||
|
||||
uint32_t mmosal_sem_get_count(struct mmosal_sem *sem)
|
||||
{
|
||||
return uxSemaphoreGetCount((SemaphoreHandle_t)sem);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
struct mmosal_semb *mmosal_semb_create(const char *name)
|
||||
{
|
||||
struct mmosal_semb *semb = (struct mmosal_semb *)xSemaphoreCreateBinary();
|
||||
#if (configUSE_TRACE_FACILITY == 1) && defined(ENABLE_TRACEALYZER) && ENABLE_TRACEALYZER
|
||||
if (name != NULL) {
|
||||
vTraceSetSemaphoreName(semb, name);
|
||||
}
|
||||
#else
|
||||
(void)name;
|
||||
#endif
|
||||
return semb;
|
||||
}
|
||||
|
||||
void mmosal_semb_delete(struct mmosal_semb *semb)
|
||||
{
|
||||
vQueueDelete((SemaphoreHandle_t)semb);
|
||||
}
|
||||
|
||||
bool mmosal_semb_give(struct mmosal_semb *semb)
|
||||
{
|
||||
return (xSemaphoreGive((SemaphoreHandle_t)semb) == pdPASS);
|
||||
}
|
||||
|
||||
bool mmosal_semb_give_from_isr(struct mmosal_semb *semb)
|
||||
{
|
||||
BaseType_t task_woken = pdFALSE;
|
||||
BaseType_t ret = xSemaphoreGiveFromISR((SemaphoreHandle_t)semb, &task_woken);
|
||||
if (ret == pdPASS) {
|
||||
portYIELD_FROM_ISR(task_woken);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool mmosal_semb_wait(struct mmosal_semb *semb, uint32_t timeout_ms)
|
||||
{
|
||||
uint32_t timeout_ticks = portMAX_DELAY;
|
||||
if (timeout_ms != UINT32_MAX) {
|
||||
timeout_ticks = timeout_ms / portTICK_PERIOD_MS;
|
||||
}
|
||||
return (xSemaphoreTake((SemaphoreHandle_t)semb, timeout_ticks) == pdPASS);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
struct mmosal_queue *mmosal_queue_create(size_t num_items, size_t item_size, const char *name)
|
||||
{
|
||||
struct mmosal_queue *queue = (struct mmosal_queue *)xQueueCreate(num_items, item_size);
|
||||
#if (configUSE_TRACE_FACILITY == 1) && defined(ENABLE_TRACEALYZER) && ENABLE_TRACEALYZER
|
||||
if (name != NULL) {
|
||||
vTraceSetQueueName(queue, name);
|
||||
}
|
||||
#else
|
||||
(void)name;
|
||||
#endif
|
||||
return queue;
|
||||
}
|
||||
|
||||
void mmosal_queue_delete(struct mmosal_queue *queue)
|
||||
{
|
||||
vQueueDelete((SemaphoreHandle_t)queue);
|
||||
}
|
||||
|
||||
bool mmosal_queue_pop(struct mmosal_queue *queue, void *item, uint32_t timeout_ms)
|
||||
{
|
||||
uint32_t timeout_ticks = portMAX_DELAY;
|
||||
if (timeout_ms != UINT32_MAX) {
|
||||
timeout_ticks = timeout_ms / portTICK_PERIOD_MS;
|
||||
}
|
||||
return (xQueueReceive((SemaphoreHandle_t)queue, item, timeout_ticks) == pdPASS);
|
||||
}
|
||||
|
||||
bool mmosal_queue_push(struct mmosal_queue *queue, const void *item, uint32_t timeout_ms)
|
||||
{
|
||||
uint32_t timeout_ticks = portMAX_DELAY;
|
||||
if (timeout_ms != UINT32_MAX) {
|
||||
timeout_ticks = timeout_ms / portTICK_PERIOD_MS;
|
||||
}
|
||||
return (xQueueSendToBack((SemaphoreHandle_t)queue, item, timeout_ticks) == pdPASS);
|
||||
}
|
||||
|
||||
bool mmosal_queue_pop_from_isr(struct mmosal_queue *queue, void *item)
|
||||
{
|
||||
BaseType_t task_woken = pdFALSE;
|
||||
if (xQueueReceiveFromISR((SemaphoreHandle_t)queue, item, &task_woken) == pdTRUE) {
|
||||
portYIELD_FROM_ISR(task_woken);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool mmosal_queue_push_from_isr(struct mmosal_queue *queue, const void *item)
|
||||
{
|
||||
BaseType_t task_woken = pdFALSE;
|
||||
if (xQueueSendToBackFromISR((SemaphoreHandle_t)queue, item, &task_woken) == pdTRUE) {
|
||||
portYIELD_FROM_ISR(task_woken);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
uint32_t mmosal_get_time_ms(void)
|
||||
{
|
||||
return xTaskGetTickCount() * portTICK_PERIOD_MS;
|
||||
}
|
||||
|
||||
uint32_t mmosal_get_time_ticks(void)
|
||||
{
|
||||
return xTaskGetTickCount();
|
||||
}
|
||||
|
||||
uint32_t mmosal_ticks_per_second(void)
|
||||
{
|
||||
return portTICK_PERIOD_MS * 1000;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
struct mmosal_timer *mmosal_timer_create(const char *name, uint32_t timer_period, bool auto_reload, void *arg,
|
||||
timer_callback_t callback)
|
||||
{
|
||||
/*
|
||||
* The software timer callback functions execute in the context of a task that is
|
||||
* created automatically when the FreeRTOS scheduler is started. Therefore, it is essential that
|
||||
* software timer callback functions never call FreeRTOS API functions that will result in the
|
||||
* calling task entering the Blocked state. It is ok to call functions such as xQueueReceive(), but
|
||||
* only if the function’s xTicksToWait parameter (which specifies the function’s block time) is set
|
||||
* to 0. It is not ok to call functions such as vTaskDelay(), as calling vTaskDelay() will always
|
||||
* place the calling task into the Blocked state.
|
||||
*/
|
||||
return (struct mmosal_timer *)xTimerCreate(name, pdMS_TO_TICKS(timer_period), (UBaseType_t)auto_reload, arg,
|
||||
(TimerCallbackFunction_t)callback);
|
||||
}
|
||||
|
||||
void mmosal_timer_delete(struct mmosal_timer *timer)
|
||||
{
|
||||
if (timer != NULL) {
|
||||
BaseType_t ret = xTimerDelete((TimerHandle_t)timer, 0);
|
||||
configASSERT(ret == pdPASS);
|
||||
}
|
||||
}
|
||||
|
||||
bool mmosal_timer_start(struct mmosal_timer *timer)
|
||||
{
|
||||
BaseType_t ret = xTimerStart((TimerHandle_t)timer, 0);
|
||||
|
||||
return (ret == pdPASS);
|
||||
}
|
||||
|
||||
bool mmosal_timer_stop(struct mmosal_timer *timer)
|
||||
{
|
||||
BaseType_t ret = xTimerStop((TimerHandle_t)timer, 0);
|
||||
|
||||
return (ret == pdPASS);
|
||||
}
|
||||
|
||||
bool mmosal_timer_change_period(struct mmosal_timer *timer, uint32_t new_period)
|
||||
{
|
||||
BaseType_t ret = xTimerChangePeriod((TimerHandle_t)timer, pdMS_TO_TICKS(new_period), 0);
|
||||
|
||||
return (ret == pdPASS);
|
||||
}
|
||||
|
||||
void *mmosal_timer_get_arg(struct mmosal_timer *timer)
|
||||
{
|
||||
return pvTimerGetTimerID((TimerHandle_t)timer);
|
||||
}
|
||||
|
||||
bool mmosal_is_timer_active(struct mmosal_timer *timer)
|
||||
{
|
||||
BaseType_t ret = xTimerIsTimerActive((TimerHandle_t)timer);
|
||||
|
||||
return (ret != pdFALSE);
|
||||
}
|
||||
|
||||
#endif /* USE_MM_IOT_ESP32 */
|
||||
@@ -0,0 +1,245 @@
|
||||
#ifdef USE_MM_IOT_ESP32
|
||||
/*
|
||||
* Copyright 2024 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "mmhal.h"
|
||||
#include "mmosal.h"
|
||||
#include "mmpkt.h"
|
||||
#include "mmpkt_list.h"
|
||||
#include "mmutils.h"
|
||||
|
||||
/* MMPKTMEM_TX_POOL_N_BLOCKS and MMPKTMEM_RX_POOL_N_BLOCKS provide an upper bound on the number
|
||||
* of packets we will allocate in the transmit and receive directions respectively. */
|
||||
|
||||
#ifndef MMPKTMEM_TX_POOL_N_BLOCKS
|
||||
#error MMPKTMEM_TX_POOL_N_BLOCKS not defined
|
||||
#endif
|
||||
|
||||
#ifndef MMPKTMEM_RX_POOL_N_BLOCKS
|
||||
#error MMPKTMEM_RX_POOL_N_BLOCKS not defined
|
||||
#endif
|
||||
|
||||
/* Packet pool for data/management frames configuration. */
|
||||
#define TX_DATA_POOL_UNPAUSE_THRESHOLD (MMPKTMEM_TX_POOL_N_BLOCKS - 2)
|
||||
#define TX_DATA_POOL_PAUSE_THRESHOLD (MMPKTMEM_TX_POOL_N_BLOCKS - 1)
|
||||
|
||||
/* Packet pool for commands configuration. */
|
||||
#define TX_COMMAND_POOL_BLOCK_SIZE (256)
|
||||
#define TX_COMMAND_POOL_N_BLOCKS (2)
|
||||
|
||||
#ifndef MMPKT_LOG
|
||||
#define MMPKT_LOG(...) printf(__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
struct pktmem_data {
|
||||
/** Count of allocated tx packets (excluding command pool -- see below). */
|
||||
volatile atomic_int_least32_t tx_data_pool_allocated;
|
||||
/** Boolean tracking whether the data path is currently paused. */
|
||||
volatile atomic_uint_fast8_t tx_data_pool_tx_paused;
|
||||
/** Count of allocated rx packets. */
|
||||
volatile atomic_int_least32_t rx_pool_allocated;
|
||||
|
||||
/** Command pool free (unallocated) packet list. */
|
||||
struct mmpkt_list tx_command_pool_free_list;
|
||||
/** Statically allocated memory for the command pool. */
|
||||
uint8_t tx_command_pool[TX_COMMAND_POOL_BLOCK_SIZE * TX_COMMAND_POOL_N_BLOCKS];
|
||||
|
||||
/** Flow control callback function pointer. */
|
||||
mmhal_wlan_pktmem_tx_flow_control_cb_t tx_flow_control_cb;
|
||||
};
|
||||
|
||||
static struct pktmem_data pktmem;
|
||||
|
||||
void mmhal_wlan_pktmem_init(struct mmhal_wlan_pktmem_init_args *args)
|
||||
{
|
||||
unsigned ii;
|
||||
|
||||
memset(&pktmem, 0, sizeof(pktmem));
|
||||
|
||||
pktmem.tx_flow_control_cb = args->tx_flow_control_cb;
|
||||
|
||||
/* Initialize the free (unallocated) packet list of the transmit command pool. */
|
||||
for (ii = 0; ii < TX_COMMAND_POOL_N_BLOCKS; ii++) {
|
||||
size_t offset = TX_COMMAND_POOL_BLOCK_SIZE * ii;
|
||||
mmpkt_list_append(&pktmem.tx_command_pool_free_list, (struct mmpkt *)(pktmem.tx_command_pool + offset));
|
||||
}
|
||||
}
|
||||
|
||||
void mmhal_wlan_pktmem_deinit(void)
|
||||
{
|
||||
size_t ii;
|
||||
|
||||
/* If there is still memory allocated, allow some time for other threads to clean up. */
|
||||
for (ii = 0; ii < 100; ii++) {
|
||||
if ((pktmem.tx_command_pool_free_list.len | pktmem.tx_data_pool_allocated | pktmem.tx_data_pool_allocated) == 0) {
|
||||
break;
|
||||
}
|
||||
mmosal_task_sleep(10);
|
||||
}
|
||||
|
||||
/* Check for memory leaks. */
|
||||
if (pktmem.tx_data_pool_allocated != 0) {
|
||||
MMPKT_LOG("Potential memory leak: %d %s pool allocations at deinit\n", (int)pktmem.tx_data_pool_allocated, "data");
|
||||
}
|
||||
|
||||
if (pktmem.tx_command_pool_free_list.len != TX_COMMAND_POOL_N_BLOCKS) {
|
||||
MMPKT_LOG("Potential memory leak: %d %s pool allocations at deinit\n",
|
||||
TX_COMMAND_POOL_N_BLOCKS - (int)pktmem.tx_command_pool_free_list.len, "command");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* --------------------------------------------------------------------------------------
|
||||
* Command pool
|
||||
* --------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
static void tx_command_reserved_free(void *mmpkt)
|
||||
{
|
||||
struct mmpkt *pkt = (struct mmpkt *)mmpkt;
|
||||
MMOSAL_TASK_ENTER_CRITICAL();
|
||||
mmpkt_list_append(&pktmem.tx_command_pool_free_list, pkt);
|
||||
MMOSAL_TASK_EXIT_CRITICAL();
|
||||
}
|
||||
|
||||
static const struct mmpkt_ops tx_command_pool_ops = {
|
||||
.free_mmpkt = tx_command_reserved_free,
|
||||
};
|
||||
|
||||
static struct mmpkt *alloc_pkt_from_list(struct mmpkt_list *list, uint32_t pktbufsize, uint32_t space_at_start,
|
||||
uint32_t space_at_end, uint32_t metadata_length)
|
||||
{
|
||||
struct mmpkt *mmpkt_buf;
|
||||
struct mmpkt *mmpkt;
|
||||
|
||||
MMOSAL_TASK_ENTER_CRITICAL();
|
||||
mmpkt_buf = mmpkt_list_dequeue(list);
|
||||
MMOSAL_TASK_EXIT_CRITICAL();
|
||||
|
||||
if (mmpkt_buf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mmpkt = mmpkt_init_buf((uint8_t *)mmpkt_buf, pktbufsize, space_at_start, space_at_end, metadata_length, &tx_command_pool_ops);
|
||||
if (mmpkt == NULL) {
|
||||
/* Command was too big for the reserved buffer. Return the reserved buffer. */
|
||||
tx_command_reserved_free(mmpkt_buf);
|
||||
}
|
||||
|
||||
return mmpkt;
|
||||
}
|
||||
|
||||
static struct mmpkt *command_pool_alloc(uint32_t space_at_start, uint32_t space_at_end, uint32_t metadata_length)
|
||||
{
|
||||
return alloc_pkt_from_list(&pktmem.tx_command_pool_free_list, TX_COMMAND_POOL_BLOCK_SIZE, space_at_start, space_at_end,
|
||||
metadata_length);
|
||||
}
|
||||
|
||||
/*
|
||||
* --------------------------------------------------------------------------------------
|
||||
* Data pool
|
||||
* --------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
static void tx_data_pool_pkt_free(void *mmpkt)
|
||||
{
|
||||
atomic_int_least32_t old_value = atomic_fetch_sub(&pktmem.tx_data_pool_allocated, 1);
|
||||
MMOSAL_ASSERT(old_value > 0);
|
||||
mmosal_free(mmpkt);
|
||||
|
||||
if (pktmem.tx_data_pool_allocated < TX_DATA_POOL_UNPAUSE_THRESHOLD) {
|
||||
atomic_uint_fast8_t old_tx_paused = atomic_exchange(&pktmem.tx_data_pool_tx_paused, 0);
|
||||
if (old_tx_paused) {
|
||||
pktmem.tx_flow_control_cb(MMWLAN_TX_READY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const struct mmpkt_ops tx_data_pool_pkt_ops = {
|
||||
.free_mmpkt = tx_data_pool_pkt_free,
|
||||
};
|
||||
|
||||
struct mmpkt *mmhal_wlan_alloc_mmpkt_for_tx(uint8_t pkt_class, uint32_t space_at_start, uint32_t space_at_end,
|
||||
uint32_t metadata_length)
|
||||
{
|
||||
atomic_int_least32_t old_value;
|
||||
struct mmpkt *mmpkt;
|
||||
|
||||
/* For command packets, try allocating from the command pool first. If that fails then
|
||||
* we proceed to allocate from the data pool. */
|
||||
if (pkt_class == MMHAL_WLAN_PKT_COMMAND) {
|
||||
mmpkt = command_pool_alloc(space_at_start, space_at_end, metadata_length);
|
||||
if (mmpkt != NULL) {
|
||||
return mmpkt;
|
||||
}
|
||||
}
|
||||
|
||||
old_value = atomic_fetch_add(&pktmem.tx_data_pool_allocated, 1);
|
||||
|
||||
if (old_value >= MMPKTMEM_TX_POOL_N_BLOCKS) {
|
||||
/* Maximum allocations reached. Do not attempt to increase further. */
|
||||
atomic_fetch_sub(&pktmem.tx_data_pool_allocated, 1);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mmpkt = mmpkt_alloc_on_heap(space_at_start, space_at_end, metadata_length);
|
||||
if (mmpkt == NULL) {
|
||||
atomic_fetch_sub(&pktmem.tx_data_pool_allocated, 1);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mmpkt->ops = &tx_data_pool_pkt_ops;
|
||||
|
||||
if (pktmem.tx_data_pool_allocated > TX_DATA_POOL_PAUSE_THRESHOLD) {
|
||||
atomic_uint_fast8_t old_tx_paused = atomic_exchange(&pktmem.tx_data_pool_tx_paused, 1);
|
||||
if (!old_tx_paused) {
|
||||
pktmem.tx_flow_control_cb(MMWLAN_TX_PAUSED);
|
||||
}
|
||||
}
|
||||
|
||||
return mmpkt;
|
||||
}
|
||||
|
||||
static void rx_pkt_free(void *mmpkt)
|
||||
{
|
||||
if (mmpkt != NULL) {
|
||||
atomic_fetch_sub(&pktmem.rx_pool_allocated, 1);
|
||||
mmosal_free(mmpkt);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct mmpkt_ops mmpkt_rx_ops = {.free_mmpkt = rx_pkt_free};
|
||||
|
||||
struct mmpkt *mmhal_wlan_alloc_mmpkt_for_rx(uint32_t capacity, uint32_t metadata_length)
|
||||
{
|
||||
atomic_int_least32_t old_value;
|
||||
struct mmpkt *mmpkt;
|
||||
|
||||
old_value = atomic_fetch_add(&pktmem.rx_pool_allocated, 1);
|
||||
|
||||
if (old_value >= MMPKTMEM_RX_POOL_N_BLOCKS) {
|
||||
/* Maximum allocations reached. Do not attempt to increase further. */
|
||||
atomic_fetch_sub(&pktmem.rx_pool_allocated, 1);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* For now we do not put an explicit limit on the of packets buffers on the RX path. */
|
||||
mmpkt = mmpkt_alloc_on_heap(0, capacity, metadata_length);
|
||||
if (mmpkt == NULL) {
|
||||
atomic_fetch_sub(&pktmem.rx_pool_allocated, 1);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Override packet ops to use a custom free function that also decrements the
|
||||
* allocation count. */
|
||||
mmpkt->ops = &mmpkt_rx_ops;
|
||||
return mmpkt;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* Copyright 2023 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MMPORT_BREAKPOINT() while (1)
|
||||
#define MMPORT_GET_LR() (__builtin_return_address(0))
|
||||
#define MMPORT_GET_PC(_a) ((_a) = 0) // TODO
|
||||
#define MMPORT_MEM_SYNC() __sync_synchronize()
|
||||
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* Copyright 2024 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup MMUTILS Morse Micro Utilities
|
||||
*
|
||||
* Utility macros and functions to improve quality of life.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Get the minimum of 2 values.
|
||||
*
|
||||
* Note that this macro is not ideal and should be used with caution. Caveats include:
|
||||
* * The two parameters may be evaluated more than once, so should be constant values that
|
||||
* do not have side effects. For example, do NOT do `MM_MIN(a++, b)`.
|
||||
* * There are no explicit constraints on types, so be careful of comparing different integer
|
||||
* types, etc.
|
||||
*
|
||||
* @param _x The first value to compare.
|
||||
* @param _y The second value to compare.
|
||||
*
|
||||
* @returns the minimum of @p _x and @p _y.
|
||||
*/
|
||||
#define MM_MIN(_x, _y) (((_x) < (_y)) ? (_x) : (_y))
|
||||
|
||||
/**
|
||||
* Get the maximum of 2 values.
|
||||
*
|
||||
* Note that this macro is not ideal and should be used with caution. Caveats include:
|
||||
* * The two parameters may be evaluated more than once, so should be constant values that
|
||||
* do not have side effects. For example, do NOT do `MM_MAX(a++, b)`.
|
||||
* * There are no explicit constraints on types, so be careful of comparing different integer
|
||||
* types, etc.
|
||||
*
|
||||
* @param _x The first value to compare.
|
||||
* @param _y The second value to compare.
|
||||
*
|
||||
* @returns the maximum of @p _x and @p _y.
|
||||
*/
|
||||
#define MM_MAX(_x, _y) (((_x) > (_y)) ? (_x) : (_y))
|
||||
|
||||
/**
|
||||
* Round @p x up to the next multiple of @p m (where @p m is a power of 2).
|
||||
*
|
||||
* @warning @p m must be a power of 2.
|
||||
*/
|
||||
#ifndef MM_FAST_ROUND_UP
|
||||
#define MM_FAST_ROUND_UP(x, m) ((((x)-1) | ((m)-1)) + 1)
|
||||
#endif
|
||||
|
||||
/** Casts the given expression to void to avoid "unused" warnings from the compiler. */
|
||||
#define MM_UNUSED(_x) (void)(_x)
|
||||
|
||||
/** Tells the compiler to pack the structure. */
|
||||
#ifndef MM_PACKED
|
||||
#define MM_PACKED __attribute__((packed))
|
||||
#endif
|
||||
|
||||
/** Used to declare a weak symbol. */
|
||||
#ifndef MM_WEAK
|
||||
#define MM_WEAK __attribute__((weak))
|
||||
#endif
|
||||
|
||||
#ifndef MM_STATIC_ASSERT
|
||||
/**
|
||||
* Assertion check that is evaluated at compile time.
|
||||
*
|
||||
* The constant expression, @p _expression, is evaluted at compile time. If zero then
|
||||
* it triggers a compilation error and @p _message is displayed. If non-zero, no code
|
||||
* is emitted.
|
||||
*
|
||||
* @param _expression Constant expression to evaluate. If zero then a compilation error
|
||||
* is triggered.
|
||||
* @param _message Message to display on error.
|
||||
*/
|
||||
#define MM_STATIC_ASSERT(_expression, _message) _Static_assert((_expression), _message)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Return the number of elements in the given array.
|
||||
*
|
||||
* @param _a The array to get the element count for. Note that this must be an _array_
|
||||
* and not a pointer. Beware that array-type function arguments are
|
||||
* actually treated as pointers by the compiler. Must not be NULL.
|
||||
*
|
||||
* @returns the count of elements in the given array.
|
||||
*/
|
||||
#define MM_ARRAY_COUNT(_a) (sizeof(_a) / sizeof((_a)[0]))
|
||||
|
||||
/**
|
||||
* Convert the least significant 4 bits of the given argument to a character representing their
|
||||
* hexadecimal value.
|
||||
*
|
||||
* For example, for input 0xde this will return 'E', for 0x01 it will return '1'.
|
||||
*
|
||||
* @param nibble The input nibble (upper 4 bits will be discarded).
|
||||
*
|
||||
* @return The character that represents the hexadecimal value of the lower 4 bits of @p nibble.
|
||||
* Values greater than 0x09 will be represented with upper case characters.
|
||||
*/
|
||||
static inline char mm_nibble_to_hex_char(uint8_t nibble)
|
||||
{
|
||||
nibble &= 0x0f;
|
||||
if (nibble < 0x0a) {
|
||||
return '0' + nibble;
|
||||
} else {
|
||||
return 'A' + nibble - 0x0a;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @defgroup MMUTILS_WLAN WLAN Utilities
|
||||
*
|
||||
* Utility macros and functions relating to WLAN.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Enumeration of Authentication Key Management (AKM) Suite OUIs as BE32 integers. */
|
||||
enum mm_akm_suite_oui {
|
||||
/** Open (no security) */
|
||||
MM_AKM_SUITE_NONE = 0,
|
||||
/** Pre-shared key (WFA OUI) */
|
||||
MM_AKM_SUITE_PSK = 0x506f9a02,
|
||||
/** Simultaneous Authentication of Equals (SAE) */
|
||||
MM_AKM_SUITE_SAE = 0x000fac08,
|
||||
/** OWE */
|
||||
MM_AKM_SUITE_OWE = 0x000fac12,
|
||||
/** Another suite not in this enum */
|
||||
MM_AKM_SUITE_OTHER = 1,
|
||||
};
|
||||
|
||||
/** Enumeration of Cipher Suite OUIs as BE32 integers. */
|
||||
enum mm_cipher_suite_oui {
|
||||
/** Open (no security) */
|
||||
MM_CIPHER_SUITE_AES_CCM = 0x000fac04,
|
||||
/** Another cipher suite not in this enum */
|
||||
MM_CIPHER_SUITE_OTHER = 1,
|
||||
};
|
||||
|
||||
/** Maximum number of pairwise cipher suites our parser will process. */
|
||||
#ifndef MM_RSN_INFORMATION_MAX_PAIRWISE_CIPHER_SUITES
|
||||
#define MM_RSN_INFORMATION_MAX_PAIRWISE_CIPHER_SUITES (2)
|
||||
#endif
|
||||
|
||||
/** Maximum number of AKM suites our parser will process. */
|
||||
#ifndef MM_RSN_INFORMATION_MAX_AKM_SUITES
|
||||
#define MM_RSN_INFORMATION_MAX_AKM_SUITES (2)
|
||||
#endif
|
||||
|
||||
/** Tag number of the RSN information element, in which we can find security details of the AP. */
|
||||
#define MM_RSN_INFORMATION_IE_TYPE (48)
|
||||
/** Tag number of the Vendor Specific information element. */
|
||||
#define MM_VENDOR_SPECIFIC_IE_TYPE (221)
|
||||
|
||||
/** Explicitly defined errno values to obviate the need to include errno.h. MM prefix to
|
||||
* avoid namespace collision in case errno.h gets included. */
|
||||
enum mm_errno {
|
||||
MM_ENOMEM = 12,
|
||||
MM_EFAULT = 14,
|
||||
MM_ENODEV = 19,
|
||||
MM_EINVAL = 22,
|
||||
MM_ETIMEDOUT = 110,
|
||||
};
|
||||
|
||||
/**
|
||||
* Data structure to represent information extracted from an RSN information element.
|
||||
*
|
||||
* All integers in host order.
|
||||
*/
|
||||
struct mm_rsn_information {
|
||||
/** The group cipher suite OUI. */
|
||||
uint32_t group_cipher_suite;
|
||||
/** Pairwise cipher suite OUIs. Count given by @c num_pairwise_cipher_suites. */
|
||||
uint32_t pairwise_cipher_suites[MM_RSN_INFORMATION_MAX_PAIRWISE_CIPHER_SUITES];
|
||||
/** AKM suite OUIs. Count given by @c num_akm_suites. */
|
||||
uint32_t akm_suites[MM_RSN_INFORMATION_MAX_AKM_SUITES];
|
||||
/** Number of pairwise cipher suites in @c pairwise_cipher_suites. */
|
||||
uint16_t num_pairwise_cipher_suites;
|
||||
/** Number of AKM suites in @c akm_suites. */
|
||||
uint16_t num_akm_suites;
|
||||
/** Version number of the RSN IE. */
|
||||
uint16_t version;
|
||||
/** RSN Capabilities field of the RSN IE (in host order). */
|
||||
uint16_t rsn_capabilities;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the name of the given AKM Suite as a string.
|
||||
*
|
||||
* @param akm_suite_oui The OUI of the AKM suite as a big endian integer.
|
||||
*
|
||||
* @returns the string representation.
|
||||
*/
|
||||
const char *mm_akm_suite_to_string(uint32_t akm_suite_oui);
|
||||
|
||||
/**
|
||||
* Search a list of Information Elements (IEs) from the given starting offset and find the first
|
||||
* instance of matching the given type.
|
||||
*
|
||||
* @warning A @p search_offset that is not aligned to the start of an IE header will result in
|
||||
* undefined behaviour.
|
||||
*
|
||||
* @param ies Buffer containing the information elements.
|
||||
* @param ies_len Length of @p ies
|
||||
* @param search_offset Offset to start searching from. This **must** point to a IE header.
|
||||
* @param ie_type The type of the IE to look for.
|
||||
*
|
||||
* @return If the information element is found, the offset of the start of the IE within @p ies; if
|
||||
* no match is found then -1; if the IE is found but is malformed then -2.
|
||||
*/
|
||||
int mm_find_ie_from_offset(const uint8_t *ies, uint32_t ies_len, uint32_t search_offset, uint8_t ie_type);
|
||||
|
||||
/**
|
||||
* Search a list of Information Elements (IEs) and find the first instance of matching the
|
||||
* given type.
|
||||
*
|
||||
* @param ies Buffer containing the information elements.
|
||||
* @param ies_len Length of @p ies
|
||||
* @param ie_type The type of the IE to look for.
|
||||
*
|
||||
* @return If the information element is found, the offset of the start of the IE within @p ies;
|
||||
* if no match is found then -1; if the IE is found but is malformed then -2.
|
||||
*/
|
||||
static inline int mm_find_ie(const uint8_t *ies, uint32_t ies_len, uint8_t ie_type)
|
||||
{
|
||||
return mm_find_ie_from_offset(ies, ies_len, 0, ie_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search through the given list of Information Elements (IEs) from the given starting offset to
|
||||
* find the first Vendor Specific IE that matches the given id.
|
||||
*
|
||||
* @warning A @p search_offset that is not aligned to the start of an IE header will result in
|
||||
* undefined behaviour.
|
||||
*
|
||||
* @param[in] ies Buffer containing the information elements.
|
||||
* @param[in] ies_len Length of @p ies
|
||||
* @param[in] search_offset Offset to start searching from. This **must** point to a IE header.
|
||||
* @param[in] id Buffer containing the IE ID, usually OUI+TYPE.
|
||||
* @param[in] id_len Length of the ID.
|
||||
*
|
||||
* @return If the information element is found, the offset of the start of the IE within @p ies; if
|
||||
* no match is found then -1; if the IE is found but is malformed then -2.
|
||||
*/
|
||||
int mm_find_vendor_specific_ie_from_offset(const uint8_t *ies, uint32_t ies_len, uint32_t search_offset, const uint8_t *id,
|
||||
size_t id_len);
|
||||
|
||||
/**
|
||||
* Search through the given list of Information Elements (IEs) to find the first Vendor Specific IE
|
||||
* that matches the given id.
|
||||
*
|
||||
* @param[in] ies Buffer containing the information elements.
|
||||
* @param[in] ies_len Length of @p ies
|
||||
* @param[in] id Buffer containing the IE ID, usually OUI+TYPE.
|
||||
* @param[in] id_len Length of the ID.
|
||||
*
|
||||
* @return If the information element is found, the offset of the start of the IE within @p ies; if
|
||||
* no match is found then -1; if the IE is found but is malformed then -2.
|
||||
*/
|
||||
static inline int mm_find_vendor_specific_ie(const uint8_t *ies, uint32_t ies_len, const uint8_t *id, size_t id_len)
|
||||
{
|
||||
return mm_find_vendor_specific_ie_from_offset(ies, ies_len, 0, id, id_len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search through the given list of information elements to find the RSN IE then parse it
|
||||
* to extract relevant information into an instance of @ref mm_rsn_information.
|
||||
*
|
||||
* @param[in] ies Buffer containing the information elements.
|
||||
* @param[in] ies_len Length of @p ies
|
||||
* @param[out] output Pointer to an instance of @ref mm_rsn_information to receive output.
|
||||
*
|
||||
* @returns -2 on parse error, -1 if the RSN IE was not found, 0 if the RSN IE was found.
|
||||
*/
|
||||
int mm_parse_rsn_information(const uint8_t *ies, uint32_t ies_len, struct mm_rsn_information *output);
|
||||
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
@@ -0,0 +1,163 @@
|
||||
#ifdef USE_MM_IOT_ESP32
|
||||
/*
|
||||
* Copyright 2024 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "mmutils.h"
|
||||
|
||||
const char *mm_akm_suite_to_string(uint32_t akm_suite_oui)
|
||||
{
|
||||
switch (akm_suite_oui) {
|
||||
case MM_AKM_SUITE_NONE:
|
||||
return "None";
|
||||
|
||||
case MM_AKM_SUITE_PSK:
|
||||
return "PSK";
|
||||
|
||||
case MM_AKM_SUITE_SAE:
|
||||
return "SAE";
|
||||
|
||||
case MM_AKM_SUITE_OWE:
|
||||
return "OWE";
|
||||
|
||||
default:
|
||||
return "Other";
|
||||
}
|
||||
}
|
||||
|
||||
int mm_find_ie_from_offset(const uint8_t *ies, uint32_t ies_len, uint32_t search_offset, uint8_t ie_type)
|
||||
{
|
||||
while ((search_offset + 2) <= ies_len) {
|
||||
uint8_t type = ies[search_offset];
|
||||
uint8_t length = ies[search_offset + 1];
|
||||
|
||||
if (type == ie_type) {
|
||||
if ((search_offset + 2 + length) > ies_len) {
|
||||
return -2;
|
||||
}
|
||||
return search_offset;
|
||||
}
|
||||
|
||||
search_offset += 2 + length;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int mm_find_vendor_specific_ie_from_offset(const uint8_t *ies, uint32_t ies_len, uint32_t search_offset, const uint8_t *id,
|
||||
size_t id_len)
|
||||
{
|
||||
int offset = 0;
|
||||
|
||||
while ((search_offset + id_len) <= ies_len) {
|
||||
offset = mm_find_ie_from_offset(ies, ies_len, search_offset, MM_VENDOR_SPECIFIC_IE_TYPE);
|
||||
if (offset < 0) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
uint8_t ie_type = ies[offset];
|
||||
uint8_t ie_length = ies[offset + 1];
|
||||
const uint8_t *ie_data = ies + (offset + 2);
|
||||
|
||||
if (ie_type == MM_VENDOR_SPECIFIC_IE_TYPE && id_len <= ie_length && (memcmp(id, ie_data, id_len) == 0)) {
|
||||
if (((uint32_t)offset + 2 + ie_length) > ies_len) {
|
||||
return -2;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
search_offset = 2 + ie_length + (uint32_t)offset;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int mm_parse_rsn_information(const uint8_t *ies, uint32_t ies_len, struct mm_rsn_information *output)
|
||||
{
|
||||
uint8_t length;
|
||||
uint16_t num_pairwise_cipher_suites;
|
||||
uint16_t num_akm_suites;
|
||||
uint16_t ii;
|
||||
|
||||
int offset = mm_find_ie(ies, ies_len, MM_RSN_INFORMATION_IE_TYPE);
|
||||
|
||||
memset(output, 0, sizeof(*output));
|
||||
|
||||
if (offset < 0) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
/* Note that we rely on mm_find_ie() to validate that the IE does not extend past the end
|
||||
* of the given buffer. */
|
||||
|
||||
length = ies[offset + 1];
|
||||
offset += 2;
|
||||
|
||||
if (length < 8) {
|
||||
printf("*WRN* RSN IE too short\n");
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* Skip version field */
|
||||
output->version = ies[offset] | ies[offset + 1] << 8;
|
||||
offset += 2;
|
||||
length -= 2;
|
||||
|
||||
output->group_cipher_suite = ies[offset] << 24 | ies[offset + 1] << 16 | ies[offset + 2] << 8 | ies[offset + 3];
|
||||
offset += 4;
|
||||
length -= 4;
|
||||
|
||||
num_pairwise_cipher_suites = ies[offset] | ies[offset + 1] << 8;
|
||||
offset += 2;
|
||||
length -= 2;
|
||||
|
||||
output->num_pairwise_cipher_suites = num_pairwise_cipher_suites;
|
||||
if (num_pairwise_cipher_suites > MM_RSN_INFORMATION_MAX_PAIRWISE_CIPHER_SUITES) {
|
||||
output->num_pairwise_cipher_suites = MM_RSN_INFORMATION_MAX_PAIRWISE_CIPHER_SUITES;
|
||||
}
|
||||
|
||||
if (length < 4 * num_pairwise_cipher_suites + 2) {
|
||||
printf("*WRN* RSN IE too short\n");
|
||||
return -2;
|
||||
}
|
||||
|
||||
for (ii = 0; ii < num_pairwise_cipher_suites; ii++) {
|
||||
if (ii < output->num_pairwise_cipher_suites) {
|
||||
output->pairwise_cipher_suites[ii] =
|
||||
ies[offset] << 24 | ies[offset + 1] << 16 | ies[offset + 2] << 8 | ies[offset + 3];
|
||||
}
|
||||
offset += 4;
|
||||
length -= 4;
|
||||
}
|
||||
|
||||
num_akm_suites = ies[offset] | ies[offset + 1] << 8;
|
||||
offset += 2;
|
||||
length -= 2;
|
||||
|
||||
output->num_akm_suites = num_akm_suites;
|
||||
if (num_akm_suites > MM_RSN_INFORMATION_MAX_AKM_SUITES) {
|
||||
output->num_akm_suites = MM_RSN_INFORMATION_MAX_AKM_SUITES;
|
||||
}
|
||||
|
||||
if (length < 4 * num_akm_suites + 2) {
|
||||
printf("*WRN* RSN IE too short\n");
|
||||
return -2;
|
||||
}
|
||||
|
||||
for (ii = 0; ii < num_akm_suites; ii++) {
|
||||
if (ii < output->num_akm_suites) {
|
||||
output->akm_suites[ii] = ies[offset] << 24 | ies[offset + 1] << 16 | ies[offset + 2] << 8 | ies[offset + 3];
|
||||
}
|
||||
offset += 4;
|
||||
length -= 4;
|
||||
}
|
||||
|
||||
output->rsn_capabilities = ies[offset] | ies[offset + 1] << 8;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* USE_MM_IOT_ESP32 */
|
||||
@@ -0,0 +1,267 @@
|
||||
#ifdef USE_MM_IOT_ESP32
|
||||
/*
|
||||
* Copyright 2021-2023 Morse Micro
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "mmhal.h"
|
||||
#include "mmosal.h"
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/spi_common.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "esp_random.h"
|
||||
#include "esp_system.h"
|
||||
|
||||
/** 10x8bit training seq */
|
||||
#define BYTE_TRAIN 16
|
||||
|
||||
/** SPI hw interrupt handler. Must be set before enabling irq */
|
||||
static mmhal_irq_handler_t spi_irq_handler = NULL;
|
||||
|
||||
/** busy interrupt handler. Must be set before enabling irq */
|
||||
static mmhal_irq_handler_t busy_irq_handler = NULL;
|
||||
|
||||
static spi_device_handle_t spi_handle;
|
||||
|
||||
static void wlan_hal_gpio_init(void)
|
||||
{
|
||||
gpio_config_t io_conf = {};
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pin_bit_mask = ((1ull << CONFIG_MM_WAKE) | (1ull << CONFIG_MM_SPI_CS));
|
||||
io_conf.pull_down_en = 0;
|
||||
io_conf.pull_up_en = 0;
|
||||
gpio_config(&io_conf);
|
||||
|
||||
gpio_set_level(CONFIG_MM_WAKE, 0);
|
||||
gpio_set_level(CONFIG_MM_SPI_CS, 0);
|
||||
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_INPUT;
|
||||
io_conf.pin_bit_mask = (1ull << CONFIG_MM_BUSY);
|
||||
io_conf.pull_down_en = 1;
|
||||
gpio_config(&io_conf);
|
||||
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_INPUT;
|
||||
io_conf.pin_bit_mask = (1ull << CONFIG_MM_SPI_IRQ);
|
||||
io_conf.pull_down_en = 0;
|
||||
gpio_config(&io_conf);
|
||||
}
|
||||
|
||||
static void wlan_hal_spi_init(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
spi_bus_config_t buscfg = {
|
||||
.miso_io_num = CONFIG_MM_SPI_MISO,
|
||||
.mosi_io_num = CONFIG_MM_SPI_MOSI,
|
||||
.sclk_io_num = CONFIG_MM_SPI_SCK,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
/* max_transfer_sz defaults to 4092 if 0 when DMA enabled, or to SOC_SPI_MAXIMUM_BUFFER_SIZE
|
||||
* if DMA is disabled. */
|
||||
.max_transfer_sz = 0,
|
||||
.flags = SPICOMMON_BUSFLAG_MASTER,
|
||||
};
|
||||
ret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
|
||||
if (ret != ESP_OK) {
|
||||
printf("spi_bus_initialize failed\n");
|
||||
}
|
||||
|
||||
/* Selected the highest available SPI clock speed that is still below the MM6108's maximum of
|
||||
* 50MHz */
|
||||
spi_device_interface_config_t dev_cfg = {
|
||||
.clock_speed_hz = SPI_MASTER_FREQ_40M,
|
||||
.mode = 0,
|
||||
.spics_io_num = -1,
|
||||
.queue_size = 1,
|
||||
};
|
||||
ret = spi_bus_add_device(SPI2_HOST, &dev_cfg, &spi_handle);
|
||||
if (ret != ESP_OK) {
|
||||
printf("spi_bus_add_device failed\n");
|
||||
}
|
||||
|
||||
/* The actual clock frequency may not be the one that was set as it is re-calculated by the
|
||||
* driver to the nearest hardware-compatible number. Importantly it is the "nearest", so it could be above
|
||||
* the value set. */
|
||||
int actual_freq_khz = 0;
|
||||
spi_device_get_actual_freq(spi_handle, &actual_freq_khz);
|
||||
printf("Actual SPI CLK %dkHz\n", actual_freq_khz);
|
||||
}
|
||||
|
||||
static void wlan_hal_spi_deinit(void)
|
||||
{
|
||||
esp_err_t ret = spi_bus_remove_device(spi_handle);
|
||||
if (ret != ESP_OK) {
|
||||
printf("spi_bus_remove_device failed\n");
|
||||
}
|
||||
|
||||
ret = spi_bus_free(SPI2_HOST);
|
||||
if (ret != ESP_OK) {
|
||||
printf("spi_bus_initialize failed\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Minium transfer length in bytes before interrupt based transactions are used. This is because
|
||||
* there is some setup time associated with using the interrupt based method when compared to the
|
||||
* polling method. In the cases where the difference in setup time exceeds the transaction duration
|
||||
* it is more efficient to uses the polling method instead of the interrupt based one. The below
|
||||
* equation was used to calculate this.
|
||||
*
|
||||
* (DMA_TRANSACTION_DURATION - POLL_TRANSACTION_DURATION) / (8/SPI_FREQ)
|
||||
*
|
||||
* The typical duration for the ESP32 can be found in the [transaction
|
||||
* duration](https://docs.espressif.com/projects/esp-idf/en/v5.1.1/esp32s3/api-reference/peripherals/spi_master.html#transaction-duration)
|
||||
* section of the docs.
|
||||
*/
|
||||
#define INTERRUPT_TRANSFER_MIN_LENGTH 75
|
||||
|
||||
static void spi_master_rw(const uint8_t *w_data, uint8_t *r_data, size_t len)
|
||||
{
|
||||
spi_transaction_t trans_desc = {
|
||||
.rx_buffer = r_data,
|
||||
.tx_buffer = w_data,
|
||||
.length = (len * 8),
|
||||
.flags = 0,
|
||||
};
|
||||
|
||||
esp_err_t err;
|
||||
if (len < INTERRUPT_TRANSFER_MIN_LENGTH) {
|
||||
err = spi_device_polling_transmit(spi_handle, &trans_desc);
|
||||
} else {
|
||||
err = spi_device_transmit(spi_handle, &trans_desc);
|
||||
}
|
||||
|
||||
if (err != ESP_OK) {
|
||||
printf("SPI rw error = %x\n", err);
|
||||
}
|
||||
}
|
||||
|
||||
void mmhal_wlan_hard_reset(void)
|
||||
{
|
||||
gpio_set_level(CONFIG_MM_RESET_N, 0);
|
||||
mmosal_task_sleep(5);
|
||||
gpio_set_level(CONFIG_MM_RESET_N, 1);
|
||||
mmosal_task_sleep(20);
|
||||
}
|
||||
|
||||
void mmhal_wlan_spi_cs_assert(void)
|
||||
{
|
||||
gpio_set_level(CONFIG_MM_SPI_CS, 0);
|
||||
}
|
||||
|
||||
void mmhal_wlan_spi_cs_deassert(void)
|
||||
{
|
||||
gpio_set_level(CONFIG_MM_SPI_CS, 1);
|
||||
}
|
||||
|
||||
uint8_t mmhal_wlan_spi_rw(uint8_t data)
|
||||
{
|
||||
uint8_t readval;
|
||||
spi_master_rw(&data, &readval, 1);
|
||||
return readval;
|
||||
}
|
||||
|
||||
void mmhal_wlan_spi_read_buf(uint8_t *buf, unsigned len)
|
||||
{
|
||||
spi_master_rw(NULL, buf, len);
|
||||
}
|
||||
|
||||
void mmhal_wlan_spi_write_buf(const uint8_t *buf, unsigned len)
|
||||
{
|
||||
spi_master_rw(buf, NULL, len);
|
||||
}
|
||||
|
||||
void mmhal_wlan_send_training_seq(void)
|
||||
{
|
||||
mmhal_wlan_spi_cs_deassert();
|
||||
/* Send >74 clock pulses to card to stabilize CLK.
|
||||
* This method of stacking up the TX data is described in RM0090 rev 19 Figure 253.
|
||||
* It results is a reduction in the time between bytes of ~85% (316ns -> 48ns).
|
||||
* Could not get this to work for the other transactions however.
|
||||
*/
|
||||
uint8_t buf[BYTE_TRAIN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
spi_master_rw(buf, NULL, BYTE_TRAIN);
|
||||
}
|
||||
|
||||
void mmhal_wlan_register_spi_irq_handler(mmhal_irq_handler_t handler)
|
||||
{
|
||||
spi_irq_handler = handler;
|
||||
gpio_isr_handler_add(CONFIG_MM_SPI_IRQ, (gpio_isr_t)spi_irq_handler, NULL);
|
||||
}
|
||||
|
||||
bool mmhal_wlan_spi_irq_is_asserted(void)
|
||||
{
|
||||
return !gpio_get_level(CONFIG_MM_SPI_IRQ);
|
||||
}
|
||||
|
||||
void mmhal_wlan_set_spi_irq_enabled(bool enabled)
|
||||
{
|
||||
if (enabled) {
|
||||
gpio_set_intr_type(CONFIG_MM_SPI_IRQ, GPIO_INTR_LOW_LEVEL);
|
||||
} else {
|
||||
gpio_set_intr_type(CONFIG_MM_SPI_IRQ, GPIO_INTR_DISABLE);
|
||||
}
|
||||
}
|
||||
|
||||
void mmhal_wlan_init(void)
|
||||
{
|
||||
wlan_hal_gpio_init();
|
||||
wlan_hal_spi_init();
|
||||
/* Raise the RESET_N line to enable the WLAN transceiver. */
|
||||
gpio_set_level(CONFIG_MM_RESET_N, 1);
|
||||
}
|
||||
|
||||
void mmhal_wlan_deinit(void)
|
||||
{
|
||||
/* Lower the RESET_N line to disable the WLAN transceiver. This will put the transceiver in its
|
||||
* lowest power state. */
|
||||
gpio_set_level(CONFIG_MM_RESET_N, 0);
|
||||
|
||||
wlan_hal_spi_deinit();
|
||||
|
||||
/* Clean up any ISR handlers that have been added. These will be added again if the WLAN
|
||||
* interface is brought back up. */
|
||||
gpio_isr_handler_remove(CONFIG_MM_SPI_IRQ);
|
||||
gpio_isr_handler_remove(CONFIG_MM_BUSY);
|
||||
}
|
||||
|
||||
void mmhal_wlan_wake_assert(void)
|
||||
{
|
||||
gpio_set_level(CONFIG_MM_WAKE, 1);
|
||||
}
|
||||
|
||||
void mmhal_wlan_wake_deassert(void)
|
||||
{
|
||||
gpio_set_level(CONFIG_MM_WAKE, 0);
|
||||
}
|
||||
|
||||
bool mmhal_wlan_busy_is_asserted(void)
|
||||
{
|
||||
return gpio_get_level(CONFIG_MM_BUSY);
|
||||
}
|
||||
|
||||
void mmhal_wlan_register_busy_irq_handler(mmhal_irq_handler_t handler)
|
||||
{
|
||||
busy_irq_handler = handler;
|
||||
gpio_isr_handler_add(CONFIG_MM_BUSY, (gpio_isr_t)busy_irq_handler, NULL);
|
||||
}
|
||||
|
||||
void mmhal_wlan_set_busy_irq_enabled(bool enabled)
|
||||
{
|
||||
if (enabled) {
|
||||
gpio_set_intr_type(CONFIG_MM_BUSY, GPIO_INTR_POSEDGE);
|
||||
} else {
|
||||
gpio_set_intr_type(CONFIG_MM_BUSY, GPIO_INTR_DISABLE);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* USE_MM_IOT_ESP32 */
|
||||
@@ -191,12 +191,10 @@ echo
|
||||
# PASS/FAIL — every hardware test would SKIP with "role not present". We
|
||||
# narrow to tests/unit explicitly so the summary reads as "no hardware,
|
||||
# unit suite only" instead of "big skip count looks suspicious".
|
||||
# Keep terminal output condensed (`-q -r fE`) so skip-heavy runs do not print
|
||||
# each skipped test in full; skip counts still appear in pytest's summary.
|
||||
if [[ -z $DETECTED && $# -eq 0 ]]; then
|
||||
echo "[pre-flight] no supported devices detected; running unit tier only."
|
||||
echo
|
||||
exec "$VENV_PY" -m pytest tests/unit -q -r fE --report-log=tests/reportlog.jsonl
|
||||
exec "$VENV_PY" -m pytest tests/unit -v --report-log=tests/reportlog.jsonl
|
||||
fi
|
||||
|
||||
# Default pytest args when the user passed none. Power users can invoke
|
||||
@@ -212,13 +210,11 @@ fi
|
||||
# skipping half the hardware tests with "not baked with session profile"
|
||||
# errors. Power users who know their hardware is current and want to shave
|
||||
# those seconds can pass `--assume-baked` explicitly.
|
||||
# Defaults also use condensed reporting (`-q -r fE`) to avoid listing every
|
||||
# skipped test verbatim while still surfacing failures/errors and summary data.
|
||||
if [[ $# -eq 0 ]]; then
|
||||
set -- tests/ \
|
||||
--html=tests/report.html --self-contained-html \
|
||||
--junitxml=tests/junit.xml \
|
||||
-q -r fE --tb=short
|
||||
-v --tb=short
|
||||
fi
|
||||
|
||||
# UI tier requires opencv-python-headless (and ideally easyocr). If it's
|
||||
|
||||
@@ -34,7 +34,6 @@ BuildRequires: python3dist(grpcio-tools)
|
||||
BuildRequires: git-core
|
||||
BuildRequires: gcc-c++
|
||||
BuildRequires: pkgconfig(yaml-cpp)
|
||||
BuildRequires: pkgconfig(jsoncpp)
|
||||
BuildRequires: pkgconfig(libgpiod)
|
||||
BuildRequires: pkgconfig(bluez)
|
||||
BuildRequires: pkgconfig(libusb-1.0)
|
||||
|
||||
+7
-7
@@ -2,7 +2,7 @@
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[platformio]
|
||||
default_envs = heltec-v3
|
||||
default_envs = tbeam
|
||||
|
||||
extra_configs =
|
||||
variants/*/*.ini
|
||||
@@ -17,7 +17,6 @@ test_build_src = true
|
||||
extra_scripts =
|
||||
pre:bin/platformio-pre.py
|
||||
bin/platformio-custom.py
|
||||
post:extra_scripts/nrf54l15_linker.py
|
||||
; note: we add src to our include search path so that lmic_project_config can override
|
||||
; note: TINYGPS_OPTION_NO_CUSTOM_FIELDS is VERY important. We don't use custom fields and somewhere in that pile
|
||||
; of code is a heap corruption bug!
|
||||
@@ -53,7 +52,6 @@ build_flags = -Wno-missing-field-initializers
|
||||
-DRADIOLIB_EXCLUDE_ADSB=1
|
||||
-DRADIOLIB_EXCLUDE_LORAWAN=1
|
||||
-DMESHTASTIC_EXCLUDE_DROPZONE=1
|
||||
-DMESHTASTIC_EXCLUDE_ZPS=1
|
||||
-DMESHTASTIC_EXCLUDE_REPLYBOT=1
|
||||
-DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1
|
||||
-DMESHTASTIC_EXCLUDE_HEALTH_TELEMETRY=1
|
||||
@@ -187,16 +185,12 @@ lib_deps =
|
||||
https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library/archive/refs/tags/v1.1.2.zip
|
||||
# renovate: datasource=github-tags depName=SparkFun 9DoF IMU Breakout ICM 20948 packageName=sparkfun/SparkFun_ICM-20948_ArduinoLibrary
|
||||
https://github.com/sparkfun/SparkFun_ICM-20948_ArduinoLibrary/archive/refs/tags/v1.3.2.zip
|
||||
# renovate: datasource=github-tags depName=TDK InvenSense ICM42670P packageName=tdk-invn-oss/motion.arduino.ICM42670P
|
||||
https://github.com/tdk-invn-oss/motion.arduino.ICM42670P/archive/refs/tags/1.0.8.zip
|
||||
# renovate: datasource=github-tags depName=Adafruit LTR390 Library packageName=adafruit/Adafruit_LTR390
|
||||
https://github.com/adafruit/Adafruit_LTR390/archive/refs/tags/1.1.2.zip
|
||||
# renovate: datasource=github-tags depName=Adafruit PCT2075 packageName=adafruit/Adafruit_PCT2075
|
||||
https://github.com/adafruit/Adafruit_PCT2075/archive/refs/tags/1.0.6.zip
|
||||
# renovate: datasource=github-tags depName=DFRobot_BMM150 packageName=dfrobot/DFRobot_BMM150
|
||||
https://github.com/DFRobot/DFRobot_BMM150/archive/refs/tags/V1.0.0.zip
|
||||
# renovate: datasource=github-tags depName=SparkFun MMC5983MA Magnetometer packageName=sparkfun/SparkFun_MMC5983MA_Magnetometer_Arduino_Library
|
||||
https://github.com/sparkfun/SparkFun_MMC5983MA_Magnetometer_Arduino_Library/archive/refs/tags/v1.1.4.zip
|
||||
# renovate: datasource=github-tags depName=Adafruit_TSL2561 packageName=adafruit/Adafruit_TSL2561
|
||||
https://github.com/adafruit/Adafruit_TSL2561/archive/refs/tags/1.1.3.zip
|
||||
# renovate: datasource=github-tags depName=BH1750_WE packageName=wollewald/BH1750_WE
|
||||
@@ -209,10 +203,16 @@ lib_deps =
|
||||
https://github.com/adafruit/Adafruit_BMP3XX/archive/refs/tags/2.1.6.zip
|
||||
# renovate: datasource=github-tags depName=Adafruit MAX1704X packageName=adafruit/Adafruit_MAX1704X
|
||||
https://github.com/adafruit/Adafruit_MAX1704X/archive/refs/tags/1.0.3.zip
|
||||
# renovate: datasource=github-tags depName=Adafruit SHTC3 packageName=adafruit/Adafruit_SHTC3
|
||||
https://github.com/adafruit/Adafruit_SHTC3/archive/refs/tags/1.0.2.zip
|
||||
# renovate: datasource=github-tags depName=Adafruit LPS2X packageName=adafruit/Adafruit_LPS2X
|
||||
https://github.com/adafruit/Adafruit_LPS2X/archive/refs/tags/2.0.6.zip
|
||||
# renovate: datasource=github-tags depName=Adafruit SHT31 packageName=adafruit/Adafruit_SHT31
|
||||
https://github.com/adafruit/Adafruit_SHT31/archive/refs/tags/2.2.2.zip
|
||||
# renovate: datasource=github-tags depName=Adafruit VEML7700 packageName=adafruit/Adafruit_VEML7700
|
||||
https://github.com/adafruit/Adafruit_VEML7700/archive/refs/tags/2.1.6.zip
|
||||
# renovate: datasource=github-tags depName=Adafruit SHT4x packageName=adafruit/Adafruit_SHT4X
|
||||
https://github.com/adafruit/Adafruit_SHT4X/archive/refs/tags/1.0.5.zip
|
||||
# renovate: datasource=github-tags depName=SparkFun Qwiic Scale NAU7802 packageName=sparkfun/SparkFun_Qwiic_Scale_NAU7802_Arduino_Library
|
||||
https://github.com/sparkfun/SparkFun_Qwiic_Scale_NAU7802_Arduino_Library/archive/refs/tags/v1.0.6.zip
|
||||
# renovate: datasource=custom.pio depName=ClosedCube OPT3001 packageName=closedcube/library/ClosedCube OPT3001
|
||||
|
||||
+1
-1
Submodule protobufs updated: 21f55ac09b...7ffb4bb60d
@@ -152,16 +152,15 @@ extern "C" void logLegacy(const char *level, const char *fmt, ...);
|
||||
// Default Bluetooth PIN
|
||||
#define defaultBLEPin 123456
|
||||
|
||||
#if HAS_ETHERNET && defined(USE_ARDUINO_ETHERNET)
|
||||
#include <Ethernet.h> // arduino-libraries/Ethernet — supports W5500 auto-detect
|
||||
#elif HAS_ETHERNET && defined(USE_CH390D)
|
||||
#if HAS_ETHERNET && defined(USE_CH390D)
|
||||
#include <ESP32_CH390.h>
|
||||
#elif HAS_ETHERNET && !defined(USE_WS5500)
|
||||
#include <RAK13800_W5100S.h>
|
||||
#endif // HAS_ETHERNET
|
||||
|
||||
#if HAS_ETHERNET && defined(ARCH_ESP32)
|
||||
#include <ETH.h>
|
||||
#if HAS_ETHERNET && defined(USE_WS5500)
|
||||
#include <ETHClass2.h>
|
||||
#define ETH ETH2
|
||||
#endif // HAS_ETHERNET
|
||||
|
||||
#if HAS_WIFI
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "DisplayFormatters.h"
|
||||
#include "MeshRadio.h"
|
||||
|
||||
const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName,
|
||||
bool usePreset)
|
||||
@@ -12,51 +11,33 @@ const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaC
|
||||
}
|
||||
|
||||
switch (preset) {
|
||||
case PRESET(SHORT_TURBO):
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
|
||||
return useShortName ? "ShortT" : "ShortTurbo";
|
||||
break;
|
||||
case PRESET(SHORT_SLOW):
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW:
|
||||
return useShortName ? "ShortS" : "ShortSlow";
|
||||
break;
|
||||
case PRESET(SHORT_FAST):
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST:
|
||||
return useShortName ? "ShortF" : "ShortFast";
|
||||
break;
|
||||
case PRESET(MEDIUM_SLOW):
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW:
|
||||
return useShortName ? "MedS" : "MediumSlow";
|
||||
break;
|
||||
case PRESET(MEDIUM_FAST):
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST:
|
||||
return useShortName ? "MedF" : "MediumFast";
|
||||
break;
|
||||
case PRESET(LONG_SLOW):
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW:
|
||||
return useShortName ? "LongS" : "LongSlow";
|
||||
break;
|
||||
case PRESET(LONG_FAST):
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST:
|
||||
return useShortName ? "LongF" : "LongFast";
|
||||
break;
|
||||
case PRESET(LONG_TURBO):
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO:
|
||||
return useShortName ? "LongT" : "LongTurbo";
|
||||
break;
|
||||
case PRESET(LONG_MODERATE):
|
||||
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
|
||||
return useShortName ? "LongM" : "LongMod";
|
||||
break;
|
||||
case PRESET(LITE_FAST):
|
||||
return useShortName ? "LiteF" : "LiteFast";
|
||||
break;
|
||||
case PRESET(LITE_SLOW):
|
||||
return useShortName ? "LiteS" : "LiteSlow";
|
||||
break;
|
||||
case PRESET(NARROW_FAST):
|
||||
return useShortName ? "NarF" : "NarrowFast";
|
||||
break;
|
||||
case PRESET(NARROW_SLOW):
|
||||
return useShortName ? "NarS" : "NarrowSlow";
|
||||
break;
|
||||
case PRESET(TINY_FAST):
|
||||
return useShortName ? "TinyF" : "TinyFast";
|
||||
break;
|
||||
case PRESET(TINY_SLOW):
|
||||
return useShortName ? "TinyS" : "TinySlow";
|
||||
break;
|
||||
default:
|
||||
return useShortName ? "Custom" : "Invalid";
|
||||
break;
|
||||
|
||||
+1
-1
@@ -277,7 +277,7 @@ void fsInit()
|
||||
*/
|
||||
void setupSDCard()
|
||||
{
|
||||
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI) && !defined(HAS_SD_MMC)
|
||||
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
|
||||
concurrency::LockGuard g(spiLock);
|
||||
SDHandler.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
|
||||
if (!SD.begin(SDCARD_CS, SDHandler, SD_SPI_FREQUENCY)) {
|
||||
|
||||
@@ -48,14 +48,6 @@ using namespace STM32_LittleFS_Namespace;
|
||||
using namespace Adafruit_LittleFS_Namespace;
|
||||
#endif
|
||||
|
||||
#if defined(ARCH_NRF54L15)
|
||||
// nRF54L15 — Zephyr LittleFS on 36 KB storage_partition (internal RRAM)
|
||||
#include "InternalFileSystem.h"
|
||||
#define FSCom InternalFS
|
||||
#define FSBegin() FSCom.begin()
|
||||
using namespace Adafruit_LittleFS_Namespace;
|
||||
#endif
|
||||
|
||||
void fsInit();
|
||||
void fsListFiles();
|
||||
bool copyFile(const char *from, const char *to);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "configuration.h"
|
||||
#if HAS_SCREEN || defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS)
|
||||
#if HAS_SCREEN
|
||||
#include "FSCommon.h"
|
||||
#include "MessageStore.h"
|
||||
#include "NodeDB.h"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#if HAS_SCREEN || defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS)
|
||||
#if HAS_SCREEN
|
||||
|
||||
// Disable debug logging entirely on release builds of HELTEC_MESH_SOLAR for space constraints
|
||||
#if defined(HELTEC_MESH_SOLAR)
|
||||
|
||||
+83
-126
@@ -24,13 +24,6 @@
|
||||
#include "meshUtils.h"
|
||||
#include "power/PowerHAL.h"
|
||||
#include "sleep.h"
|
||||
#ifdef ARCH_ESP32
|
||||
// #include <driver/adc.h>
|
||||
#include <esp_adc/adc_cali.h>
|
||||
#include <esp_adc/adc_cali_scheme.h>
|
||||
#include <esp_adc/adc_oneshot.h>
|
||||
#include <esp_err.h>
|
||||
#endif
|
||||
|
||||
#if defined(ARCH_PORTDUINO)
|
||||
#include "api/WiFiServerAPI.h"
|
||||
@@ -70,8 +63,9 @@
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
#if HAS_ETHERNET && defined(ARCH_ESP32)
|
||||
#include <ETH.h>
|
||||
#if HAS_ETHERNET && defined(USE_WS5500)
|
||||
#include <ETHClass2.h>
|
||||
#define ETH ETH2
|
||||
#endif // HAS_ETHERNET
|
||||
|
||||
#endif
|
||||
@@ -83,86 +77,21 @@
|
||||
#if defined(BATTERY_PIN) && defined(ARCH_ESP32)
|
||||
|
||||
#ifndef BAT_MEASURE_ADC_UNIT // ADC1 is default
|
||||
static const adc_channel_t adc_channel = ADC_CHANNEL;
|
||||
static const adc1_channel_t adc_channel = ADC_CHANNEL;
|
||||
static const adc_unit_t unit = ADC_UNIT_1;
|
||||
#else // ADC2
|
||||
static const adc_channel_t adc_channel = ADC_CHANNEL;
|
||||
#else // ADC2
|
||||
static const adc2_channel_t adc_channel = ADC_CHANNEL;
|
||||
static const adc_unit_t unit = ADC_UNIT_2;
|
||||
RTC_NOINIT_ATTR uint64_t RTC_reg_b;
|
||||
|
||||
#endif // BAT_MEASURE_ADC_UNIT
|
||||
|
||||
static adc_oneshot_unit_handle_t adc_handle = nullptr;
|
||||
static adc_cali_handle_t adc_cali_handle = nullptr;
|
||||
static bool adc_calibrated = false;
|
||||
esp_adc_cal_characteristics_t *adc_characs = (esp_adc_cal_characteristics_t *)calloc(1, sizeof(esp_adc_cal_characteristics_t));
|
||||
#ifndef ADC_ATTENUATION
|
||||
static const adc_atten_t atten = ADC_ATTEN_DB_12;
|
||||
#else
|
||||
static const adc_atten_t atten = ADC_ATTENUATION;
|
||||
#endif
|
||||
#ifdef ADC_BITWIDTH
|
||||
static const adc_bitwidth_t adc_width = ADC_BITWIDTH;
|
||||
#else
|
||||
static const adc_bitwidth_t adc_width = ADC_BITWIDTH_DEFAULT;
|
||||
#endif
|
||||
|
||||
static int adcBitWidthToBits(adc_bitwidth_t width)
|
||||
{
|
||||
switch (width) {
|
||||
case ADC_BITWIDTH_9:
|
||||
return 9;
|
||||
case ADC_BITWIDTH_10:
|
||||
return 10;
|
||||
case ADC_BITWIDTH_11:
|
||||
return 11;
|
||||
case ADC_BITWIDTH_12:
|
||||
return 12;
|
||||
#ifdef ADC_BITWIDTH_13
|
||||
case ADC_BITWIDTH_13:
|
||||
return 13;
|
||||
#endif
|
||||
default:
|
||||
return 12;
|
||||
}
|
||||
}
|
||||
|
||||
static bool initAdcCalibration()
|
||||
{
|
||||
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
|
||||
adc_cali_curve_fitting_config_t cali_config = {
|
||||
.unit_id = unit,
|
||||
.atten = atten,
|
||||
.bitwidth = adc_width,
|
||||
};
|
||||
esp_err_t ret = adc_cali_create_scheme_curve_fitting(&cali_config, &adc_cali_handle);
|
||||
if (ret == ESP_OK) {
|
||||
LOG_INFO("ADC calibration: curve fitting enabled");
|
||||
return true;
|
||||
}
|
||||
if (ret != ESP_ERR_NOT_SUPPORTED) {
|
||||
LOG_WARN("ADC calibration: curve fitting failed: %s", esp_err_to_name(ret));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
|
||||
adc_cali_line_fitting_config_t cali_config = {
|
||||
.unit_id = unit,
|
||||
.atten = atten,
|
||||
.bitwidth = adc_width,
|
||||
.default_vref = DEFAULT_VREF,
|
||||
};
|
||||
esp_err_t ret = adc_cali_create_scheme_line_fitting(&cali_config, &adc_cali_handle);
|
||||
if (ret == ESP_OK) {
|
||||
LOG_INFO("ADC calibration: line fitting enabled");
|
||||
return true;
|
||||
}
|
||||
if (ret != ESP_ERR_NOT_SUPPORTED) {
|
||||
LOG_WARN("ADC calibration: line fitting failed: %s", esp_err_to_name(ret));
|
||||
}
|
||||
#endif
|
||||
|
||||
LOG_INFO("ADC calibration not supported; using approximate scaling");
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif // BATTERY_PIN && ARCH_ESP32
|
||||
|
||||
#ifdef EXT_PWR_DETECT
|
||||
@@ -438,20 +367,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
scaled *= operativeAdcMultiplier;
|
||||
#elif defined(ARCH_ESP32) // ADC block for espressif platforms
|
||||
raw = espAdcRead();
|
||||
int voltage_mv = 0;
|
||||
if (adc_calibrated && adc_cali_handle) {
|
||||
if (adc_cali_raw_to_voltage(adc_cali_handle, raw, &voltage_mv) != ESP_OK) {
|
||||
LOG_WARN("ADC calibration read failed; using raw value");
|
||||
voltage_mv = 0;
|
||||
}
|
||||
}
|
||||
if (voltage_mv == 0) {
|
||||
// Fallback approximate conversion without calibration
|
||||
const int bits = adcBitWidthToBits(adc_width);
|
||||
const float max_code = powf(2.0f, bits) - 1.0f;
|
||||
voltage_mv = (int)((raw / max_code) * DEFAULT_VREF);
|
||||
}
|
||||
scaled = voltage_mv * operativeAdcMultiplier;
|
||||
scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs);
|
||||
scaled *= operativeAdcMultiplier;
|
||||
#else // block for all other platforms
|
||||
#ifdef ARCH_NRF52
|
||||
concurrency::LockGuard saadcGuard(concurrency::nrf52SaadcLock);
|
||||
@@ -493,22 +410,51 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
uint32_t raw = 0;
|
||||
uint8_t raw_c = 0; // raw reading counter
|
||||
|
||||
if (!adc_handle) {
|
||||
LOG_ERROR("ADC oneshot handle not initialized");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef BAT_MEASURE_ADC_UNIT // ADC1
|
||||
for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
|
||||
int val = 0;
|
||||
esp_err_t err = adc_oneshot_read(adc_handle, adc_channel, &val);
|
||||
if (err == ESP_OK) {
|
||||
raw += val;
|
||||
int val_ = adc1_get_raw(adc_channel);
|
||||
if (val_ >= 0) { // save only valid readings
|
||||
raw += val_;
|
||||
raw_c++;
|
||||
}
|
||||
// delayMicroseconds(100);
|
||||
}
|
||||
#else // ADC2
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32S3 // ESP32S3
|
||||
// ADC2 wifi bug workaround not required, breaks compile
|
||||
// On ESP32S3, ADC2 can take turns with Wifi (?)
|
||||
|
||||
int32_t adc_buf;
|
||||
esp_err_t read_result;
|
||||
|
||||
// Multiple samples
|
||||
for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
|
||||
adc_buf = 0;
|
||||
read_result = -1;
|
||||
|
||||
read_result = adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf);
|
||||
if (read_result == ESP_OK) {
|
||||
raw += adc_buf;
|
||||
raw_c++; // Count valid samples
|
||||
} else {
|
||||
LOG_DEBUG("ADC read failed: %s", esp_err_to_name(err));
|
||||
LOG_DEBUG("An attempt to sample ADC2 failed");
|
||||
}
|
||||
}
|
||||
|
||||
#else // Other ESP32
|
||||
int32_t adc_buf = 0;
|
||||
for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
|
||||
// ADC2 wifi bug workaround, see
|
||||
// https://github.com/espressif/arduino-esp32/issues/102
|
||||
WRITE_PERI_REG(SENS_SAR_READ_CTRL2_REG, RTC_reg_b);
|
||||
SET_PERI_REG_MASK(SENS_SAR_READ_CTRL2_REG, SENS_SAR2_DATA_INV);
|
||||
adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf);
|
||||
raw += adc_buf;
|
||||
raw_c++;
|
||||
}
|
||||
#endif // BAT_MEASURE_ADC_UNIT
|
||||
|
||||
#endif // End BAT_MEASURE_ADC_UNIT
|
||||
return (raw / (raw_c < 1 ? 1 : raw_c));
|
||||
}
|
||||
#endif
|
||||
@@ -720,31 +666,42 @@ bool Power::analogInit()
|
||||
#ifdef ARCH_STM32WL
|
||||
analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS);
|
||||
#elif defined(ARCH_ESP32) // ESP32 needs special analog stuff
|
||||
adc_oneshot_unit_init_cfg_t init_config = {
|
||||
.unit_id = unit,
|
||||
};
|
||||
|
||||
if (!adc_handle) {
|
||||
esp_err_t err = adc_oneshot_new_unit(&init_config, &adc_handle);
|
||||
if (err != ESP_OK) {
|
||||
LOG_ERROR("ADC oneshot init failed: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
#ifndef ADC_WIDTH // max resolution by default
|
||||
static const adc_bits_width_t width = ADC_WIDTH_BIT_12;
|
||||
#else
|
||||
static const adc_bits_width_t width = ADC_WIDTH;
|
||||
#endif
|
||||
#ifndef BAT_MEASURE_ADC_UNIT // ADC1
|
||||
adc1_config_width(width);
|
||||
adc1_config_channel_atten(adc_channel, atten);
|
||||
#else // ADC2
|
||||
adc2_config_channel_atten(adc_channel, atten);
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32S3
|
||||
// ADC2 wifi bug workaround
|
||||
// Not required with ESP32S3, breaks compile
|
||||
RTC_reg_b = READ_PERI_REG(SENS_SAR_READ_CTRL2_REG);
|
||||
#endif
|
||||
#endif
|
||||
// calibrate ADC
|
||||
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(unit, atten, width, DEFAULT_VREF, adc_characs);
|
||||
// show ADC characterization base
|
||||
if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
|
||||
LOG_INFO("ADC config based on Two Point values stored in eFuse");
|
||||
} else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
|
||||
LOG_INFO("ADC config based on reference voltage stored in eFuse");
|
||||
}
|
||||
|
||||
adc_oneshot_chan_cfg_t chan_cfg = {
|
||||
.atten = atten,
|
||||
.bitwidth = adc_width,
|
||||
};
|
||||
|
||||
esp_err_t err = adc_oneshot_config_channel(adc_handle, adc_channel, &chan_cfg);
|
||||
if (err != ESP_OK) {
|
||||
LOG_ERROR("ADC channel config failed: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32S3
|
||||
// ESP32S3
|
||||
else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP_FIT) {
|
||||
LOG_INFO("ADC config based on Two Point values and fitting curve "
|
||||
"coefficients stored in eFuse");
|
||||
}
|
||||
|
||||
adc_calibrated = initAdcCalibration();
|
||||
#endif // ARCH_ESP32
|
||||
#endif
|
||||
else {
|
||||
LOG_INFO("ADC config based on default reference voltage");
|
||||
}
|
||||
#endif // ARCH_ESP32
|
||||
|
||||
// NRF52 ADC init moved to powerHAL_init in nrf52 platform
|
||||
|
||||
|
||||
+1
-11
@@ -1,16 +1,6 @@
|
||||
// TODO refactor this out with better radio configuration system
|
||||
#ifdef USE_RF95
|
||||
|
||||
#ifndef RF95_RESET
|
||||
#define RF95_RESET LORA_RESET
|
||||
#endif
|
||||
|
||||
#ifndef RF95_IRQ
|
||||
#define RF95_IRQ LORA_DIO0 // on SX1262 version this is a no connect DIO0
|
||||
#endif
|
||||
|
||||
#ifndef RF95_DIO1
|
||||
#define RF95_IRQ LORA_DIO0 // on SX1262 version this is a no connect DIO0
|
||||
#define RF95_DIO1 LORA_DIO1 // Note: not really used for RF95, but used for pure SX127x
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -51,7 +51,7 @@ size_t RedirectablePrint::write(uint8_t c)
|
||||
size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_list arg)
|
||||
{
|
||||
va_list copy;
|
||||
#if ARCH_PORTDUINO
|
||||
#if ENABLE_JSON_LOGGING || ARCH_PORTDUINO
|
||||
static char printBuf[512];
|
||||
#else
|
||||
static char printBuf[160];
|
||||
@@ -225,8 +225,6 @@ void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_
|
||||
isBleConnected = nimbleBluetooth && nimbleBluetooth->isActive() && nimbleBluetooth->isConnected();
|
||||
#elif defined(ARCH_NRF52)
|
||||
isBleConnected = nrf52Bluetooth != nullptr && nrf52Bluetooth->isConnected();
|
||||
#elif defined(ARCH_NRF54L15)
|
||||
isBleConnected = nrf54l15Bluetooth != nullptr && nrf54l15Bluetooth->isConnected();
|
||||
#endif
|
||||
if (isBleConnected) {
|
||||
auto thread = concurrency::OSThread::currentThread;
|
||||
@@ -243,8 +241,6 @@ void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_
|
||||
nimbleBluetooth->sendLog(buffer.get(), size);
|
||||
#elif defined(ARCH_NRF52)
|
||||
nrf52Bluetooth->sendLog(buffer.get(), size);
|
||||
#elif defined(ARCH_NRF54L15)
|
||||
nrf54l15Bluetooth->sendLog(buffer.get(), size);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
+3
-4
@@ -133,12 +133,11 @@ bool AirTime::isTxAllowedChannelUtil(bool polite)
|
||||
|
||||
bool AirTime::isTxAllowedAirUtil()
|
||||
{
|
||||
float effectiveDutyCycle = getEffectiveDutyCycle();
|
||||
if (!config.lora.override_duty_cycle && effectiveDutyCycle < 100) {
|
||||
if (utilizationTXPercent() < effectiveDutyCycle * polite_duty_cycle_percent / 100) {
|
||||
if (!config.lora.override_duty_cycle && myRegion->dutyCycle < 100) {
|
||||
if (utilizationTXPercent() < myRegion->dutyCycle * polite_duty_cycle_percent / 100) {
|
||||
return true;
|
||||
} else {
|
||||
LOG_WARN("TX air util. >%f%%. Skip send", effectiveDutyCycle * polite_duty_cycle_percent / 100);
|
||||
LOG_WARN("TX air util. >%f%%. Skip send", myRegion->dutyCycle * polite_duty_cycle_percent / 100);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user