Compare commits

...
60 Commits
Author SHA1 Message Date
robert 4e7c664455 Add generic mg_health report for mdash 2026-09-23 12:07:11 +03:00
robert 60c75a22b8 Validate WebSocket continuation frame sequencing 2026-09-21 15:32:10 +03:00
Sergey Lyubka bbee8ae61f Merge pull request #3702 from cesanta/up3
Add mg_http_stream_body() helper for generic streaming uploads
2026-09-21 13:00:15 +01:00
Sergey Lyubka 9173c8dfda Merge pull request #3698 from cesanta/nxp-examples
Reorganized NXP examples
2026-09-21 08:56:23 +01:00
cpq b93cbbeb13 Add mg_http_stream_body() helper for generic streaming uploads 2026-09-21 08:55:04 +01:00
Sergio R. Caprile aa1dfe8000 Merge pull request #3699 from cesanta/otah7
fix casting
2026-09-18 10:54:29 -03:00
Sergio R. Caprile e8a82d1821 fix casting 2026-09-18 10:45:49 -03:00
robert 201a81a315 Reorganized NXP examples 2026-09-18 10:07:08 +03:00
Sergey Lyubka aae31aba9c Merge pull request #3695 from eunho87/fix-mg_file_printf-null
Guard against NULL return in mg_file_printf
2026-09-16 10:21:23 +01:00
cpq 29aa53cebd Show backtrace 2026-09-15 14:20:15 +01:00
eunho87 5d4fffbb14 Guard against NULL return in mg_file_printf
mg_vmprintf() returns io.buf from a mg_iobuf that starts empty. When the
internal mg_iobuf_resize() allocation fails, the buffer is left NULL and
mg_vmprintf() returns NULL. mg_file_printf() passed that result straight
to strlen(data), dereferencing NULL on allocation failure.

Skip the write when mg_vmprintf() returns NULL and return false, matching
how the rest of the codebase checks mg_calloc()/allocation results.
2026-09-15 10:50:14 +09:00
Sergey Lyubka ed5ef09d1c Merge pull request #3692 from cesanta/health
health record support
2026-09-12 08:12:04 +01:00
cpq 1e99974b83 health record support 2026-09-11 14:09:55 +01:00
Sergey Lyubka 6814b44b03 Merge pull request #3689 from cesanta/isfull
support c->is_full flow control in builtin TCP/IP
2026-09-10 20:53:33 +01:00
Sergio R. Caprile 7e9ddc2d09 support c->is_full flow control in builtin TCP/IP 2026-09-09 15:55:55 -03:00
Sergio R. Caprile c13d5d7a56 Merge pull request #3688 from cesanta/nits
fix C89 and be tests
2026-09-09 15:55:00 -03:00
Sergio R. Caprile 555c5a76be fix C89 and be tests 2026-09-09 15:53:44 -03:00
robertc2000 863aea34c4 Merge pull request #3687 from cesanta/esp32crash
Add ESP32 minimal project with crash helper
2026-09-09 12:09:21 +03:00
Sergio R. Caprile 830f6c589b Add ESP32 minimal project with crash helper 2026-09-09 12:07:51 +03:00
Sergio R. Caprile cc64c41737 Merge pull request #3682 from cesanta/tcprtx
add optional TCP retransmission
2026-09-08 12:33:39 -03:00
Sergio R. Caprile 4660f2ca0f add nightly TLS test 2026-09-08 12:22:31 -03:00
Sergio R. Caprile ffcad7ee47 fix too small MSSs, enable auto config 2026-09-08 12:04:25 -03:00
robert 09a97b915a Claude: scan only src/ files 2026-09-08 16:52:39 +03:00
robert 35a9242b67 Disable automatic PR review 2026-09-08 11:03:05 +03:00
Sergio R. Caprile 5c34018899 add optional TCP retransmission 2026-09-07 15:34:08 -03:00
Sergio R. Caprile c857ddae57 Properly ACK duplicate segments 2026-09-07 13:48:33 -03:00
Sergio R. Caprile 830acc19ee Merge pull request #3681 from cesanta/l2len
fix PPP frame size, enforce L2 max lenghts
2026-09-07 12:00:07 -03:00
Sergio R. Caprile f87d439710 fix PPP frame size, enforce L2 max lenghts 2026-09-07 11:14:58 -03:00
Sergio R. Caprile 86adf12a77 improve SAN matching 2026-09-04 17:29:44 -03:00
Sergio R. Caprile a561f3e096 reject improper TLS record lengths 2026-09-04 11:24:30 -03:00
Sergio R. Caprile 2b0a5e9701 improve OID subj handling 2026-09-03 14:06:25 -03:00
Sergio R. Caprile 8346a60971 fix 'retry jobs' failing when there's no job to retry... 2026-09-02 11:14:37 -03:00
Sergio R. Caprile 140fa6cebf Merge pull request #3678 from cesanta/ip6sum
account for extension headers in IPv6 transported protocols checksums
2026-09-01 22:11:56 -03:00
Sergio R. Caprile cd05b162a8 account for extension headers in IPv6 transported protocols checksums 2026-09-01 21:33:48 -03:00
Sergey Lyubka 3fde04137c Merge pull request #3677 from cesanta/cubefull
Add full cubemx project for h723
2026-09-01 18:44:02 +01:00
cpq 5591b7d131 Add full cubemx project for h723 2026-09-01 18:10:17 +01:00
robert 9ddac8faa7 Terminate destination buffer in mg_url_decode 2026-09-01 17:33:56 +03:00
robert 9123330f72 Avoid computing offset twice 2026-09-01 16:39:56 +03:00
robert b938728c2a improve scanning filtering 2026-09-01 12:28:28 +03:00
robertc2000 fab922230c Merge pull request #3675 from cesanta/claude-refactor
Refactor Claude Codebase Scanning: split into multiple smaller scans
2026-09-01 11:25:01 +03:00
Sergio R. Caprile 90418ffb1b Merge pull request #3659 from cesanta/zerowin
make FIN wait for TLS drain
2026-08-31 10:25:58 -03:00
Sergio R. Caprile 5a317e81d9 make FIN wait for TLS drain 2026-08-31 10:03:27 -03:00
robert 7e8c3a59e2 Refactor Claude Codebase Scanning: split into multiple smaller scans 2026-08-28 16:32:06 +03:00
cpq 6093ac12a0 Remove rogue health.c 2026-08-27 09:47:03 +01:00
cpq e7cfaa70c7 Add a cap to mg_dash_send_change() 2026-08-27 09:43:56 +01:00
robert 897d26edfb nits 2026-08-26 15:11:09 +03:00
robertc2000 6b5d358ede Merge pull request #3671 from cesanta/netc-mdns
Enable multicast in NETC driver
2026-08-25 10:56:47 +03:00
robert 0d97280af4 Enable multicast in NETC driver 2026-08-25 10:48:21 +03:00
Sergey Lyubka afc84b7c92 Merge pull request #3664 from cesanta/drainudp
make builtin UDP honor c->is_draining, as socket does
2026-08-23 12:54:16 +01:00
Sergey Lyubka 66bf5952a4 Merge pull request #3660 from cesanta/ack
handle ACK in FIN, fix ACK to FIN + payload
2026-08-23 12:53:43 +01:00
Sergey Lyubka f002c30cbb Merge pull request #3665 from cesanta/fatfs
unbreak fatfs
2026-08-23 12:52:54 +01:00
robertc2000 53d2356472 Merge pull request #3668 from cesanta/ota-rt1180
Add OTA for IMXRT1180
2026-08-20 11:04:42 +03:00
robert e312219e80 Add OTA for IMXRT1180 2026-08-20 10:40:58 +03:00
cpq 2aff813795 Fix dosctrings 2026-08-15 11:23:15 +01:00
robertc2000 9c4506763b Merge pull request #3666 from cesanta/netc
Add Ethernet controller (NETC) driver for IMXRT1180
2026-08-14 17:01:04 +03:00
robert ec3b9e7c4f Add Ethernet controller (NETC) driver for IMXRT1180 2026-08-14 16:49:15 +03:00
Sergio R. Caprile ddd9651f91 temporary fix S390 faiing OOM 2026-08-14 10:30:58 -03:00
Sergio R. Caprile 304ea2a649 unbreak fatfs 2026-08-13 18:23:09 -03:00
Sergio R. Caprile 83bac34094 make UDP honor c->is_draining 2026-08-13 12:41:55 -03:00
Sergio R. Caprile 9c6b4edd37 handle ACK in FIN, fix ACK to FIN + payload 2026-08-07 11:49:28 -03:00
2141 changed files with 219011 additions and 1344701 deletions

No files matched your search

+151 -36
View File
@@ -1,16 +1,42 @@
name: Claude Full Codebase Security Scan name: Claude Codebase Security Scan
on: on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
prompt_path:
description: "Path to the repo prompt file Claude should read"
required: true
default: "resources/specs/claude-security-scan.md"
target_ref: target_ref:
description: "Branch, tag, or SHA to scan" description: "Branch, tag, or SHA to scan"
required: false required: false
default: "" default: ""
common_misc:
description: "Run common/misc security scan"
required: false
type: boolean
default: true
mongoose_core:
description: "Run Mongoose core security scan"
required: false
type: boolean
default: true
web_http_auth:
description: "Run web/HTTP/auth security scan"
required: false
type: boolean
default: true
tls:
description: "Run TLS security scan"
required: false
type: boolean
default: true
net_builtin:
description: "Run built-in network security scan"
required: false
type: boolean
default: true
protocols:
description: "Run protocol security scan"
required: false
type: boolean
default: true
#schedule: #schedule:
# Weekly scan: Sunday 02:00 UTC # Weekly scan: Sunday 02:00 UTC
# - cron: "0 2 * * 0" # - cron: "0 2 * * 0"
@@ -20,53 +46,140 @@ permissions:
id-token: write id-token: write
concurrency: concurrency:
group: claude-full-codebase-security-scan-${{ github.ref }} group: claude-security-scans
cancel-in-progress: false cancel-in-progress: false
queue: max
jobs: jobs:
scan: prepare:
name: Full codebase security scan name: Prepare selected scan matrix
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 60
env: outputs:
DEFAULT_PROMPT_PATH: "resources/specs/claude-security-scan.md" target_ref: ${{ steps.plan.outputs.target_ref }}
matrix: ${{ steps.plan.outputs.matrix }}
has_scans: ${{ steps.plan.outputs.has_scans }}
steps: steps:
- name: Resolve scan inputs - name: Resolve scan inputs and build matrix
id: vars id: plan
shell: bash shell: bash
run: | run: |
set -euo pipefail
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
PROMPT_PATH="${{ inputs.prompt_path }}"
TARGET_REF="${{ inputs.target_ref }}" TARGET_REF="${{ inputs.target_ref }}"
RUN_COMMON_MISC="${{ inputs.common_misc }}"
RUN_MONGOOSE_CORE="${{ inputs.mongoose_core }}"
RUN_WEB_HTTP_AUTH="${{ inputs.web_http_auth }}"
RUN_TLS="${{ inputs.tls }}"
RUN_NET_BUILTIN="${{ inputs.net_builtin }}"
RUN_PROTOCOLS="${{ inputs.protocols }}"
else else
PROMPT_PATH="${DEFAULT_PROMPT_PATH}"
TARGET_REF="" TARGET_REF=""
RUN_COMMON_MISC="true"
RUN_MONGOOSE_CORE="true"
RUN_WEB_HTTP_AUTH="true"
RUN_TLS="true"
RUN_NET_BUILTIN="true"
RUN_PROTOCOLS="true"
fi fi
if [ -z "$TARGET_REF" ]; then if [ -z "$TARGET_REF" ]; then
TARGET_REF="${{ github.ref }}" TARGET_REF="${{ github.ref }}"
fi fi
echo "prompt_path=$PROMPT_PATH" >> "$GITHUB_OUTPUT" MATRIX="$(jq -cn \
echo "target_ref=$TARGET_REF" >> "$GITHUB_OUTPUT" --arg common_misc "$RUN_COMMON_MISC" \
--arg mongoose_core "$RUN_MONGOOSE_CORE" \
--arg web_http_auth "$RUN_WEB_HTTP_AUTH" \
--arg tls "$RUN_TLS" \
--arg net_builtin "$RUN_NET_BUILTIN" \
--arg protocols "$RUN_PROTOCOLS" \
'{
include: ([
{
enabled: ($common_misc == "true"),
area: "common_misc",
display_name: "Common / Misc",
area_prompt: "resources/specs/areas/common_misc.md"
},
{
enabled: ($mongoose_core == "true"),
area: "mongoose_core",
display_name: "Mongoose Core",
area_prompt: "resources/specs/areas/mongoose_core.md"
},
{
enabled: ($web_http_auth == "true"),
area: "web_http_auth",
display_name: "Web / HTTP / Auth",
area_prompt: "resources/specs/areas/web_http_auth.md"
},
{
enabled: ($tls == "true"),
area: "tls",
display_name: "TLS",
area_prompt: "resources/specs/areas/tls.md"
},
{
enabled: ($net_builtin == "true"),
area: "net_builtin",
display_name: "Built-In Network",
area_prompt: "resources/specs/areas/net_builtin.md"
},
{
enabled: ($protocols == "true"),
area: "protocols",
display_name: "Protocols",
area_prompt: "resources/specs/areas/protocols.md"
}
] | map(select(.enabled)) | map(del(.enabled)))
}'
)"
HAS_SCANS="$(jq -r '.include | length > 0' <<< "$MATRIX")"
echo "target_ref=$TARGET_REF" >> "$GITHUB_OUTPUT"
echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"
echo "has_scans=$HAS_SCANS" >> "$GITHUB_OUTPUT"
scan:
name: ${{ matrix.display_name }} security scan
needs: prepare
if: needs.prepare.outputs.has_scans == 'true'
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
max-parallel: 1
matrix: ${{ fromJSON(needs.prepare.outputs.matrix) }}
env:
MASTER_PROMPT_PATH: "resources/specs/claude-security-scan.md"
FILTERING_PROMPT_PATH: "resources/specs/claude-false-positive-filtering.md"
SCAN_AREA: ${{ matrix.area }}
AREA_PROMPT_PATH: ${{ matrix.area_prompt }}
steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6 uses: actions/checkout@v6
with: with:
ref: ${{ steps.vars.outputs.target_ref }} ref: ${{ needs.prepare.outputs.target_ref }}
fetch-depth: 0 fetch-depth: 0
- name: Verify prompt file exists - name: Verify required prompt files exist
shell: bash shell: bash
run: | run: |
test -f "${{ steps.vars.outputs.prompt_path }}" || { for prompt_file in "$MASTER_PROMPT_PATH" "$AREA_PROMPT_PATH" "$FILTERING_PROMPT_PATH"; do
echo "Prompt file not found: ${{ steps.vars.outputs.prompt_path }}" test -f "$prompt_file" || {
exit 1 echo "Required prompt file not found: $prompt_file"
} exit 1
}
done
- name: Run Claude full codebase security scan - name: Run Claude ${{ matrix.display_name }} security scan
id: claude_scan id: claude_scan
uses: anthropics/claude-code-action@v1 uses: anthropics/claude-code-action@v1
timeout-minutes: 30 timeout-minutes: 30
@@ -75,19 +188,21 @@ jobs:
prompt: | prompt: |
REPO: ${{ github.repository }} REPO: ${{ github.repository }}
REF: ${{ steps.vars.outputs.target_ref }} REF: ${{ needs.prepare.outputs.target_ref }}
PROMPT_FILE: ${{ steps.vars.outputs.prompt_path }} MASTER_PROMPT_FILE: ${{ env.MASTER_PROMPT_PATH }}
SCAN_AREA: ${{ matrix.area }}
AREA_PROMPT_FILE: ${{ matrix.area_prompt }}
FILTERING_PROMPT_FILE: ${{ env.FILTERING_PROMPT_PATH }}
You are running a full-codebase security scan in GitHub Actions. You are running a focused security scan for the assigned area in GitHub Actions.
First, read the main prompt file at: First, read the master prompt at ${{ env.MASTER_PROMPT_PATH }}.
${{ steps.vars.outputs.prompt_path }} Then read the assigned area prompt at ${{ matrix.area_prompt }}.
Read the false-positive filtering prompt at ${{ env.FILTERING_PROMPT_PATH }} as required by the master prompt.
Treat that file as the authoritative security-review instruction set. You will also Perform the security review for the ${{ matrix.area }} area. Follow relevant supporting code when necessary, but do not independently scan unrelated areas.
read and analyze additional prompt files mentioned in the main prompt file by their absolute paths in the repo, Return only the JSON required by the master prompt.
as per the instructions found in it.
Scan the checked-out repository as a whole codebase.
Do not modify files. Do not modify files.
Do not create commits. Do not create commits.
Do not open pull requests. Do not open pull requests.
@@ -106,8 +221,8 @@ jobs:
set -u set -u
mkdir -p private-scan-results mkdir -p private-scan-results
TODAY="$(date -u +%Y%m%d)" TODAY="$(date -u +%Y%m%d)"
EXECUTION_OUTPUT_FILE="claude-execution-output_${TODAY}.json" EXECUTION_OUTPUT_FILE="claude-execution-output_${SCAN_AREA}_${TODAY}.json"
FINDINGS_FILE="security-findings_${TODAY}.json" FINDINGS_FILE="security-findings_${SCAN_AREA}_${TODAY}.json"
HAS_EXECUTION_OUTPUT="false" HAS_EXECUTION_OUTPUT="false"
HAS_JSON_FINDINGS="false" HAS_JSON_FINDINGS="false"
@@ -177,5 +292,5 @@ jobs:
exit 0 exit 0
fi fi
git commit -m "Add security scan results ${{ steps.prepare_outputs.outputs.date_suffix }}" git commit -m "Add ${SCAN_AREA} security scan results ${{ steps.prepare_outputs.outputs.date_suffix }}"
git push git push
@@ -1,9 +1,6 @@
name: Claude PR Security Review name: Claude PR Security Review
on: on:
pull_request:
types: [opened]
workflow_dispatch: workflow_dispatch:
inputs: inputs:
pr_number: pr_number:
@@ -22,7 +19,7 @@ permissions:
issues: write issues: write
concurrency: concurrency:
group: claude-pr-security-review-${{ github.event.pull_request.number || inputs.pr_number }} group: claude-pr-security-review-${{ inputs.pr_number }}
cancel-in-progress: false cancel-in-progress: false
jobs: jobs:
@@ -33,7 +30,7 @@ jobs:
env: env:
GH_TOKEN: ${{ github.token }} GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }} PR_NUMBER: ${{ inputs.pr_number }}
steps: steps:
- name: Resolve workflow variables - name: Resolve workflow variables
+41 -33
View File
@@ -116,6 +116,26 @@ jobs:
cat log cat log
test/health.awk < log > json test/health.awk < log > json
scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/${{ matrix.target }}_${{ matrix.cc }}_${{ matrix.ssl }}_$(date +"%Y%m%d").json" scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/${{ matrix.target }}_${{ matrix.cc }}_${{ matrix.ssl }}_$(date +"%Y%m%d").json"
miprtx:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
ssl: ["", BUILTIN]
name: MIP RTX TAP SSL=${{ matrix.ssl }}
env:
IPV6: 0
RTX: 1
SSL: ${{ matrix.ssl }}
TFLAGS: -DMQTT_HOST -DIPV6_NOROUTING -DNO_ABORT
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- run: |
source ./test/setup_ga_network.sh ; sed -i -e "s/127.0.0.1//" test/mosquitto.conf ; ./test/setup_mqtt_server.sh
make -C test mip_tap_test
mip89: mip89:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
@@ -147,6 +167,21 @@ jobs:
if [[ "${{ matrix.target }}" == *port* ]]; then source ./test/setup_ga_network.sh ; export ENV="-e Tmp=. -e WINEDEBUG=-all -e HOST_IP -e HOST_IPV6 -e HOST_IFC" ; sed -i -e "s/127.0.0.1//" test/mosquitto.conf; cat test/mosquitto.conf; ./test/setup_mqtt_server.sh ; make -C test port_tap_bridge ; test/port_tap_bridge & sleep 2 ; fi if [[ "${{ matrix.target }}" == *port* ]]; then source ./test/setup_ga_network.sh ; export ENV="-e Tmp=. -e WINEDEBUG=-all -e HOST_IP -e HOST_IPV6 -e HOST_IFC" ; sed -i -e "s/127.0.0.1//" test/mosquitto.conf; cat test/mosquitto.conf; ./test/setup_mqtt_server.sh ; make -C test port_tap_bridge ; test/port_tap_bridge & sleep 2 ; fi
make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }} SSL=${{ matrix.ssl }} MULTIREC=NO make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }} SSL=${{ matrix.ssl }} MULTIREC=NO
miprtxbe:
runs-on: ubuntu-latest
name: MIP RTX be s390 port
env:
IPV6: 0
RTX: 1
TFLAGS: -DMQTT_HOST -DIPV6_NOROUTING -DNO_ABORT
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 2 }
- run: |
sudo apt -y update ; sudo apt -y install binfmt-support qemu-user-static && docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
source ./test/setup_ga_network.sh ; export ENV="-e Tmp=. -e WINEDEBUG=-all -e HOST_IP -e HOST_IPV6 -e HOST_IFC -e RTX" ; sed -i -e "s/127.0.0.1//" test/mosquitto.conf ; ./test/setup_mqtt_server.sh ; make -C test port_tap_bridge ; test/port_tap_bridge & sleep 2
make -C test mip_port_s390 MULTIREC=NO
l2: l2:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
@@ -534,11 +569,12 @@ jobs:
infineon/xmc47_relax-make-baremetal-builtin infineon/xmc47_relax-make-baremetal-builtin
infineon/xmc_plt2go_4400-make-baremetal-builtin infineon/xmc_plt2go_4400-make-baremetal-builtin
infineon/xmc72_evk-make-baremetal-builtin infineon/xmc72_evk-make-baremetal-builtin
nxp/frdm-mcxn947-make-baremetal-builtin nxp/frdm-imxrt1186/minimal
nxp/frdm-mcxn947-make-freertos-builtin nxp/frdm-mcxe247/minimal
nxp/rt1020-evk-make-baremetal-builtin nxp/frdm-mcxn947/minimal
nxp/rt1060-evk-make-baremetal-builtin nxp/frdm-rw612/minimal
nxp/rt1170-evk-make-baremetal-builtin nxp/rt1060-evkb/minimal
nxp/rt1170-evkb/minimal
- group: rp-ti - group: rp-ti
examples: | examples: |
rp/pico-w rp/pico-w
@@ -614,28 +650,6 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- group: nxp-1
examples: |
nxp/nxp-frdmk66f-freertos
nxp/nxp-lpcxpresso54s018m-freertos
nxp/nxp-mimxrt1020-freertos
nxp/nxp-evkbimxrt1050-lwip-freertos
nxp/nxp-evkmimxrt1020-lwip-freertos
nxp/nxp-evkmimxrt1024-lwip-freertos
nxp/nxp-evkmimxrt1060-lwip-freertos
nxp/nxp-evkmimxrt1064-lwip-freertos
nxp/nxp-evkmimxrt1160-cm7-lwip-freertos
- group: nxp-2
examples: |
nxp/nxp-evkmimxrt1170-cm7-lwip-freertos
nxp/nxp-frdmk64f-lwip-freertos
nxp/nxp-frdmk66f-lwip-freertos
nxp/nxp-lpcxpresso54018-lwip-freertos
nxp/nxp-lpcxpresso54608-lwip-freertos
nxp/nxp-lpcxpresso54618-lwip-freertos
nxp/nxp-lpcxpresso54628-lwip-freertos
nxp/nxp-twrk65f180m-lwip-freertos
nxp/nxp-twrkv58f220m-lwip-freertos
- group: other - group: other
examples: | examples: |
mqtt/mqtt-client/microchip/same54-xpro mqtt/mqtt-client/microchip/same54-xpro
@@ -696,17 +710,11 @@ jobs:
cflags: "" cflags: ""
examples: | examples: |
http/device-dashboard/microchip/same54-xpro http/device-dashboard/microchip/same54-xpro
nxp/rt1020-evk-make-freertos-builtin
nxp/rt1060-evk-make-freertos-builtin
nxp/rt1170-evk-make-freertos-builtin
renesas/ek-ra6m4-make-baremetal-builtin renesas/ek-ra6m4-make-baremetal-builtin
- group: builtin - group: builtin
cflags: -DMG_TLS=MG_TLS_BUILTIN cflags: -DMG_TLS=MG_TLS_BUILTIN
examples: | examples: |
http/device-dashboard/microchip/same54-xpro http/device-dashboard/microchip/same54-xpro
nxp/rt1020-evk-make-freertos-builtin
nxp/rt1060-evk-make-freertos-builtin
nxp/rt1170-evk-make-freertos-builtin
renesas/ek-ra6m4-make-baremetal-builtin renesas/ek-ra6m4-make-baremetal-builtin
name: device dashboard ARM ${{ matrix.group }} name: device dashboard ARM ${{ matrix.group }}
steps: steps:
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
run: | run: |
min=$(date -u -d '5 hours ago' +%s) min=$(date -u -d '5 hours ago' +%s)
max=$(date -u -d '2 hours ago' +%s) max=$(date -u -d '2 hours ago' +%s)
gh api --paginate "/repos/$GITHUB_REPOSITORY/actions/workflows/nightly.yml/runs?status=failure&per_page=100" --jq '.workflow_runs[] | select(.run_attempt == 1) | [.id, .updated_at] | @tsv' | gh api --paginate "/repos/$GITHUB_REPOSITORY/actions/workflows/nightly.yml/runs?status=failure&per_page=100" --jq '.workflow_runs[] | select(.run_attempt == 1) | [.id, .created_at] | @tsv' |
while IFS=$'\t' read -r id updated; do while IFS=$'\t' read -r id updated; do
when=$(date -u -d "$updated" +%s) when=$(date -u -d "$updated" +%s)
if [ "$when" -ge "$min" ] && [ "$when" -le "$max" ]; then if [ "$when" -ge "$min" ] && [ "$when" -le "$max" ]; then
+2 -14
View File
@@ -53,16 +53,6 @@ jobs:
run: sudo apt -y update && sudo apt -y install gcc-arm-none-eabi run: sudo apt -y update && sudo apt -y install gcc-arm-none-eabi
- run: make -C tutorials/stm32/nucleo-h563zi/minimal test VCON_API_KEY=${{secrets.VCON_API_KEY}} - run: make -C tutorials/stm32/nucleo-h563zi/minimal test VCON_API_KEY=${{secrets.VCON_API_KEY}}
test_rt1020:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with: { fetch-depth: 3 }
- name: baremetal
run: sudo apt -y update && sudo apt -y install gcc-arm-none-eabi
- run: make -C tutorials/nxp/rt1020-evk-make-baremetal-builtin test VCON_API_KEY=${{secrets.VCON_API_KEY}}
- run: make -C tutorials/nxp/rt1020-evk-make-freertos-builtin test VCON_API_KEY=${{secrets.VCON_API_KEY}}
test_rt1060: test_rt1060:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -70,8 +60,7 @@ jobs:
with: { fetch-depth: 3 } with: { fetch-depth: 3 }
- name: baremetal - name: baremetal
run: sudo apt -y update && sudo apt -y install gcc-arm-none-eabi run: sudo apt -y update && sudo apt -y install gcc-arm-none-eabi
- run: make -C tutorials/nxp/rt1060-evk-make-baremetal-builtin test VCON_API_KEY=${{secrets.VCON_API_KEY}} - run: make -C tutorials/nxp/rt1060-evkb/minimal build
- run: make -C tutorials/nxp/rt1060-evk-make-freertos-builtin test VCON_API_KEY=${{secrets.VCON_API_KEY}}
test_rt1170: test_rt1170:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -80,8 +69,7 @@ jobs:
with: { fetch-depth: 3 } with: { fetch-depth: 3 }
- name: baremetal - name: baremetal
run: sudo apt -y update && sudo apt -y install gcc-arm-none-eabi run: sudo apt -y update && sudo apt -y install gcc-arm-none-eabi
- run: make -C tutorials/nxp/rt1170-evk-make-baremetal-builtin test VCON_API_KEY=${{secrets.VCON_API_KEY}} - run: make -C tutorials/nxp/rt1170-evkb/minimal build
- run: make -C tutorials/nxp/rt1170-evk-make-freertos-builtin test VCON_API_KEY=${{secrets.VCON_API_KEY}}
test_ra6m4: test_ra6m4:
runs-on: ubuntu-latest runs-on: ubuntu-latest
+7 -9
View File
@@ -148,11 +148,12 @@ jobs:
- path: infineon/xmc47_relax-make-baremetal-builtin - path: infineon/xmc47_relax-make-baremetal-builtin
- path: infineon/xmc_plt2go_4400-make-baremetal-builtin - path: infineon/xmc_plt2go_4400-make-baremetal-builtin
- path: infineon/xmc72_evk-make-baremetal-builtin - path: infineon/xmc72_evk-make-baremetal-builtin
- path: nxp/frdm-mcxn947-make-baremetal-builtin - path: nxp/frdm-imxrt1186/minimal
- path: nxp/frdm-mcxn947-make-freertos-builtin - path: nxp/frdm-mcxe247/minimal
- path: nxp/rt1020-evk-make-baremetal-builtin - path: nxp/frdm-mcxn947/minimal
- path: nxp/rt1060-evk-make-baremetal-builtin - path: nxp/frdm-rw612/minimal
- path: nxp/rt1170-evk-make-baremetal-builtin - path: nxp/rt1060-evkb/minimal
- path: nxp/rt1170-evkb/minimal
- path: stm32/nucleo-f429zi/minimal - path: stm32/nucleo-f429zi/minimal
- path: stm32/nucleo-f746zg/minimal - path: stm32/nucleo-f746zg/minimal
- path: stm32/nucleo-h563zi/minimal - path: stm32/nucleo-h563zi/minimal
@@ -169,7 +170,7 @@ jobs:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
with: { fetch-depth: 2 } with: { fetch-depth: 2 }
- run: | - run: |
if ./test/match_changed_files.sh '^src'; then if ./test/match_changed_files.sh '^src|^tutorials/${{ matrix.example.path }}'; then
echo GO=1 >> $GITHUB_ENV echo GO=1 >> $GITHUB_ENV
sudo apt -y update && sudo apt -y install gcc-arm-none-eabi sudo apt -y update && sudo apt -y install gcc-arm-none-eabi
fi fi
@@ -248,9 +249,6 @@ jobs:
matrix: matrix:
example: example:
- path: http/device-dashboard/microchip/same54-xpro - path: http/device-dashboard/microchip/same54-xpro
- path: nxp/rt1020-evk-make-freertos-builtin
- path: nxp/rt1060-evk-make-freertos-builtin
- path: nxp/rt1170-evk-make-freertos-builtin
- path: renesas/ek-ra6m4-make-baremetal-builtin - path: renesas/ek-ra6m4-make-baremetal-builtin
- path: rp/pico-rndis - path: rp/pico-rndis
- path: rp/pico-w - path: rp/pico-w
+1467 -136
View File
File diff suppressed because it is too large. Load diff
+363 -12
View File
@@ -447,7 +447,7 @@ static inline uint32_t mg_ota_esp32_state_set(uint32_t state) {
#endif #endif
#if MG_ENABLE_TCPIP && !defined(MG_ENABLE_DRIVER_IMXRT10) && \ #if MG_ENABLE_TCPIP && !defined(MG_ENABLE_DRIVER_IMXRT10) && \
!defined(MG_ENABLE_DRIVER_IMXRT11) !defined(MG_ENABLE_DRIVER_IMXRT11) && !defined(MG_ENABLE_DRIVER_NETC)
#if defined(CPU_MIMXRT1021DAG5A) || defined(CPU_MIMXRT1024DAG5A) || \ #if defined(CPU_MIMXRT1021DAG5A) || defined(CPU_MIMXRT1024DAG5A) || \
defined(CPU_MIMXRT1042XJM5B) || defined(CPU_MIMXRT1052DVL6B) || \ defined(CPU_MIMXRT1042XJM5B) || defined(CPU_MIMXRT1052DVL6B) || \
defined(CPU_MIMXRT1062DVL6B) || defined(CPU_MIMXRT1064DVL6B) || \ defined(CPU_MIMXRT1062DVL6B) || defined(CPU_MIMXRT1064DVL6B) || \
@@ -462,6 +462,8 @@ static inline uint32_t mg_ota_esp32_state_set(uint32_t state) {
defined(_MIMXRT1176_cm7_H_) || defined(_MIMXRT1176_cm4_H_) || \ defined(_MIMXRT1176_cm7_H_) || defined(_MIMXRT1176_cm4_H_) || \
defined(_MIMXRT1176_H_) defined(_MIMXRT1176_H_)
#define MG_ENABLE_DRIVER_IMXRT11 1 #define MG_ENABLE_DRIVER_IMXRT11 1
#elif defined(CPU_MIMXRT1186CVJ8C_cm33) || defined(MIMXRT1186_cm33_H_)
#define MG_ENABLE_DRIVER_NETC 1
#else #else
#error Select an Ethernet driver in mongoose_config.h #error Select an Ethernet driver in mongoose_config.h
#endif #endif
@@ -493,13 +495,31 @@ static inline uint32_t mg_ota_esp32_state_set(uint32_t state) {
defined(_MIMXRT1176_cm7_H_) || defined(_MIMXRT1176_cm4_H_) || \ defined(_MIMXRT1176_cm7_H_) || defined(_MIMXRT1176_cm4_H_) || \
defined(_MIMXRT1176_H_)) defined(_MIMXRT1176_H_))
#define MG_OTA MG_OTA_RT1170 #define MG_OTA MG_OTA_RT1170
#elif !defined(MG_OTA) && \
(defined(CPU_MIMXRT1186CVJ8C_cm33) || defined(MIMXRT1186_cm33_H_))
#define MG_OTA MG_OTA_RT1180
#endif #endif
#ifndef MG_IRAM #ifndef MG_IRAM
#if defined(CPU_MIMXRT1186CVJ8C_cm33) || defined(MIMXRT1186_cm33_H_)
#define MG_IRAM \
__attribute__((noinline, section(".ramfunc.$SRAM_ITC_cm33")))
#else
#define MG_IRAM __attribute__((noinline, section(".data_RAM2"))) #define MG_IRAM __attribute__((noinline, section(".data_RAM2")))
#endif #endif
#endif
#ifndef MG_ETH_RAM
#if defined(CPU_MIMXRT1186CVJ8C_cm33) || defined(MIMXRT1186_cm33_H_)
#define MG_ETH_RAM __attribute__((section(".bss.$ETH_RAM")))
#endif
#endif
#ifndef MG_SET_MAC_ADDRESS #ifndef MG_SET_MAC_ADDRESS
#if defined(MG_ENABLE_DRIVER_NETC) && MG_ENABLE_DRIVER_NETC
// No OCOTP_FUSES present on RT1186 to generate the UUID
#define MG_SET_MAC_ADDRESS(mac) (void) (mac)
#else
#if defined(MG_ENABLE_DRIVER_IMXRT11) && MG_ENABLE_DRIVER_IMXRT11 #if defined(MG_ENABLE_DRIVER_IMXRT11) && MG_ENABLE_DRIVER_IMXRT11
#define MG_OCOTP_FUSES ((volatile uint32_t *) 0x40cac900) #define MG_OCOTP_FUSES ((volatile uint32_t *) 0x40cac900)
#else #else
@@ -515,11 +535,70 @@ static inline uint32_t mg_ota_esp32_state_set(uint32_t state) {
mac[5] = (MG_OCOTP_FUSES[4] >> 0) & 255; \ mac[5] = (MG_OCOTP_FUSES[4] >> 0) & 255; \
} while (0) } while (0)
#endif #endif
#endif
#ifndef MG_IMXRT_WDOG1_TIMEOUT_MS #ifndef MG_IMXRT_WDOG1_TIMEOUT_MS
#define MG_IMXRT_WDOG1_TIMEOUT_MS 10000 #define MG_IMXRT_WDOG1_TIMEOUT_MS 10000U
#endif #endif
#if defined(CPU_MIMXRT1186CVJ8C_cm33) || defined(MIMXRT1186_cm33_H_)
#define MG_IMXRT_RTWDOG1_UNLOCK() \
do { \
if ((RTWDOG1->CS & RTWDOG_CS_CMD32EN_MASK) != 0U) { \
RTWDOG1->CNT = RTWDOG_UPDATE_KEY; \
} else { \
RTWDOG1->CNT = RTWDOG_UPDATE_KEY & 0xffffU; \
RTWDOG1->CNT = RTWDOG_UPDATE_KEY >> 16; \
} \
while ((RTWDOG1->CS & RTWDOG_CS_ULK_MASK) == 0U) (void) 0; \
} while (0)
#define MG_IMXRT_RTWDOG1_FEED() \
do { \
uint32_t primask_ = __get_PRIMASK(); \
__disable_irq(); \
if ((RTWDOG1->CS & RTWDOG_CS_CMD32EN_MASK) != 0U) { \
RTWDOG1->CNT = RTWDOG_REFRESH_KEY; \
} else { \
RTWDOG1->CNT = RTWDOG_REFRESH_KEY & 0xffffU; \
RTWDOG1->CNT = RTWDOG_REFRESH_KEY >> 16; \
} \
if (primask_ == 0U) __enable_irq(); \
} while (0)
#ifndef MG_OTA_ROLLBACK_TIMER_START
#define MG_OTA_ROLLBACK_TIMER_START() \
do { \
uint32_t primask_ = __get_PRIMASK(); \
SRC_GENERAL_REG->SRMASK &= ~SRC_GENERAL_SRMASK_WDOG1_MASK_MASK; \
__disable_irq(); \
MG_IMXRT_RTWDOG1_UNLOCK(); \
RTWDOG1->WIN = 0U; \
RTWDOG1->TOVAL = (MG_IMXRT_WDOG1_TIMEOUT_MS + 7U) / 8U; \
RTWDOG1->CS = RTWDOG_CS_EN_MASK | RTWDOG_CS_UPDATE_MASK | \
RTWDOG_CS_CLK(1U) | RTWDOG_CS_PRES_MASK | \
RTWDOG_CS_CMD32EN_MASK; \
while ((RTWDOG1->CS & RTWDOG_CS_RCS_MASK) == 0U) (void) 0; \
if (primask_ == 0U) __enable_irq(); \
} while (0)
#endif
#ifndef MG_OTA_ROLLBACK_TIMER_FEED
#define MG_OTA_ROLLBACK_TIMER_FEED() MG_IMXRT_RTWDOG1_FEED()
#endif
// GPR5 survives RTWDOG global resets and is not reserved by the boot ROM.
#ifndef MG_OTA_STATE_GET
#define MG_OTA_STATE_GET() SRC_GENERAL_REG->GPR[5]
#endif
#ifndef MG_OTA_STATE_SET
#define MG_OTA_STATE_SET(v) (SRC_GENERAL_REG->GPR[5] = (uint32_t) (v))
#endif
#else
#define MG_IMXRT_WDOG1_FEED() \ #define MG_IMXRT_WDOG1_FEED() \
do { \ do { \
WDOG1->WSR = 0x5555; \ WDOG1->WSR = 0x5555; \
@@ -562,6 +641,8 @@ static inline uint32_t mg_ota_esp32_state_set(uint32_t state) {
#endif #endif
#endif
#if MG_ARCH == MG_ARCH_PICOSDK #if MG_ARCH == MG_ARCH_PICOSDK
#if !defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP #if !defined(MG_ENABLE_LWIP) || !MG_ENABLE_LWIP
@@ -1375,6 +1456,14 @@ struct timeval {
#define MG_ENABLE_TCPIP_DRIVER_INIT 1 // enabled built-in driver for #define MG_ENABLE_TCPIP_DRIVER_INIT 1 // enabled built-in driver for
#endif // Mongoose built-in network stack #endif // Mongoose built-in network stack
#ifndef MG_ENABLE_TCPIP_TCPRTX
#define MG_ENABLE_TCPIP_TCPRTX 0 // TCP retransmission queue
#endif
#ifndef MG_TCPIP_DHCPNAME_SIZE
#define MG_TCPIP_DHCPNAME_SIZE 18 // struct mg_tcpip_if :: dhcp_name size
#endif
#ifndef MG_TCPIP_IP // e.g. MG_IPV4(192, 168, 0, 223) #ifndef MG_TCPIP_IP // e.g. MG_IPV4(192, 168, 0, 223)
#define MG_TCPIP_IP MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0 (DHCP) #define MG_TCPIP_IP MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0 (DHCP)
#endif #endif
@@ -1411,10 +1500,6 @@ struct timeval {
#define MG_SET_MAC_ADDRESS(mac) #define MG_SET_MAC_ADDRESS(mac)
#endif #endif
#ifndef MG_TCPIP_DHCPNAME_SIZE
#define MG_TCPIP_DHCPNAME_SIZE 18 // struct mg_tcpip_if :: dhcp_name size
#endif
#ifndef MG_SET_WIFI_CONFIG #ifndef MG_SET_WIFI_CONFIG
#define MG_SET_WIFI_CONFIG(ifp, driver_data) #define MG_SET_WIFI_CONFIG(ifp, driver_data)
#endif #endif
@@ -1671,8 +1756,8 @@ typedef size_t (*mg_pm_t)(mg_pfn_t fn, void *arg, va_list *);
// - mg_print_base64 - prints a buffer as a base64-encoded string // - mg_print_base64 - prints a buffer as a base64-encoded string
// - mg_print_esc - prints a JSON-escaped string // - mg_print_esc - prints a JSON-escaped string
// - mg_print_hex - prints a buffer as a hex string // - mg_print_hex - prints a buffer as a hex string
// - mg_print_ip - prints an IP address in a struct mg_str // - mg_print_ip - prints an IP address in a struct mg_addr
// - mg_print_ip_port - prints IP address and port in a struct mg_str // - mg_print_ip_port - prints IP address and port in a struct mg_addr
// - mg_print_ip4 - prints an IPv4 address // - mg_print_ip4 - prints an IPv4 address
// - mg_print_ip6 - prints an IPv6 address // - mg_print_ip6 - prints an IPv6 address
// - mg_print_mac - prints a MAC address // - mg_print_mac - prints a MAC address
@@ -1730,15 +1815,15 @@ char *mg_mprintf(const char *fmt, ...);
size_t mg_queue_printf(struct mg_queue *, const char *fmt, ...); size_t mg_queue_printf(struct mg_queue *, const char *fmt, ...);
// Built-in %M/%m printer functions. Each reads its argument(s) from ap. // Built-in %M/%m printer functions. Each reads its argument(s) from ap.
size_t mg_print_base64(mg_pfn_t, void *arg, va_list *ap); // expects: const void *buf, size_t len size_t mg_print_base64(mg_pfn_t, void *arg, va_list *ap); // expects: int len, uint8_t *buf
size_t mg_print_esc(mg_pfn_t, void *arg, va_list *ap); // expects: int len, const char *str -- use MG_ESC() size_t mg_print_esc(mg_pfn_t, void *arg, va_list *ap); // expects: int len, const char *str -- use MG_ESC()
size_t mg_print_hex(mg_pfn_t, void *arg, va_list *ap); // expects: const void *buf, size_t len size_t mg_print_hex(mg_pfn_t, void *arg, va_list *ap); // expects: int len, uint8_t *buf
size_t mg_print_ip(mg_pfn_t, void *arg, va_list *ap); // expects: const struct mg_addr * size_t mg_print_ip(mg_pfn_t, void *arg, va_list *ap); // expects: const struct mg_addr *
size_t mg_print_ip_port(mg_pfn_t, void *arg, va_list *ap); // expects: const struct mg_addr * size_t mg_print_ip_port(mg_pfn_t, void *arg, va_list *ap); // expects: const struct mg_addr *
size_t mg_print_ip4(mg_pfn_t, void *arg, va_list *ap); // expects: uint32_t *ipv4 size_t mg_print_ip4(mg_pfn_t, void *arg, va_list *ap); // expects: uint32_t *ipv4
size_t mg_print_ip6(mg_pfn_t, void *arg, va_list *ap); // expects: uint8_t[16] ipv6 size_t mg_print_ip6(mg_pfn_t, void *arg, va_list *ap); // expects: uint8_t[16] ipv6
size_t mg_print_mac(mg_pfn_t, void *arg, va_list *ap); // expects: uint8_t[6] mac size_t mg_print_mac(mg_pfn_t, void *arg, va_list *ap); // expects: uint8_t[6] mac
size_t mg_print_ieee64(mg_pfn_t, void *arg, va_list *ap); // expects: uint64_t size_t mg_print_ieee64(mg_pfn_t, void *arg, va_list *ap); // expects: uint8_t[8] ieee64
size_t mg_print_l2addr(mg_pfn_t, void *arg, va_list *ap); // expects: uint8_t l2, uint8_t[n] n-byte l2-dependent address size_t mg_print_l2addr(mg_pfn_t, void *arg, va_list *ap); // expects: uint8_t l2, uint8_t[n] n-byte l2-dependent address
size_t mg_print_html_esc(mg_pfn_t, void *arg, va_list *ap); // expects: int len, const char *str -- use MG_ESC() size_t mg_print_html_esc(mg_pfn_t, void *arg, va_list *ap); // expects: int len, const char *str -- use MG_ESC()
@@ -2358,6 +2443,7 @@ struct mg_mgr {
struct mg_tcpip_if *ifp; // Builtin TCP/IP stack: network interface pointer struct mg_tcpip_if *ifp; // Builtin TCP/IP stack: network interface pointer
size_t extraconnsize; // Builtin TCP/IP stack: extra bytes allocated per connection size_t extraconnsize; // Builtin TCP/IP stack: extra bytes allocated per connection
union mg_pipe pipe; // Socketpair write-end / queue, used by mg_wakeup() union mg_pipe pipe; // Socketpair write-end / queue, used by mg_wakeup()
struct mg_rpc *rpcs; // Linked list of registered RPC handlers
#if MG_ENABLE_FREERTOS_TCP #if MG_ENABLE_FREERTOS_TCP
SocketSet_t ss; // FreeRTOS-TCP socket set SocketSet_t ss; // FreeRTOS-TCP socket set
#endif #endif
@@ -2749,6 +2835,29 @@ size_t mg_http_next_multipart(struct mg_str, size_t, struct mg_http_part *);
// Returns the HTTP status code from a parsed response message (e.g. 200, 404). // Returns the HTTP status code from a parsed response message (e.g. 200, 404).
int mg_http_status(const struct mg_http_message *hm); int mg_http_status(const struct mg_http_message *hm);
// Streams a POST/PUT body to a callback as it arrives, without buffering it.
// Call it from your connection handler on every event.
//
// Callback upload_fn(hm, data, user_data) is called:
// - at start: hm is set, data is NULL. Open the file, set user_data.
// - per chunk: hm is NULL, data is set. Chunks are 512 bytes aligned
// except the last one.
// - at end: both NULL. Close the file.
// After the last chunk, "200 ok" is sent and the connection is drained.
// Example:
// mg_http_stream_body(c, ev, ev_data, mg_str("/fs/*"), upload);
// Full example:
// tutorials/http/file-upload-single-post
// Related APIs:
// mg_http_start_upload(), mg_http_start_ota()
// Notes:
// Requires Content-Length, chunked bodies are not streamed. user_data is
// &c->fn_data. While streaming, c->pfn is NULL
void mg_http_stream_body(struct mg_connection *c, int ev, void *ev_data,
struct mg_str uri_pattern,
void (*upload_fn)(struct mg_http_message *,
struct mg_str *, void **user_data));
void mg_http_serve_ssi(struct mg_connection *c, const char *root, void mg_http_serve_ssi(struct mg_connection *c, const char *root,
const char *fullpath); const char *fullpath);
@@ -2835,7 +2944,7 @@ void mg_tls_free(struct mg_connection *);
long mg_tls_send(struct mg_connection *, const void *buf, size_t len); long mg_tls_send(struct mg_connection *, const void *buf, size_t len);
long mg_tls_recv(struct mg_connection *, void *buf, size_t len); long mg_tls_recv(struct mg_connection *, void *buf, size_t len);
size_t mg_tls_pending(struct mg_connection *); size_t mg_tls_pending(struct mg_connection *);
void mg_tls_flush(struct mg_connection *); long mg_tls_flush(struct mg_connection *);
void mg_tls_handshake(struct mg_connection *); void mg_tls_handshake(struct mg_connection *);
void mg_tls_ctx_init(struct mg_mgr *); void mg_tls_ctx_init(struct mg_mgr *);
void mg_tls_ctx_free(struct mg_mgr *); void mg_tls_ctx_free(struct mg_mgr *);
@@ -4667,6 +4776,7 @@ extern void mg_mqtt_poll(struct mg_mgr *);
#define MG_OTA_RT1060 302 // IMXRT1060 #define MG_OTA_RT1060 302 // IMXRT1060
#define MG_OTA_RT1064 303 // IMXRT1064 #define MG_OTA_RT1064 303 // IMXRT1064
#define MG_OTA_RT1170 304 // IMXRT1170 #define MG_OTA_RT1170 304 // IMXRT1170
#define MG_OTA_RT1180 305 // IMXRT1180
#define MG_OTA_MCXN 310 // MCXN947 #define MG_OTA_MCXN 310 // MCXN947
#define MG_OTA_RW612 320 // FRDM-RW612 #define MG_OTA_RW612 320 // FRDM-RW612
#define MG_OTA_FLASH 900 // OTA via internal flash #define MG_OTA_FLASH 900 // OTA via internal flash
@@ -4833,6 +4943,196 @@ void mg_ota_device_id(char *buf, size_t len);
// mg_mgr_poll() when MG_OTA_URL is set in mongoose_config.h. Do not call // mg_mgr_poll() when MG_OTA_URL is set in mongoose_config.h. Do not call
// directly. // directly.
void mg_ota_poll(struct mg_mgr *); void mg_ota_poll(struct mg_mgr *);
// Device health monitoring
//
// A health provider owns the report format and returns it as an opaque blob.
// Applications and transports use mg_health_get_blob() without knowing how the
// report was collected or encoded.
#define MG_HEALTH_NONE 0
#define MG_HEALTH_CORTEX 1
#ifndef MG_HEALTH
#define MG_HEALTH MG_HEALTH_NONE
#endif
#ifndef MG_HEALTH_MAGIC
#define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version
#endif
// Why the device restarted. Hardware usually reports several flags at once,
// so these are ordered by how much they tell you: the most specific cause a
// device reports wins
enum mg_health_reason {
MG_HEALTH_RESET_UNKNOWN = 0, // Nothing reported, or no masks configured
MG_HEALTH_RESET_POWER, // Power-on reset, RAM contents are lost
MG_HEALTH_RESET_BROWNOUT, // Supply dipped below the brown-out level
MG_HEALTH_RESET_PIN, // External reset pin, e.g. the reset button
MG_HEALTH_RESET_SOFTWARE, // Firmware asked for it, e.g. NVIC_SystemReset
MG_HEALTH_RESET_LOWPOWER, // Illegal or failed low-power transition
MG_HEALTH_RESET_WATCHDOG, // Watchdog fired: the application stopped
MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack
};
typedef struct mg_str mg_health_blob;
struct mg_health {
char magic[4];
mg_health_blob (*get_blob)(void *);
void *fn_data;
uint32_t reset_reason;
};
extern struct mg_health mg_health_record;
// Return true if the record holds valid data, i.e. it survived the reset and
// was written by this firmware layout
static inline bool mg_health_valid(void) {
static const char magic[] = MG_HEALTH_MAGIC;
return memcmp(mg_health_record.magic, magic,
sizeof(mg_health_record.magic)) == 0;
}
// Select and initialise the configured health provider. Call after
// initializing the board-specific persistent-storage
void mg_health_init(void);
// Return the provider's opaque report. The returned memory is provider-owned
// and remains valid until the next call to this function
struct mg_str mg_health_get_blob(void);
static inline int mg_health_reason(void) {
return mg_health_valid() ? (int) mg_health_record.reset_reason
: MG_HEALTH_RESET_UNKNOWN;
}
static inline const char *mg_health_reason_str(int reason) {
switch (reason) {
case MG_HEALTH_RESET_POWER:
return "power";
case MG_HEALTH_RESET_BROWNOUT:
return "brownout";
case MG_HEALTH_RESET_PIN:
return "pin";
case MG_HEALTH_RESET_SOFTWARE:
return "software";
case MG_HEALTH_RESET_LOWPOWER:
return "lowpower";
case MG_HEALTH_RESET_WATCHDOG:
return "watchdog";
case MG_HEALTH_RESET_FAULT:
return "fault";
default:
return "unknown";
}
}
#define MG_HEALTH_INIT() mg_health_init()
#define MG_HEALTH_CORTEX_VERSION 1U
#ifndef MG_HEALTH_CORTEX_STORAGE
#define MG_HEALTH_CORTEX_STORAGE NULL
#endif
#ifndef MG_HEALTH_CORTEX_STORAGE_SIZE
#define MG_HEALTH_CORTEX_STORAGE_SIZE 0
#endif
#ifndef MG_HEALTH_CORTEX_STACK_WORDS
#define MG_HEALTH_CORTEX_STACK_WORDS 980U
#endif
#ifndef MG_HEALTH_CORTEX_REPORT_SIZE
#define MG_HEALTH_CORTEX_REPORT_SIZE 16384U
#endif
// Storage operations are deliberately format-agnostic. H723 uses the default
// direct-memory implementation; boards with different retained storage can
// replace these callbacks before calling MG_HEALTH_INIT()
struct mg_health_cortex {
void *storage;
size_t storage_size;
bool (*init)(void *, size_t);
bool (*read)(const void *, size_t, void *, size_t);
bool (*write)(void *, size_t, const void *, size_t);
};
struct mg_health_cortex_record {
char magic[4];
uint32_t version;
uint32_t cpuid;
uint32_t exception_sp;
uint32_t sp;
uint32_t exc_return;
int32_t exception_valid;
int32_t stack_valid;
uint32_t r0;
uint32_t r1;
uint32_t r2;
uint32_t r3;
uint32_t r4;
uint32_t r5;
uint32_t r6;
uint32_t r7;
uint32_t r8;
uint32_t r9;
uint32_t r10;
uint32_t r11;
uint32_t r12;
uint32_t lr;
uint32_t pc;
uint32_t psr;
uint32_t cfsr;
uint32_t hfsr;
uint32_t dfsr;
uint32_t afsr;
uint32_t bfar;
uint32_t mmfar;
uint32_t abfsr;
uint32_t stack_word_count;
uint32_t stack_words[MG_HEALTH_CORTEX_STACK_WORDS];
};
extern struct mg_health_cortex mg_health_cortex;
void mg_health_cortex_init(struct mg_health_cortex *);
struct mg_str mg_health_cortex_get_blob(void *);
// Cloud connector API to https://mdash.net - device management cloud
#ifndef MG_MDASH_URL
#define MG_MDASH_URL "wss://mdash.net/api/v2/rpc"
#endif
#ifndef MG_MDASH_KEY
#define MG_MDASH_KEY ""
#endif
#ifndef MG_MDASH_FIRMWARE_VERSION
#define MG_MDASH_FIRMWARE_VERSION "1.0.0"
#endif
#ifndef MG_ENABLE_MDASH
#define MG_ENABLE_MDASH 0
#endif
void mg_mdash_init(struct mg_mgr *);
void mg_mdash_poll(struct mg_mgr *);
@@ -5018,6 +5318,7 @@ struct mg_tcpip_if {
bool enable_req_sntp; // DHCP client requests an SNTP server address bool enable_req_sntp; // DHCP client requests an SNTP server address
bool enable_fcs_check; // Verify and strip FCS from received frames bool enable_fcs_check; // Verify and strip FCS from received frames
bool enable_mac_check; // Drop frames not addressed to this MAC bool enable_mac_check; // Drop frames not addressed to this MAC
bool enable_tcp_retransmit; // Enable optional TCP retransmission queue
bool update_mac_hash_table; // Signal driver to refresh MAC multicast hash table bool update_mac_hash_table; // Signal driver to refresh MAC multicast hash table
bool is_ip_changed; // Set by stack when IP changes; triggers connection restart bool is_ip_changed; // Set by stack when IP changes; triggers connection restart
struct mg_tcpip_driver *driver; // Hardware driver; must be set before mg_tcpip_init() struct mg_tcpip_driver *driver; // Hardware driver; must be set before mg_tcpip_init()
@@ -5111,6 +5412,7 @@ extern struct mg_tcpip_driver mg_tcpip_driver_cyw;
extern struct mg_tcpip_driver mg_tcpip_driver_nxp_wifi; extern struct mg_tcpip_driver mg_tcpip_driver_nxp_wifi;
extern struct mg_tcpip_driver mg_tcpip_driver_st67w6; extern struct mg_tcpip_driver mg_tcpip_driver_st67w6;
extern struct mg_tcpip_driver mg_tcpip_driver_atcmd; extern struct mg_tcpip_driver mg_tcpip_driver_atcmd;
extern struct mg_tcpip_driver mg_tcpip_driver_netc;
// SPI bus abstraction for SPI-attached network chips (e.g. W5500). // SPI bus abstraction for SPI-attached network chips (e.g. W5500).
// Populate and assign to the driver's driver_data field. // Populate and assign to the driver's driver_data field.
@@ -5455,6 +5757,55 @@ struct mg_tcpip_driver_imxrt_data {
#endif #endif
#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP && \
defined(MG_ENABLE_DRIVER_NETC) && MG_ENABLE_DRIVER_NETC
struct mg_tcpip_driver_netc_data {
int mdc_cr;
uint8_t phy_addr;
// Selects the physical Ethernet link (ETH0 through ETH3)
// Its switch port is bridged to pseudo-port 4,
// which connects internally to ENETC1
// IMXRT1180 RM 53.4.1.1 NETC Block diagram
uint8_t link;
};
#ifndef MG_TCPIP_PHY_ADDR
#define MG_TCPIP_PHY_ADDR 5
#endif
#ifndef MG_DRIVER_MDC_CR
#define MG_DRIVER_MDC_CR 48
#endif
#ifndef MG_TCPIP_NETC_LINK
#define MG_TCPIP_NETC_LINK 0
#endif
struct mg_tcpip_driver;
extern struct mg_tcpip_driver mg_tcpip_driver_netc;
#define MG_TCPIP_DRIVER_INIT(mgr) \
do { \
static struct mg_tcpip_driver_netc_data driver_data_; \
static struct mg_tcpip_if mif_; \
driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
driver_data_.link = MG_TCPIP_NETC_LINK; \
mif_.ip = MG_TCPIP_IP; \
mif_.mask = MG_TCPIP_MASK; \
mif_.gw = MG_TCPIP_GW; \
mif_.driver = &mg_tcpip_driver_netc; \
mif_.driver_data = &driver_data_; \
MG_SET_MAC_ADDRESS(mif_.mac); \
mg_tcpip_init(mgr, &mif_); \
MG_INFO(("Driver: netc, MAC: %M", mg_print_mac, mif_.mac)); \
} while (0)
#endif
#if MG_ENABLE_TCPIP && \ #if MG_ENABLE_TCPIP && \
defined(MG_ENABLE_DRIVER_NXP_WIFI) && MG_ENABLE_DRIVER_NXP_WIFI defined(MG_ENABLE_DRIVER_NXP_WIFI) && MG_ENABLE_DRIVER_NXP_WIFI
@@ -39,8 +39,7 @@ patch_linker_script() {
HUART=`perl -nle 'print \$1 if /^UART_HandleTypeDef (.+);/' $MAIN_C` HUART=`perl -nle 'print \$1 if /^UART_HandleTypeDef (.+);/' $MAIN_C`
grep -q mongoose.h $MAIN_C || perl -i -ne 'print; print "#include \"mongoose.h\"\n" if /BEGIN Includes/' $MAIN_C grep -q mongoose.h $MAIN_C || perl -i -ne 'print; print "#include \"mongoose.h\"\n" if /BEGIN Includes/' $MAIN_C
grep -q '^static void log_fn' $MAIN_C || perl -i -ne "print; print \"static void log_fn(char ch, void *param) {\n HAL_UART_Transmit(param, (unsigned char *) &ch, 1, HAL_MAX_DELAY);\n}\n\" if /USER CODE BEGIN 0/" $MAIN_C grep -q '^static void log_fn' $MAIN_C || perl -i -ne "print; print \"static void log_fn(char ch, void *param) {\n HAL_UART_Transmit(param, (unsigned char *) &ch, 1, HAL_MAX_DELAY);\n}\n\" if /USER CODE BEGIN 0/" $MAIN_C
grep -q 'mg_mgr_init' $MAIN_C || perl -i -ne 'print; print " struct mg_mgr mgr;\n mg_mgr_init(&mgr);\n mg_log_set_fn(log_fn, &'"${HUART}"');\n\n" if /USER CODE BEGIN WHILE/' $MAIN_C grep -q 'mg_mgr_init' $MAIN_C || perl -i -ne 'print; print " struct mg_mgr mgr;\n mg_log_set_fn(log_fn, &'"${HUART}"');\n mg_mgr_init(&mgr);\n mg_log_set_fn(log_fn, &'"${HUART}"');\n\n" if /USER CODE BEGIN WHILE/' $MAIN_C
grep -q 'mg_mgr_init' $MAIN_C && grep -q 'mg_log_set_fn' $MAIN_C || perl -i -ne 'print; print " mg_log_set_fn(log_fn, &'"${HUART}"');\n" if /mg_mgr_init/' $MAIN_C
grep -q 'mg_mgr_poll' $MAIN_C || perl -i -ne 'print " mg_mgr_poll(&mgr, 0);\n" if /USER CODE END WHILE/; print;' $MAIN_C grep -q 'mg_mgr_poll' $MAIN_C || perl -i -ne 'print " mg_mgr_poll(&mgr, 0);\n" if /USER CODE END WHILE/; print;' $MAIN_C
patch_linker_script $DIR/STM32H723XG_FLASH.ld patch_linker_script $DIR/STM32H723XG_FLASH.ld
patch_linker_script $DIR/STM32H743XX_FLASH.ld patch_linker_script $DIR/STM32H743XX_FLASH.ld
+38
View File
@@ -0,0 +1,38 @@
# Security Scan Area: common_misc
## Target
Area: common_misc
This area covers common utility code, parsing/string/formatting helpers, URL handling, hash/base64 helpers, filesystem backends, little filesystem integration, and flash support.
Primary files:
- `src/base64.c`
- `src/fmt.c`
- `src/json.c`
- `src/log.c`
- `src/md5.c`
- `src/printf.c`
- `src/sha1.c`
- `src/sha256.c`
- `src/str.c`
- `src/url.c`
- `src/util.c`
- `src/fs*.c`
- `src/lfs.c`
- `src/flash.c`
Perform a focused security review centered on these files and this security surface.
Supporting code elsewhere under `src/` may be inspected when necessary to establish reachability, data flow, state, validation, mitigation, or impact, but do not inspect or report code outside `src/` or independently review unrelated areas.
## Area-Specific Security Guidance
**Filesystem Backends:**
- Review packed filesystem code and filesystem backends for traversal, unauthorized file read/write, overwrite, or truncation.
- Check path canonicalization across POSIX, Windows, embedded filesystems, packed filesystems, and custom `mg_fs` implementations.
- Look for trailing slash confusion, NUL byte truncation, and case sensitivity differences in backend path handling.
**URL Parsing:**
- Review URL parsing for scheme, host, port, IPv6 literal, userinfo, percent-encoding, embedded NUL, empty host, default port, and path/query boundary confusion.
- Check for SSRF-relevant behavior only when attacker input can control the destination host, protocol, or network boundary. Path-only control is not sufficient.
+30
View File
@@ -0,0 +1,30 @@
# Security Scan Area: mongoose_core
## Target
Area: mongoose_core
This area covers the core event/network/connection machinery, iobuf handling, queues, socket transport, timers, and BSD abstraction.
Primary files:
- `src/event.c`
- `src/iobuf.c`
- `src/net.c`
- `src/queue.c`
- `src/sock.c`
- `src/timer.c`
- `src/bsd.c`
Perform a focused security review centered on these files and this security surface.
Supporting code elsewhere under `src/` may be inspected when necessary to establish reachability, data flow, state, validation, mitigation, or impact, but do not inspect or report code outside `src/` or independently review unrelated areas.
## Area-Specific Security Guidance
**Protocol State Machine and Event-Driven Lifecycle Bugs:**
- Review transitions between listening, accepted, connecting, resolving, TLS handshaking, HTTP, WebSocket, MQTT, draining, closing, and closed states.
- Check whether malformed input can cause protocol handlers to process data in the wrong state, process stale data after close, or invoke callbacks with invalid event data.
- Look for use-after-free or stale pointer use when callbacks call `mg_close_conn`, mark a connection closing, mutate iobufs, change protocol handlers, or initiate TLS/WebSocket/MQTT transitions.
- Verify that partial reads/writes, backpressure, `is_full`, draining, TLS pending states, and send-buffer mutations cannot cause duplicated sends, skipped validation, or parser confusion.
- Review timer and wakeup handling for stale user data pointers, cross-thread queue misuse, and callbacks firing after associated connections/state have been freed.
- Only report concurrency or race issues when a concrete event ordering can trigger memory corruption, unauthorized action, or reliable device/process crash.
+26
View File
@@ -0,0 +1,26 @@
# Security Scan Area: net_builtin
## Target
Area: net_builtin
This area covers the built-in TCP/IP stack and its selected L2 implementations.
Primary files:
- `src/l2_*.c`
- `src/net_builtin.c`
Perform a focused security review centered on these files and this security surface.
Supporting code elsewhere under `src/` may be inspected when necessary to establish reachability, data flow, state, validation, mitigation, or impact, but do not inspect or report code outside `src/` or independently review unrelated areas.
## Area-Specific Security Guidance
**Built-In TCP/IP Stack, Packet Parsing, and Embedded Network Drivers:**
- Review Ethernet, ARP, IP, IPv6, ICMP, UDP, TCP, DHCP, SNTP, and driver receive/transmit paths for packet-length validation before reading protocol headers.
- Check IP/TCP/UDP header length fields, options length, fragmentation/reassembly behavior, checksum handling, and payload offset calculations for underflow, overflow, or out-of-bounds access.
- Look for malformed packets that can corrupt descriptor rings, RX/TX buffers, queue state, ARP tables, connection state, or timers.
- Review interrupt handlers and bare-metal driver code for bounded processing loops. Flag IRQ paths where attacker-controlled traffic can cause livelock, starvation, descriptor ownership loss, or repeated processing of the same malformed frame.
- Check DMA descriptor ownership transitions, memory barriers, cache coherency handling, and buffer length fields for race-like bugs that can cause memory corruption or stale packet reuse.
- Review platform-specific drivers such as STM32, NXP, TI, Microchip, Renesas, Infineon, Wiznet, Cypress WiFi, cellular, and other built-in stack integrations for inconsistent bounds checks or endian conversions.
- Treat a remotely reachable malformed Ethernet/IP/TCP/UDP packet that crashes, wedges, or corrupts an embedded target as reportable.
+38
View File
@@ -0,0 +1,38 @@
# Security Scan Area: protocols
## Target
Area: protocols
This area covers the smaller externally controlled protocol parsers and state machines other than HTTP, WebSocket, and TLS.
Primary files:
- `src/modbus.c`
- `src/rpc.c`
- `src/sntp.c`
- `src/mqtt.c`
- `src/dns.c`
Perform a focused security review centered on these files and this security surface.
Supporting code elsewhere under `src/` may be inspected when necessary to establish reachability, data flow, state, validation, mitigation, or impact, but do not inspect or report code outside `src/` or independently review unrelated areas.
## Area-Specific Security Guidance
**MQTT Parser and Broker/Client State Machine Vulnerabilities:**
- Review MQTT fixed header parsing, remaining-length decoding, variable-length integer loops, packet identifier handling, QoS transitions, topic extraction, property parsing, and MQTT 3.x vs MQTT 5 differences.
- Look for malformed MQTT packets that can trigger out-of-bounds reads/writes, stack buffer overflow, heap corruption, assertion/crash, or parser desynchronization.
- Specifically check SUBSCRIBE and UNSUBSCRIBE handling for unbounded numbers of topics, malformed topic length fields, missing QoS bytes, invalid QoS values, and writes to fixed-size response arrays.
- Verify that PUBLISH handling enforces topic length, packet length, QoS rules, retained flag handling, packet identifier requirements, and message bounds before accessing fields.
- Check protocol handlers for authorization bypass, cross-topic data exposure, wildcard topic mishandling, `$SYS` or reserved topic confusion, and unintended publish/subscribe access.
- Review MQTT reconnect/resubscribe logic for stale connection state, packet identifier reuse bugs, and memory lifetime problems after close/reconnect.
- Treat unauthenticated single-packet broker crash, client crash from malicious broker response, or memory corruption during MQTT parsing as reportable.
**DNS and mDNS Parsing and Name Resolution:**
- Review DNS and mDNS message parsing for label length validation, compression pointer loops, pointer bounds, recursive expansion limits, integer overflow, malformed resource records, and out-of-bounds reads.
- Check that DNS answers are matched to outstanding requests by transaction ID, question name/type/class, and source expectations where applicable.
- Look for cache poisoning, response spoofing, or name confusion if DNS response validation is weak in client or resolver code.
- Verify that DNS failures, timeouts, and retries cannot leave dangling active request state or corrupt connection state.
**Modbus, RPC, and SNTP:**
- Review externally controlled lengths, framing, message parsing, and protocol state for malformed peer input that can cause parser desynchronization, state corruption, or a remotely triggerable crash or wedge.
+43
View File
@@ -0,0 +1,43 @@
# Security Scan Area: tls
## Target
Area: tls
This area covers the complete Mongoose TLS implementation represented by the primary files.
Primary files:
- `src/tls_aes128.c`
- `src/tls_builtin.c`
- `src/tls_chacha20.c`
- `src/tls_dummy.c`
- `src/tls_mbed.c`
- `src/tls_openssl.c`
- `src/tls_rsa.c`
- `src/tls_uecc.c`
- `src/tls_x25519.c`
Perform a focused security review centered on these files and this security surface.
Supporting code elsewhere under `src/` may be inspected when necessary to establish reachability, data flow, state, validation, mitigation, or impact, but do not inspect or report code outside `src/` or independently review unrelated areas.
Connection, core, network, and helper code under `src/` may be inspected when needed to understand the complete TLS execution path.
## Area-Specific Security Guidance
### Mandatory High-Risk Coverage: Built-In TLS
The built-in TLS implementation (`MG_TLS_BUILTIN`) is a mandatory high-risk review target. Perform a substantial security analysis of all security-relevant built-in TLS code, including parsing, cryptographic processing, authentication and certificate validation, length and buffer handling, protocol state, cross-call behavior, and failure paths.
Trace attacker-controlled TLS peer input through normal client and server execution, including traffic received before peer authentication is complete. Report any credible memory-safety, authentication, confidentiality, integrity, state-corruption, or remotely triggerable availability vulnerability.
**TLS, Certificate, Hostname Verification, and Cryptographic Validation:**
- Review all TLS backends, including built-in TLS, mbedTLS, OpenSSL, WolfSSL, and custom TLS integration, for equivalent security behavior where possible.
- Flag certificate verification bypasses, accidental `skip_verification` use in security-sensitive defaults, missing hostname verification when `name`/SNI is available, or inconsistent validation across TLS backends.
- For hostname verification, check wildcard handling carefully. A wildcard certificate identity must not match multiple DNS labels. For example, `*.example.com` must not match `a.b.example.com`.
- Verify that wildcard matching is applied only to certificate presented identifiers, not to arbitrary reference identifiers or general DNS glob matching.
- Review `mg_match` or other glob/matching helpers if used for certificate identity checks, authorization checks, filesystem paths, routing, or topic matching. Flag cases where glob semantics are broader than the security policy requires.
- Check certificate parsing for DER/PEM boundary errors, length overflows, malformed ASN.1, extension parsing mistakes, name constraints, SAN vs CN precedence, embedded NUL, and invalid string type handling.
- Review TLS record parsing, handshake transcript handling, key schedule, random generation, ECC/X25519 operations, AES-GCM nonce/tag handling, and error handling for memory safety and authentication failures.
- Flag any behavior that silently falls back from verified TLS to unverified TLS or plaintext.
- Check that TLS receive/send error paths cannot corrupt connection state, reuse freed buffers, or continue processing unauthenticated plaintext as encrypted data.
+61
View File
@@ -0,0 +1,61 @@
# Security Scan Area: web_http_auth
## Target
Area: web_http_auth
This area covers HTTP parsing and framing, HTTP-facing filesystem behavior, SSI, WebSocket, authentication/token/dashboard code, and HTTP-based OTA behavior.
Primary files:
- `src/http.c`
- `src/ssi.c`
- `src/ws.c`
- `src/jwt.c`
- `src/dash.c`
- `src/ota.c`
Perform a focused security review centered on these files and this security surface.
Supporting code elsewhere under `src/` may be inspected when necessary to establish reachability, data flow, state, validation, mitigation, or impact, but do not inspect or report code outside `src/` or independently review unrelated areas.
For OTA behavior, follow operations under `src/` into TLS, flash, filesystem, JSON, URL, or other support code when necessary, while keeping the review centered on the `web_http_auth` surface.
## Area-Specific Security Guidance
**HTTP Parser, Request Smuggling, and Message Framing:**
- Verify that HTTP requests or responses containing both `Content-Length` and `Transfer-Encoding` are rejected or handled in a way that cannot cause front-end/back-end desynchronization. Treat CL+TE coexistence as security-relevant request smuggling risk.
- Verify that duplicate `Content-Length`, duplicate `Transfer-Encoding`, conflicting transfer codings, malformed chunk sizes, chunk extensions, premature EOF, and body length mismatches cannot cause inconsistent body parsing.
- Check whether HTTP/1.0, HTTP/1.1, and malformed protocol versions alter CL/TE handling unsafely. A message with both `Content-Length` and `Transfer-Encoding` should not be accepted merely because of version differences.
- Look for parsing paths where headers are normalized differently from later lookup paths, including case folding, whitespace around colon, obs-fold/line folding, embedded NUL, duplicate headers, or partial header names.
- Review URL decoding, query parsing, multipart parsing, upload handling, range handling, and chunked responses for bounds mistakes, truncation, or inconsistent decoded-vs-raw path decisions.
- Check static file serving and SSI handling for path traversal through percent-encoding, double decoding, backslashes on Windows, mixed separators, dot-dot segments, absolute paths, drive letters, symlinks, embedded NUL, or alias/root_dir confusion.
- Review HTTP authentication helper paths for parsing ambiguities in Basic auth, header extraction, credential truncation, empty username/password acceptance, or incorrect comparison behavior.
- Check that HTTP upgrade to WebSocket validates method, headers, key, version, and connection semantics consistently before switching protocol state.
**WebSocket Framing and Upgrade Vulnerabilities:**
- Review WebSocket frame parsing for extended payload length handling, 16-bit/64-bit length conversion, masking rules, fragmentation, continuation frames, control-frame length limits, close/ping/pong handling, and compression-related assumptions if present.
- Check that client-to-server frames require masking and that malformed mask/payload boundaries cannot read or write out of bounds.
- Look for state-machine confusion after HTTP upgrade, especially where partial frames, fragmented messages, or mixed HTTP/WebSocket bytes can cause parser desynchronization.
- Check whether large or malformed frame lengths can cause integer overflow, memory corruption, uncontrolled allocation, connection lockup, or single-connection/device crash.
- Verify that WebSocket message APIs preserve attacker-controlled binary data length correctly and do not pass non-null-terminated payloads to C-string functions.
**HTTP-Facing Filesystem Serving, Uploads, and SSI:**
- Review `mg_http_serve_dir`, `mg_http_serve_file`, upload APIs, and SSI expansion for traversal, unauthorized file read/write, overwrite, truncation, or unexpected executable/include behavior.
- Look for inconsistent handling of decoded and undecoded paths, double decoding, query string leakage into path, trailing slash confusion, NUL byte truncation, and case sensitivity differences.
- Review multipart upload parsing for boundary confusion, filename traversal, field length overflow, partial-write behavior, and max-size enforcement.
- Check SSI include handling for directory escape, recursive include loops, unintended disclosure of local files, and parsing of attacker-controlled SSI directives.
- Verify that range requests and static response headers cannot cause out-of-bounds reads or disclose memory.
**OTA/Firmware Update and Device Dashboard Security:**
- Review firmware update endpoints and code paths for missing authentication, authorization bypass, path confusion, arbitrary write, unsafe flash offset/length handling, and failure to verify firmware authenticity where the code claims secure update behavior.
- Check update chunk parsing for integer overflow, flash boundary crossing, rollback/downgrade exposure, incomplete image bootability, and crash/power-loss unsafe state transitions.
- Flag any code path where unauthenticated network input can write firmware, configuration, filesystem contents, flash pages, bootloader state, or device identity material.
- Review device dashboard code for security-sensitive default routes, credentials, upload handlers, debug endpoints, config mutation endpoints, and CORS/header behavior.
**Authentication, Authorization, and Access-Control Patterns in Mongoose Integrations:**
- Review helper APIs and code paths that implement HTTP auth, cookies, sessions, tokens, device dashboards, REST APIs, upload endpoints, and firmware update endpoints.
- Flag authentication bypasses caused by prefix route matching, glob matching, decoded-vs-raw URI confusion, case normalization mistakes, missing boundary checks, or default-open sensitive endpoints.
- Check for authorization decisions made in client-side JavaScript only. Client-side checks are not security boundaries.
- Review CORS and origin checks only when they protect credentialed sensitive operations and the exploit path is concrete.
- Look for credential comparison bugs, truncation of usernames/passwords, accepting empty credentials, parsing only part of an Authorization header, or treating malformed credentials as anonymous-but-authorized.
- Flag sensitive data exposure when secrets, credentials, tokens, private keys, firmware signing material, PII, or device identity values can be read by unauthorized remote clients or logged from attacker-triggered paths.
@@ -5,7 +5,7 @@
3. The exploit depends on NULL, dangling, fake, or corrupted pointers/objects supplied by a local caller, including invalid `mg_connection`, `mg_mgr`, `mg_str`, iobufs, or callback data. 3. The exploit depends on NULL, dangling, fake, or corrupted pointers/objects supplied by a local caller, including invalid `mg_connection`, `mg_mgr`, `mg_str`, iobufs, or callback data.
4. The exploit requires manually mutating internal structs, flags, parser offsets, descriptor indexes, connection state, TLS state, MQTT/WebSocket state, or filesystem state into impossible combinations. 4. The exploit requires manually mutating internal structs, flags, parser offsets, descriptor indexes, connection state, TLS state, MQTT/WebSocket state, or filesystem state into impossible combinations.
5. The bug exists only in tests, fuzz harnesses, benchmarks, synthetic unit tests, or debug-only code, with no shared production logic. 5. The bug exists only in tests, fuzz harnesses, benchmarks, synthetic unit tests, or debug-only code, with no shared production logic.
6. The issue is only in a tutorial/demo/example with no realistic production relevance or sensitive operation. 6. The affected file is outside `src/`.
7. The attacker must edit source code, build flags, compile-time macros, TLS backend choices, filesystem roots, or local device configuration. 7. The attacker must edit source code, build flags, compile-time macros, TLS backend choices, filesystem roots, or local device configuration.
8. The attacker must directly write hardware registers, MMIO, DMA descriptors, descriptor ownership bits, IRQ state, cache registers, or peripheral configuration. 8. The attacker must directly write hardware registers, MMIO, DMA descriptors, descriptor ownership bits, IRQ state, cache registers, or peripheral configuration.
9. Generic DoS from high traffic volume, many valid connections, slow clients, missing quotas, missing rate limits, or ordinary resource saturation. 9. Generic DoS from high traffic volume, many valid connections, slow clients, missing quotas, missing rate limits, or ordinary resource saturation.
@@ -14,6 +14,8 @@
12. Generic crypto advice with no concrete impact on authentication, confidentiality, integrity, downgrade resistance, hostname verification, certificate validation, key handling, nonces/tags, or plaintext fallback. 12. Generic crypto advice with no concrete impact on authentication, confidentiality, integrity, downgrade resistance, hostname verification, certificate validation, key handling, nonces/tags, or plaintext fallback.
13. Speculative protocol claims without a specific malformed input shape, reachable parser path, and concrete security impact. 13. Speculative protocol claims without a specific malformed input shape, reachable parser path, and concrete security impact.
14. Pure application policy gaps: no default auth, no CSRF, no user access model, no account lockout, no audit logs, or no password policy. 14. Pure application policy gaps: no default auth, no CSRF, no user access model, no account lockout, no audit logs, or no password policy.
15. OTA findings based only on allowing plain HTTP, lacking firmware hash/signature verification, or permitting update URLs to change scheme/host when this is documented/configurable behavior and no existing security check is bypassed.
16. Filter TCP findings that rely primarily on predictability or insufficient entropy in protocol sequence-state generation, including attacks based on guessing or reconstructing connection state without observing the traffic.
# SIGNAL QUALITY CRITERIA # SIGNAL QUALITY CRITERIA
**For remaining findings, assess:** **For remaining findings, assess:**
@@ -25,7 +27,7 @@
6. Does the report provide exact files, functions, line numbers, data flow, bounds reasoning, state reasoning, or a minimal malformed input? 6. Does the report provide exact files, functions, line numbers, data flow, bounds reasoning, state reasoning, or a minimal malformed input?
7. Is the malformed input shape clear, such as CL+TE HTTP, oversized MQTT SUBSCRIBE, malformed WebSocket length, DNS compression loop, wildcard TLS certificate, invalid packet header length, or encoded path traversal? 7. Is the malformed input shape clear, such as CL+TE HTTP, oversized MQTT SUBSCRIBE, malformed WebSocket length, DNS compression loop, wildcard TLS certificate, invalid packet header length, or encoded path traversal?
8. Is the severity justified by reachability and impact? 8. Is the severity justified by reachability and impact?
9. Is this a library/helper/default/example-pattern vulnerability rather than only arbitrary application misuse? 9. Is this a library/helper/default vulnerability rather than only arbitrary application misuse?
10. Is the fix specific enough to act on: reject CL+TE, bound arrays, validate DNS pointers, enforce one-label wildcard matching, check packet lengths, canonicalize paths, or bound RX loops? 10. Is the fix specific enough to act on: reject CL+TE, bound arrays, validate DNS pointers, enforce one-label wildcard matching, check packet lengths, canonicalize paths, or bound RX loops?
11. Is a DoS finding caused by a concrete implementation flaw rather than volume, slow clients, missing quotas, or capacity limits? 11. Is a DoS finding caused by a concrete implementation flaw rather than volume, slow clients, missing quotas, or capacity limits?
12. In embedded context, can a small malformed input reliably crash, wedge, corrupt, or permanently disrupt a device/process? 12. In embedded context, can a small malformed input reliably crash, wedge, corrupt, or permanently disrupt a device/process?
@@ -42,26 +44,26 @@
8. Keep unsafe `mg_match` or glob use at security boundaries: certificate identity, route authorization, filesystem policy, MQTT topic authorization, or access control. 8. Keep unsafe `mg_match` or glob use at security boundaries: certificate identity, route authorization, filesystem policy, MQTT topic authorization, or access control.
9. Keep DNS/mDNS label, compression pointer, pointer loop, RR length, transaction matching, spoofing, OOB, or infinite-loop issues with external reachability. 9. Keep DNS/mDNS label, compression pointer, pointer loop, RR length, transaction matching, spoofing, OOB, or infinite-loop issues with external reachability.
10. Keep filesystem traversal through exposed serving/upload/SSI paths, including encoding, double decoding, backslashes, dot-dot, absolute paths, drive letters, symlinks, NULs, alias/root confusion, and upload filename traversal. 10. Keep filesystem traversal through exposed serving/upload/SSI paths, including encoding, double decoding, backslashes, dot-dot, absolute paths, drive letters, symlinks, NULs, alias/root confusion, and upload filename traversal.
11. Keep OTA/update issues allowing unauthenticated or improperly validated writes to firmware, flash, config, filesystem, boot state, device identity, or update metadata. 11. Keep embedded packet parser bugs in Ethernet, ARP, IP, IPv6, ICMP, UDP, TCP, DHCP, SNTP, or drivers when malformed traffic causes OOB, overflow, descriptor corruption, queue corruption, state corruption, or crash.
12. Keep embedded packet parser bugs in Ethernet, ARP, IP, IPv6, ICMP, UDP, TCP, DHCP, SNTP, or drivers when malformed traffic causes OOB, overflow, descriptor corruption, queue corruption, state corruption, or crash. 12. Keep IRQ, polling, RX descriptor, and packet-loop livelock/starvation/wedge bugs caused by received traffic.
13. Keep IRQ, polling, RX descriptor, and packet-loop livelock/starvation/wedge bugs caused by received traffic. 13. Keep event-lifecycle bugs reachable from external input: UAF, stale pointer, double close, callback lifetime error, iobuf mutation after close, protocol transition confusion, timer/wakeup after free.
14. Keep event-lifecycle bugs reachable from external input: UAF, stale pointer, double close, callback lifetime error, iobuf mutation after close, protocol transition confusion, timer/wakeup after free. 14. Keep externally reachable `mg_str` null-termination mistakes causing OOB read, data leak, auth bypass, parser confusion, or crash.
15. Keep externally reachable `mg_str` null-termination mistakes causing OOB read, data leak, auth bypass, parser confusion, or crash. 15. Keep integer conversion bugs where external lengths affect allocation, copying, parsing, or bounds checks.
16. Keep integer conversion bugs where external lengths affect allocation, copying, parsing, or bounds checks. 16. Filter parser/helper reports based only on a local caller passing invalid pointers, fake lengths, fake structs, or corrupted objects.
17. Filter parser/helper reports based only on a local caller passing invalid pointers, fake lengths, fake structs, or corrupted objects. 17. Filter reports requiring direct mutation of connection internals such as protocol flags, closing flags, handler pointers, iobuf fields, callback pointers, or user data.
18. Filter reports requiring direct mutation of connection internals such as protocol flags, closing flags, handler pointers, iobuf fields, callback pointers, or user data. 18. Filter reports requiring attacker control of `MG_ENABLE_*`, TLS backend macros, filesystem backend macros, compiler flags, debug flags, sanitizer settings, or platform defines.
19. Filter reports requiring attacker control of `MG_ENABLE_*`, TLS backend macros, filesystem backend macros, compiler flags, debug flags, sanitizer settings, or platform defines. 19. Filter direct hardware-access reports requiring writes to registers, DMA descriptors, MMIO, IRQ state, cache controls, or peripheral configuration.
20. Filter direct hardware-access reports requiring writes to registers, DMA descriptors, MMIO, IRQ state, cache controls, or peripheral configuration. 20. Filter generic “Mongoose lacks authentication” reports unless a helper, route pattern, auth parser, MQTT broker behavior, dashboard, upload handler, or OTA endpoint creates a concrete issue.
21. Filter generic “Mongoose lacks authentication” reports unless a helper, example, route pattern, auth parser, MQTT broker behavior, dashboard, upload handler, or OTA endpoint creates a concrete issue. 21. Filter generic rate-limit/quota/connection-cap/upload-cap findings unless a specific bug lets a small malformed input trigger crash, corruption, infinite loop, or disproportionate exhaustion.
22. Filter generic rate-limit/quota/connection-cap/upload-cap findings unless a specific bug lets a small malformed input trigger crash, corruption, infinite loop, or disproportionate exhaustion. 22. Filter purely theoretical UB, portability, unaligned-access, or signed-overflow reports with no supported-platform impact and no realistic external trigger.
23. Filter purely theoretical UB, portability, unaligned-access, or signed-overflow reports with no supported-platform impact and no realistic external trigger. 23. Filter RFC non-compliance without concrete smuggling, bypass, memory corruption, data exposure, state corruption, or crash.
24. Filter RFC non-compliance without concrete smuggling, bypass, memory corruption, data exposure, state corruption, or crash. 24. Keep security-relevant production library code under `src/` for dashboards, uploads, MQTT, firmware updates, authentication, TLS, and filesystem serving.
25. Keep security-relevant production-like examples: dashboards, upload servers, MQTT brokers, firmware update flows, auth examples, TLS examples, and filesystem-serving examples. 25. Keep client-side findings caused by malicious servers, brokers, DNS responders, TLS peers, WebSocket peers, or HTTP peers.
26. Keep client-side findings caused by malicious servers, brokers, DNS responders, TLS peers, WebSocket peers, or HTTP peers. 26. Keep callback-related findings only when Mongoose invokes the callback normally and then unsafely continues using freed or mutated state.
27. Keep callback-related findings only when Mongoose invokes the callback normally and then unsafely continues using freed or mutated state. 27. Keep availability findings when one malformed packet, request, certificate, DNS response, MQTT message, WebSocket frame, or small sequence can reliably crash, wedge, corrupt, or disrupt a Mongoose target.
28. Keep availability findings when one malformed packet, request, certificate, DNS response, MQTT message, WebSocket frame, or small sequence can reliably crash, wedge, corrupt, or disrupt a Mongoose target. 28. Keep custom backend findings only when the issue is in Mongoose’s backend interface, default backend, documented backend contract, or common backend implementation.
29. Keep custom backend findings only when the issue is in Mongoose’s backend interface, default backend, documented backend contract, or common backend implementation. 29. When uncertain, keep findings with clear external input, reachable code path, and concrete impact in Mongoose library/protocol/driver code; filter internal-access, impossible-state, local-misuse, generic-hardening, and speculative reports.
30. When uncertain, keep findings with clear external input, reachable code path, and concrete impact in Mongoose library/protocol/driver code; filter internal-access, impossible-state, local-misuse, generic-hardening, and speculative reports. 30. For dashboard reports, do not infer unauthenticated access from a route-specific check alone; account for the broader authentication flow. Keep concrete reachable bugs such as path traversal, unsafe decoded paths, or file read/write/delete issues.
31. For dashboard reports, do not infer unauthenticated access from a route-specific check alone; account for the broader authentication flow. Keep concrete reachable bugs such as path traversal, unsafe decoded paths, or file read/write/delete issues. 31. Filter reports regarding DNS transaction ID predictability or lack of strict question-name matching in DNS response processing, as these are acknowledged design limitations of the current implementation.
32. Filter reports regarding DNS transaction ID predictability or lack of strict question-name matching in DNS response processing, as these are acknowledged design limitations of the current implementation. 32. Filter reports that unauthenticated SNTP time affects TLS certificate validity when Mongoose only performs the SNTP sync if mg_boot_timestamp_ms has not already been initialized.
33. Filter reports that unauthenticated SNTP time affects TLS certificate validity when Mongoose only performs the SNTP sync if mg_boot_timestamp_ms has not already been initialized. 33. Filter TCP ISN predictability, weak sequence-number generation, RFC 6528 non-compliance, blind/off-path handshake spoofing, forged RST, and sequence-number-guessing reports in the built-in TCP/IP stack.
+20 -9
View File
@@ -1,24 +1,35 @@
Review the complete PR diff. This contains all code changes in the PR. Review only changes under `src/` in the complete PR diff. Ignore all other changes in the PR.
# OBJECTIVE # OBJECTIVE
Perform a security-focused code review to identify HIGH-CONFIDENCE security vulnerabilities that could have real exploitation potential. Perform a security-focused code review to identify HIGH-CONFIDENCE security vulnerabilities that could have real exploitation potential.
This is not a general code review - focus ONLY on security implications newly added by this PR. Do not comment on existing security concerns. This is not a general code review - focus ONLY on security implications newly added by this PR under `src/`. Do not analyze, count, or report changes outside `src/`. Do not comment on existing security concerns.
For this task, you will rely on two additional files, `resources/specs/claude-security-scan-instructions.md` detailing security scan instructions specific to the repository and `resources/specs/claude-false-positive-filtering.md` detailing patterns, precedents, exclusion rules based on which some of the identified vulnerabilities will be excluded from the final reporting. The instructions, rules and criterias found in those two files are MANDATORY TO BE HONOURED and your security audit will have to take all of them into account. For this task, you will rely on the specified shared security-review sections in `resources/specs/claude-security-scan.md`, the specified security-guidance sections in the six `resources/specs/areas/*.md` prompts listed below, and `resources/specs/claude-false-positive-filtering.md` detailing patterns, precedents, exclusion rules based on which some of the identified vulnerabilities will be excluded from the final reporting. The instructions, rules and criterias found in those referenced sections and the filtering file are MANDATORY TO BE HONOURED and your security audit will have to take all of them into account.
YOU MUST READ AND ANALYZE BOTH OF THESE FILES BEFORE COMMENCING THE SECURITY AUDIT. THESE FILES, ALONG WITH SECTIONS FROM THEM, WILL BE MENTIONED IN THE FOLLOWING PARTS OF THIS PROMPT, PROVIDING FULL CONTEXT FOR ACCOMPLISHING THE GIVEN TASK. YOU MUST READ AND ANALYZE THE SPECIFIED SECTIONS AND THE FILTERING FILE BEFORE COMMENCING THE SECURITY AUDIT. THESE SOURCES WILL BE MENTIONED IN THE FOLLOWING PARTS OF THIS PROMPT, PROVIDING FULL CONTEXT FOR ACCOMPLISHING THE GIVEN TASK.
# CRITICAL INSTRUCTIONS # CRITICAL INSTRUCTIONS
1. MINIMIZE FALSE POSITIVES: Only flag issues where you're >80% confident of actual exploitability 1. MINIMIZE FALSE POSITIVES: Only flag issues where you're >80% confident of actual exploitability
2. AVOID NOISE: Skip theoretical issues, style concerns, or low-impact findings 2. AVOID NOISE: Skip theoretical issues, style concerns, or low-impact findings
3. UNDERSTANDING CONTEXT AND SCOPE OF REVIEW: In the security scan instructions file `resources/specs/claude-security-scan-instructions.md`, read the `External Attacker Threat Model` and `Mongoose-Specific Security Review Scope` subsections found in the `SECURITY SCAN CONTEXT` main section for a full understanding of the scanning model, code review context and the perspective from which the scanning is going to take place. 3. UNDERSTANDING CONTEXT AND SCOPE OF REVIEW: In `resources/specs/claude-security-scan.md`, read only the `External Attacker Threat Model` and `Mongoose-Specific Security Review Scope` subsections found in the `SECURITY SCAN CONTEXT` main section for a full understanding of the scanning model, code review context and the perspective from which the scanning is going to take place. Do not apply unrelated full-scan instructions from that file. In particular, do not inherit assigned-area scan scope, full-scan JSON output requirements, full-scan `review_completed` semantics, instructions to scan long-standing code independently of the PR, or area-selection workflow behavior. This PR prompt's scope, methodology, validation, and output rules remain authoritative.
4. TREAT EXCLUSIONS AS MANDATORY. The exclusions section below is mandatory. If a potential finding falls under an exclusion, do not report it. If a finding partially overlaps an exclusion, only report it if there is a concrete, non-excluded security impact. 4. TREAT EXCLUSIONS AS MANDATORY. The exclusions section below is mandatory. If a potential finding falls under an exclusion, do not report it. If a finding partially overlaps an exclusion, only report it if there is a concrete, non-excluded security impact.
# SECURITY CATEGORIES TO EXAMINE # SECURITY CATEGORIES TO EXAMINE
In the security scan instructions file `resources/specs/claude-security-scan-instructions.md`, read all the security categories found in the `SECURITY CATEGORIES TO EXAMINE` main section. THESE ARE THE SECURITY CATEGORIES WHICH NEED TO BE EXAMINED. Read the general/common security categories in the `GENERAL SECURITY CATEGORIES TO EXAMINE` section of `resources/specs/claude-security-scan.md`.
Also read the `Area-Specific Security Guidance` section from every area prompt:
* `resources/specs/areas/common_misc.md`
* `resources/specs/areas/mongoose_core.md`
* `resources/specs/areas/web_http_auth.md`
* `resources/specs/areas/tls.md`
* `resources/specs/areas/net_builtin.md`
* `resources/specs/areas/protocols.md`
Use only the security guidance from the area prompts and ignore their full-scan-specific `Target` sections. Consider the complete set of common and area-specific security categories for every file the PR modifies under `src/`. Do not choose one area or make this PR review area-scoped. Ignore all other changed paths and report only security vulnerabilities introduced by the PR under `src/`.
Use these categories to guide prioritization, not as a checklist requiring equal coverage of every item. Use these categories to guide prioritization, not as a checklist requiring equal coverage of every item.
@@ -37,7 +48,7 @@ Phase 2 - Comparative Analysis:
- Flag code that introduces new attack surfaces - Flag code that introduces new attack surfaces
Phase 3 - Vulnerability Assessment: Phase 3 - Vulnerability Assessment:
- Examine each modified file for security implications - Examine each modified file under `src/` for security implications
- Trace data flow from user inputs to sensitive operations - Trace data flow from user inputs to sensitive operations
- Look for privilege boundaries being crossed unsafely - Look for privilege boundaries being crossed unsafely
- Identify injection points and unsafe deserialization - Identify injection points and unsafe deserialization
@@ -110,6 +121,6 @@ Better to miss some theoretical issues than flood the report with false positive
Read the `HARD EXCLUSIONS` section found in the `resources/specs/claude-false-positive-filtering.md` filtering file, for each possbile vulnerability found, check if it matches any items from the patterns listed in that section and if that is the case, exclude that vulnerability from the report. DO NOT REPORT IT if a match is found. Also, take into account the project-specific filtering and reporting precedents found in the `PRECEDENTS` section of the filtering file. Read the `HARD EXCLUSIONS` section found in the `resources/specs/claude-false-positive-filtering.md` filtering file, for each possbile vulnerability found, check if it matches any items from the patterns listed in that section and if that is the case, exclude that vulnerability from the report. DO NOT REPORT IT if a match is found. Also, take into account the project-specific filtering and reporting precedents found in the `PRECEDENTS` section of the filtering file.
Begin your analysis now. Use the repository exploration tools to understand the codebase context, then analyze the PR changes for security implications. Begin your analysis now. Use the repository exploration tools to understand the `src/` codebase context, then analyze only the PR changes under `src/` for security implications.
Your final action must be posting the PR comment. You should not reply again after posting the PR comment. Your final action must be posting the PR comment. You should not reply again after posting the PR comment.
@@ -1,130 +0,0 @@
# SECURITY SCAN CONTEXT
**External Attacker Threat Model:**
- Analyze vulnerabilities from the perspective of an external attacker, not from the perspective of a developer, local user, debugger, test harness, or code running inside the process.
- The attacker can provide bytes through real external interfaces exposed by Mongoose or a Mongoose-based application: TCP, UDP, HTTP, WebSocket, MQTT, DNS, mDNS, TLS handshakes/certificates, Ethernet/IP/TCP/UDP packets, uploaded files, request paths, query strings, headers, message bodies, malicious server responses, and other protocol inputs.
- The attacker cannot directly call internal C functions, pass arbitrary invalid pointers, mutate internal structs, invoke callbacks manually, edit compile-time macros, alter build flags, write to hardware registers, control DMA descriptors directly, attach a debugger, or run code inside the target process.
- For a finding to be valid, show how attacker-controlled external input reaches the vulnerable code path through normal library/application execution.
- Do not report findings that require impossible internal states unless the finding also explains how an external input can cause that state.
- Do not report “API misuse” as a library vulnerability unless the API contract makes the insecure use likely, undocumented, or unavoidable.
- For embedded networking and driver code, assume the attacker can send malformed network traffic to the device, but cannot directly manipulate hardware registers, memory-mapped I/O, DMA rings, or interrupt state except as a consequence of packets processed by the driver.
**Mongoose-Specific Security Review Scope:**
- Treat this repository as a security-sensitive embedded C/C++ networking stack. Mongoose processes attacker-controlled bytes from TCP, UDP, HTTP, WebSocket, MQTT, DNS, mDNS, TLS, filesystem upload/download paths, and built-in TCP/IP drivers.
- In this repository, remotely triggerable denial of service is security-relevant when it is caused by a concrete implementation flaw such as memory corruption, parser abort, assertion failure, stack exhaustion, infinite loop, unbounded recursion, IRQ livelock, connection state corruption, descriptor-ring corruption, or a single unauthenticated packet causing process/device crash.
- Prioritize HIGH and MEDIUM severity findings. Include LOW only when unusually concrete, externally relevant, and useful. Do not report vague robustness concerns. Every finding must identify the attacker-controlled input, affected parser/state machine/buffer, concrete impact, and the exact code path.
- Prefer findings with a plausible packet/request/message shape. For network protocol issues, describe the relevant malformed HTTP request, MQTT packet, WebSocket frame, DNS message, TLS handshake/certificate input, Ethernet/IP/TCP/UDP packet, or filesystem request.
- Because this is C/C++, memory safety vulnerabilities are in scope. Buffer overflows, stack overflows, heap overflows, out-of-bounds reads/writes, use-after-free, double free, integer overflow leading to memory corruption, invalid pointer lifetime, and unsafe length conversions must be reviewed carefully.
- Do not dismiss issues merely because they are “only DoS” if the issue is remotely triggerable against an embedded server, broker, device dashboard, firmware-update endpoint, or network-facing parser.
# SECURITY CATEGORIES TO EXAMINE
## Mandatory High-Risk Coverage: Built-In TLS
The built-in TLS implementation (`MG_TLS_BUILTIN`) is a mandatory high-risk review target. Perform a substantial security analysis of all security-relevant built-in TLS code, including parsing, cryptographic processing, authentication and certificate validation, length and buffer handling, protocol state, cross-call behavior, and failure paths.
Trace attacker-controlled TLS peer input through normal client and server execution, including traffic received before peer authentication is complete. Report any credible memory-safety, authentication, confidentiality, integrity, state-corruption, or remotely triggerable availability vulnerability.
Do not set `review_completed` to `true` unless the built-in TLS implementation has been meaningfully inspected as a whole.
**C Memory Safety and Length-Handling Vulnerabilities:**
- Look for writes to fixed-size stack or heap buffers where the loop bound is derived from attacker-controlled protocol fields, including topic counts, header counts, chunk counts, multipart parts, DNS labels, WebSocket fragments, TCP/IP options, or filesystem path components.
- Check all conversions between `size_t`, `int`, `long`, `uint16_t`, `uint32_t`, and signed protocol lengths. Flag integer truncation, wraparound, negative-to-large conversion, or off-by-one behavior that can affect allocation, parsing, copying, or bounds checks.
- Review any `memcpy`, `memmove`, `memcmp`, `snprintf`, `mg_snprintf`, `mg_xprintf`, iobuf append/delete/resize operation, and manual pointer increment where source length can originate from network input.
- Treat `struct mg_str` values as non-null-terminated unless the code proves otherwise. Flag use of `strlen`, `%s`, `strcmp`, `strstr`, or C-string APIs on attacker-controlled `mg_str.buf` unless length and termination are guaranteed.
- Check for parser code that advances pointers without proving that enough bytes remain. Flag any read of fixed-size fields, variable-length integers, DNS labels, TLS vectors, MQTT remaining length, WebSocket extended lengths, or HTTP chunks before bounds are validated.
- Look for inconsistent ownership and lifetime of buffers referenced by `mg_str`, connection receive/send iobufs, TLS buffers, filesystem buffers, and event callback data.
- Check for reentrancy hazards in event callbacks where user handlers may close a connection, mutate iobufs, start TLS, change protocol handlers, or free state while protocol code continues using stale pointers.
**HTTP Parser, Request Smuggling, and Message Framing:**
- Verify that HTTP requests or responses containing both `Content-Length` and `Transfer-Encoding` are rejected or handled in a way that cannot cause front-end/back-end desynchronization. Treat CL+TE coexistence as security-relevant request smuggling risk.
- Verify that duplicate `Content-Length`, duplicate `Transfer-Encoding`, conflicting transfer codings, malformed chunk sizes, chunk extensions, premature EOF, and body length mismatches cannot cause inconsistent body parsing.
- Check whether HTTP/1.0, HTTP/1.1, and malformed protocol versions alter CL/TE handling unsafely. A message with both `Content-Length` and `Transfer-Encoding` should not be accepted merely because of version differences.
- Look for parsing paths where headers are normalized differently from later lookup paths, including case folding, whitespace around colon, obs-fold/line folding, embedded NUL, duplicate headers, or partial header names.
- Review URL decoding, query parsing, multipart parsing, upload handling, range handling, and chunked responses for bounds mistakes, truncation, or inconsistent decoded-vs-raw path decisions.
- Check static file serving and SSI handling for path traversal through percent-encoding, double decoding, backslashes on Windows, mixed separators, dot-dot segments, absolute paths, drive letters, symlinks, embedded NUL, or alias/root_dir confusion.
- Review HTTP authentication helper paths for parsing ambiguities in Basic auth, header extraction, credential truncation, empty username/password acceptance, or incorrect comparison behavior.
- Check that HTTP upgrade to WebSocket validates method, headers, key, version, and connection semantics consistently before switching protocol state.
**MQTT Parser and Broker/Client State Machine Vulnerabilities:**
- Review MQTT fixed header parsing, remaining-length decoding, variable-length integer loops, packet identifier handling, QoS transitions, topic extraction, property parsing, and MQTT 3.x vs MQTT 5 differences.
- Look for malformed MQTT packets that can trigger out-of-bounds reads/writes, stack buffer overflow, heap corruption, assertion/crash, or parser desynchronization.
- Specifically check SUBSCRIBE and UNSUBSCRIBE handling for unbounded numbers of topics, malformed topic length fields, missing QoS bytes, invalid QoS values, and writes to fixed-size response arrays.
- Verify that PUBLISH handling enforces topic length, packet length, QoS rules, retained flag handling, packet identifier requirements, and message bounds before accessing fields.
- Check broker examples and protocol handlers for authorization bypass, cross-topic data exposure, wildcard topic mishandling, `$SYS` or reserved topic confusion, and unintended publish/subscribe access.
- Review MQTT reconnect/resubscribe logic for stale connection state, packet identifier reuse bugs, and memory lifetime problems after close/reconnect.
- Treat unauthenticated single-packet broker crash, client crash from malicious broker response, or memory corruption during MQTT parsing as reportable.
**WebSocket Framing and Upgrade Vulnerabilities:**
- Review WebSocket frame parsing for extended payload length handling, 16-bit/64-bit length conversion, masking rules, fragmentation, continuation frames, control-frame length limits, close/ping/pong handling, and compression-related assumptions if present.
- Check that client-to-server frames require masking and that malformed mask/payload boundaries cannot read or write out of bounds.
- Look for state-machine confusion after HTTP upgrade, especially where partial frames, fragmented messages, or mixed HTTP/WebSocket bytes can cause parser desynchronization.
- Check whether large or malformed frame lengths can cause integer overflow, memory corruption, uncontrolled allocation, connection lockup, or single-connection/device crash.
- Verify that WebSocket message APIs preserve attacker-controlled binary data length correctly and do not pass non-null-terminated payloads to C-string functions.
**TLS, Certificate, Hostname Verification, and Cryptographic Validation:**
- Review all TLS backends, including built-in TLS, mbedTLS, OpenSSL, WolfSSL, and custom TLS integration, for equivalent security behavior where possible.
- Flag certificate verification bypasses, accidental `skip_verification` use in security-sensitive examples or defaults, missing hostname verification when `name`/SNI is available, or inconsistent validation across TLS backends.
- For hostname verification, check wildcard handling carefully. A wildcard certificate identity must not match multiple DNS labels. For example, `*.example.com` must not match `a.b.example.com`.
- Verify that wildcard matching is applied only to certificate presented identifiers, not to arbitrary reference identifiers or general DNS glob matching.
- Review `mg_match` or other glob/matching helpers if used for certificate identity checks, authorization checks, filesystem paths, routing, or topic matching. Flag cases where glob semantics are broader than the security policy requires.
- Check certificate parsing for DER/PEM boundary errors, length overflows, malformed ASN.1, extension parsing mistakes, name constraints, SAN vs CN precedence, embedded NUL, and invalid string type handling.
- Review TLS record parsing, handshake transcript handling, key schedule, random generation, ECC/X25519 operations, AES-GCM nonce/tag handling, and error handling for memory safety and authentication failures.
- Flag any behavior that silently falls back from verified TLS to unverified TLS or plaintext.
- Check that TLS receive/send error paths cannot corrupt connection state, reuse freed buffers, or continue processing unauthenticated plaintext as encrypted data.
**DNS, mDNS, URL Parsing, and Name Resolution:**
- Review DNS and mDNS message parsing for label length validation, compression pointer loops, pointer bounds, recursive expansion limits, integer overflow, malformed resource records, and out-of-bounds reads.
- Check that DNS answers are matched to outstanding requests by transaction ID, question name/type/class, and source expectations where applicable.
- Look for cache poisoning, response spoofing, or name confusion if DNS response validation is weak in client or resolver code.
- Review URL parsing for scheme, host, port, IPv6 literal, userinfo, percent-encoding, embedded NUL, empty host, default port, and path/query boundary confusion.
- Check for SSRF-relevant behavior only when attacker input can control the destination host, protocol, or network boundary. Path-only control is not sufficient.
- Verify that DNS failures, timeouts, and retries cannot leave dangling active request state or corrupt connection state.
**Built-In TCP/IP Stack, Packet Parsing, and Embedded Network Drivers:**
- Review Ethernet, ARP, IP, IPv6, ICMP, UDP, TCP, DHCP, SNTP, and driver receive/transmit paths for packet-length validation before reading protocol headers.
- Check IP/TCP/UDP header length fields, options length, fragmentation/reassembly behavior, checksum handling, and payload offset calculations for underflow, overflow, or out-of-bounds access.
- Look for malformed packets that can corrupt descriptor rings, RX/TX buffers, queue state, ARP tables, connection state, or timers.
- Review interrupt handlers and bare-metal driver code for bounded processing loops. Flag IRQ paths where attacker-controlled traffic can cause livelock, starvation, descriptor ownership loss, or repeated processing of the same malformed frame.
- Check DMA descriptor ownership transitions, memory barriers, cache coherency handling, and buffer length fields for race-like bugs that can cause memory corruption or stale packet reuse.
- Review platform-specific drivers such as STM32, NXP, TI, Microchip, Renesas, Infineon, Wiznet, Cypress WiFi, cellular, and other built-in stack integrations for inconsistent bounds checks or endian conversions.
- Treat a remotely reachable malformed Ethernet/IP/TCP/UDP packet that crashes, wedges, or corrupts an embedded target as reportable.
**Filesystem Serving, Uploads, Packed Filesystems, and SSI:**
- Review `mg_http_serve_dir`, `mg_http_serve_file`, upload APIs, packed filesystem code, and SSI expansion for traversal, unauthorized file read/write, overwrite, truncation, or unexpected executable/include behavior.
- Check path canonicalization across POSIX, Windows, embedded filesystems, packed filesystems, and custom `mg_fs` implementations.
- Look for inconsistent handling of decoded and undecoded paths, double decoding, query string leakage into path, trailing slash confusion, NUL byte truncation, and case sensitivity differences.
- Review multipart upload parsing for boundary confusion, filename traversal, field length overflow, partial-write behavior, and max-size enforcement.
- Check SSI include handling for directory escape, recursive include loops, unintended disclosure of local files, and parsing of attacker-controlled SSI directives.
- Verify that range requests and static response headers cannot cause out-of-bounds reads or disclose memory.
**OTA/Firmware Update and Device Dashboard Security:**
- Review firmware update endpoints and examples for missing authentication, authorization bypass, path confusion, arbitrary write, unsafe flash offset/length handling, and failure to verify firmware authenticity where the code claims secure update behavior.
- Check update chunk parsing for integer overflow, flash boundary crossing, rollback/downgrade exposure, incomplete image bootability, and crash/power-loss unsafe state transitions.
- Flag any code path where unauthenticated network input can write firmware, configuration, filesystem contents, flash pages, bootloader state, or device identity material.
- Review device dashboard examples for security-sensitive default routes, credentials, upload handlers, debug endpoints, config mutation endpoints, and CORS/header behavior when they are likely to be copied into production.
- Do not report ordinary insecure demo behavior unless it creates a realistic copy-paste production vulnerability or affects library code.
**Protocol State Machine and Event-Driven Lifecycle Bugs:**
- Review transitions between listening, accepted, connecting, resolving, TLS handshaking, HTTP, WebSocket, MQTT, draining, closing, and closed states.
- Check whether malformed input can cause protocol handlers to process data in the wrong state, process stale data after close, or invoke callbacks with invalid event data.
- Look for use-after-free or stale pointer use when callbacks call `mg_close_conn`, mark a connection closing, mutate iobufs, change protocol handlers, or initiate TLS/WebSocket/MQTT transitions.
- Verify that partial reads/writes, backpressure, `is_full`, draining, TLS pending states, and send-buffer mutations cannot cause duplicated sends, skipped validation, or parser confusion.
- Review timer and wakeup handling for stale user data pointers, cross-thread queue misuse, and callbacks firing after associated connections/state have been freed.
- Only report concurrency or race issues when a concrete event ordering can trigger memory corruption, unauthorized action, or reliable device/process crash.
**Cross-Platform C Portability With Security Impact:**
- Review code paths whose security depends on struct packing, alignment, endianness, pointer size, signedness of `char`, integer width, or platform-specific filesystem/network behavior.
- Check for unaligned memory access on embedded targets when parsing attacker-controlled network packets.
- Verify endian conversions for Ethernet/IP/TCP/UDP/DNS/MQTT/TLS fields before bounds checks and allocations.
- Look for platform branches where one backend performs validation and another does not, especially across UNIX, Windows, lwIP, Zephyr, FreeRTOS-like systems, bare-metal, and built-in TCP/IP stack modes.
- Flag portability bugs only when they create a realistic vulnerability on a supported platform, not merely theoretical undefined behavior.
**Authentication, Authorization, and Access-Control Patterns in Mongoose Integrations:**
- Review examples and helper APIs that implement HTTP auth, cookies, sessions, tokens, device dashboards, REST APIs, MQTT topic access, upload endpoints, and firmware update endpoints.
- Flag authentication bypasses caused by prefix route matching, glob matching, decoded-vs-raw URI confusion, case normalization mistakes, missing boundary checks, or default-open sensitive endpoints.
- Check for authorization decisions made in client-side JavaScript only. Client-side checks are not security boundaries.
- Review CORS and origin checks only when they protect credentialed sensitive operations and the exploit path is concrete.
- Look for credential comparison bugs, truncation of usernames/passwords, accepting empty credentials, parsing only part of an Authorization header, or treating malformed credentials as anonymous-but-authorized.
- Flag sensitive data exposure when secrets, credentials, tokens, private keys, firmware signing material, PII, or device identity values can be read by unauthorized remote clients or logged from attacker-triggered paths.
+73 -28
View File
@@ -1,24 +1,34 @@
# Full Codebase Security Scan Prompt # Security Scan Master Prompt
You are an expert security reviewer performing a full-codebase security audit of the checked-out repository. You are an expert security reviewer performing a focused security audit centered on one assigned scan area in the checked-out repository.
You must analyze the repository as a whole and identify credible, externally reachable security vulnerabilities in the current implementation. You must analyze the assigned target and security surface and identify credible, externally reachable security vulnerabilities in the current implementation.
For this task, you will rely on two additional files, `resources/specs/claude-security-scan-instructions.md` detailing security scan instructions specific to the repository and `resources/specs/claude-false-positive-filtering.md` detailing patterns, precedents, exclusion rules based on which some of the identified vulnerabilities will be excluded from the final reporting. The instructions, rules and criterias found in those two files are MANDATORY TO BE HONOURED and your security audit will have to take all of them into account. Every full security scan uses:
YOU MUST READ AND ANALYZE BOTH OF THESE FILES BEFORE COMMENCING THE SECURITY AUDIT. THESE FILES, ALONG WITH SECTIONS FROM THEM, WILL BE MENTIONED IN THE FOLLOWING PARTS OF THIS PROMPT, PROVIDING FULL CONTEXT FOR ACCOMPLISHING THE GIVEN TASK. 1. the shared master prompt: `resources/specs/claude-security-scan.md`;
2. one area prompt selected by the workflow: `resources/specs/areas/<area>.md`;
3. the common filtering prompt: `resources/specs/claude-false-positive-filtering.md`.
The area prompt defines what is being scanned. This master prompt defines how security analysis and reporting are performed. The filtering prompt defines what must not be reported and which historical precedents must be honored.
YOU MUST READ AND ANALYZE THE ASSIGNED AREA PROMPT AND THE COMMON FILTERING PROMPT BEFORE COMMENCING THE SECURITY AUDIT. THE INSTRUCTIONS, RULES, AND CRITERIA IN ALL THREE PROMPTS ARE MANDATORY TO BE HONORED.
## OBJECTIVE ## OBJECTIVE
You are reviewing a full source repository for security vulnerabilities. You are reviewing one assigned security area in a source repository for security vulnerabilities.
The repository may contain application code, protocol parsers, networking code, embedded code, library code, examples, tests, build scripts, documentation, generated files, and third-party/vendor code. The `src/` directory contains the protocol parsers, networking code, embedded code, library code, generated code, and platform-specific code in scope for this review.
Your task is to determine which parts of the repository are security-relevant, identify externally reachable attack surfaces, and report only findings with a concrete vulnerability pattern, credible attacker influence, and meaningful security impact. The area-specific prompt defines the primary files and security surface for this invocation. Your task is to identify externally reachable attack surfaces within that assigned area and report only findings with a concrete vulnerability pattern, credible attacker influence, and meaningful security impact.
The security review target is limited to code under `src/`. Do not analyze, count, or report code outside `src/` as part of the security review.
You may inspect code outside the listed target files only when it is also under `src/` and is necessary to understand data flow, reachability, call chains, state transitions, memory ownership, mitigations, authentication or authorization, protocol interaction, exploitability, or impact. Inspecting supporting code must not turn into an independent security review of unrelated areas. Findings must remain tied to the assigned target and security surface and must identify an affected file under `src/`.
DO NOT modify files, create commits, open PRs, create issues or post comments! You must only produce the final JSON report requested below. DO NOT modify files, create commits, open PRs, create issues or post comments! You must only produce the final JSON report requested below.
Identify high-confidence security vulnerabilities in the full codebase. For this prompt, high-confidence means confidence >= 0.7 under the scoring rules below. Focus on vulnerabilities that are reachable through normal execution paths and attacker-controlled inputs. Report only findings that have a concrete security impact. Identify high-confidence security vulnerabilities in the assigned scan area. For this prompt, high-confidence means confidence >= 0.7 under the scoring rules below. Focus on vulnerabilities that are reachable through normal execution paths and attacker-controlled inputs. Report only findings that have a concrete security impact.
Do not report vague best-practice concerns, speculative hardening suggestions, style issues, maintainability concerns, generic bug risks, or findings without a clear attacker-controlled path. Do not report vague best-practice concerns, speculative hardening suggestions, style issues, maintainability concerns, generic bug risks, or findings without a clear attacker-controlled path.
@@ -26,9 +36,26 @@ Existing vulnerabilities in the current codebase are in scope.
Previously introduced vulnerabilities are in scope. Previously introduced vulnerabilities are in scope.
Any security-relevant issue present in the checked-out repository is in scope unless explicitly excluded below. Any security-relevant issue tied to the assigned target or security surface is in scope unless explicitly excluded below.
In the security scan instructions file `resources/specs/claude-security-scan-instructions.md`, read the `External Attacker Threat Model` and `Mongoose-Specific Security Review Scope` subsections found in the `SECURITY SCAN CONTEXT` main section for a full understanding of the scanning model and the code review context. ## SECURITY SCAN CONTEXT
**External Attacker Threat Model:**
- Analyze vulnerabilities from the perspective of an external attacker, not from the perspective of a developer, local user, debugger, test harness, or code running inside the process.
- The attacker can provide bytes through real external interfaces exposed by Mongoose or a Mongoose-based application: TCP, UDP, HTTP, WebSocket, MQTT, DNS, mDNS, TLS handshakes/certificates, Ethernet/IP/TCP/UDP packets, uploaded files, request paths, query strings, headers, message bodies, malicious server responses, and other protocol inputs.
- The attacker cannot directly call internal C functions, pass arbitrary invalid pointers, mutate internal structs, invoke callbacks manually, edit compile-time macros, alter build flags, write to hardware registers, control DMA descriptors directly, attach a debugger, or run code inside the target process.
- For a finding to be valid, show how attacker-controlled external input reaches the vulnerable code path through normal library/application execution.
- Do not report findings that require impossible internal states unless the finding also explains how an external input can cause that state.
- Do not report “API misuse” as a library vulnerability unless the API contract makes the insecure use likely, undocumented, or unavoidable.
- For embedded networking and driver code, assume the attacker can send malformed network traffic to the device, but cannot directly manipulate hardware registers, memory-mapped I/O, DMA rings, or interrupt state except as a consequence of packets processed by the driver.
**Mongoose-Specific Security Review Scope:**
- Treat this repository as a security-sensitive embedded C/C++ networking stack. Mongoose processes attacker-controlled bytes from TCP, UDP, HTTP, WebSocket, MQTT, DNS, mDNS, TLS, filesystem upload/download paths, and built-in TCP/IP drivers.
- In this repository, remotely triggerable denial of service is security-relevant when it is caused by a concrete implementation flaw such as memory corruption, parser abort, assertion failure, stack exhaustion, infinite loop, unbounded recursion, IRQ livelock, connection state corruption, descriptor-ring corruption, or a single unauthenticated packet causing process/device crash.
- Prioritize HIGH and MEDIUM severity findings. Include LOW only when unusually concrete, externally relevant, and useful. Do not report vague robustness concerns. Every finding must identify the attacker-controlled input, affected parser/state machine/buffer, concrete impact, and the exact code path.
- Prefer findings with a plausible packet/request/message shape. For network protocol issues, describe the relevant malformed HTTP request, MQTT packet, WebSocket frame, DNS message, TLS handshake/certificate input, Ethernet/IP/TCP/UDP packet, or filesystem request.
- Because this is C/C++, memory safety vulnerabilities are in scope. Buffer overflows, stack overflows, heap overflows, out-of-bounds reads/writes, use-after-free, double free, integer overflow leading to memory corruption, invalid pointer lifetime, and unsafe length conversions must be reviewed carefully.
- Do not dismiss issues merely because they are “only DoS” if the issue is remotely triggerable against an embedded server, broker, device dashboard, firmware-update endpoint, or network-facing parser.
## CRITICAL INSTRUCTIONS ## CRITICAL INSTRUCTIONS
@@ -56,12 +83,14 @@ Do not report:
* Purely defensive hardening suggestions. * Purely defensive hardening suggestions.
* Issues that only matter under unrealistic assumptions. * Issues that only matter under unrealistic assumptions.
### 3. Analyze the repository as a whole ### 3. Analyze the assigned scan area
This is a full-codebase scan. You should inspect the repository structure, identify major components, and prioritize security-sensitive code paths. This is a targeted scan centered on the primary files and security surface defined by the assigned area prompt. Follow relevant supporting code under `src/` when necessary to establish reachability, data flow, state, validation, mitigation, exploitability, or impact, but do not inspect code outside `src/` as part of the security review or independently review unrelated areas.
Do not restrict analysis to a diff. Do not assume only changed files matter. Do not assume an issue is irrelevant because it appears in old or long-standing code. Do not restrict analysis to a diff. Do not assume only changed files matter. Do not assume an issue is irrelevant because it appears in old or long-standing code.
Findings must remain tied to the assigned target and security surface.
### 4. Prioritize externally reachable attack surfaces ### 4. Prioritize externally reachable attack surfaces
Prioritize code reachable from attacker-controlled or peer-controlled inputs. Relevant inputs may include, but are not limited to: Prioritize code reachable from attacker-controlled or peer-controlled inputs. Relevant inputs may include, but are not limited to:
@@ -94,25 +123,40 @@ The exclusions section below is mandatory. If a potential finding falls under an
If a finding partially overlaps an exclusion, only report it if there is a concrete, non-excluded security impact. If a finding partially overlaps an exclusion, only report it if there is a concrete, non-excluded security impact.
## SECURITY CATEGORIES TO EXAMINE ## GENERAL SECURITY CATEGORIES TO EXAMINE
In the security scan instructions file `resources/specs/claude-security-scan-instructions.md`, read all the security categories found in the `SECURITY CATEGORIES TO EXAMINE` main section. THESE ARE THE SECURITY CATEGORIES WHICH NEED TO BE EXAMINED. The following common security categories apply to every assigned scan area. The assigned area prompt provides additional area-specific security guidance.
Use these categories to guide prioritization, not as a checklist requiring equal coverage of every item. Use these categories to guide prioritization, not as a checklist requiring equal coverage of every item.
**C Memory Safety and Length-Handling Vulnerabilities:**
- Look for writes to fixed-size stack or heap buffers where the loop bound is derived from attacker-controlled protocol fields, including topic counts, header counts, chunk counts, multipart parts, DNS labels, WebSocket fragments, TCP/IP options, or filesystem path components.
- Check all conversions between `size_t`, `int`, `long`, `uint16_t`, `uint32_t`, and signed protocol lengths. Flag integer truncation, wraparound, negative-to-large conversion, or off-by-one behavior that can affect allocation, parsing, copying, or bounds checks.
- Review any `memcpy`, `memmove`, `memcmp`, `snprintf`, `mg_snprintf`, `mg_xprintf`, iobuf append/delete/resize operation, and manual pointer increment where source length can originate from network input.
- Treat `struct mg_str` values as non-null-terminated unless the code proves otherwise. Flag use of `strlen`, `%s`, `strcmp`, `strstr`, or C-string APIs on attacker-controlled `mg_str.buf` unless length and termination are guaranteed.
- Check for parser code that advances pointers without proving that enough bytes remain. Flag any read of fixed-size fields, variable-length integers, DNS labels, TLS vectors, MQTT remaining length, WebSocket extended lengths, or HTTP chunks before bounds are validated.
- Look for inconsistent ownership and lifetime of buffers referenced by `mg_str`, connection receive/send iobufs, TLS buffers, filesystem buffers, and event callback data.
- Check for reentrancy hazards in event callbacks where user handlers may close a connection, mutate iobufs, start TLS, change protocol handlers, or free state while protocol code continues using stale pointers.
**Cross-Platform C Portability With Security Impact:**
- Review code paths whose security depends on struct packing, alignment, endianness, pointer size, signedness of `char`, integer width, or platform-specific filesystem/network behavior.
- Check for unaligned memory access on embedded targets when parsing attacker-controlled network packets.
- Verify endian conversions for Ethernet/IP/TCP/UDP/DNS/MQTT/TLS fields before bounds checks and allocations.
- Look for platform branches where one backend performs validation and another does not, especially across UNIX, Windows, lwIP, Zephyr, FreeRTOS-like systems, bare-metal, and built-in TCP/IP stack modes.
- Flag portability bugs only when they create a realistic vulnerability on a supported platform, not merely theoretical undefined behavior.
## ANALYSIS METHODOLOGY ## ANALYSIS METHODOLOGY
Follow this methodology before producing the final JSON. Follow this methodology before producing the final JSON.
### Phase 1: Repository context research ### Phase 1: Repository context research
First, understand the repository. First, understand the assigned area within `src/`.
Identify: Identify:
* Main source directories. * Relevant directories under `src/`.
* Build systems. * Public APIs declared under `src/`.
* Public APIs.
* Protocol implementations. * Protocol implementations.
* Network-facing components. * Network-facing components.
* File parsing components. * File parsing components.
@@ -120,14 +164,13 @@ Identify:
* TLS, crypto, or certificate validation logic. * TLS, crypto, or certificate validation logic.
* Memory management patterns. * Memory management patterns.
* Platform-specific code. * Platform-specific code.
* Example, test, fuzz, generated, and vendor directories. * Components in the assigned area most likely to process attacker-controlled input.
* Components most likely to process attacker-controlled input.
Use repository files to infer the actual architecture instead of assuming a generic web application model. Use files under `src/` to infer the actual architecture instead of assuming a generic web application model.
### Phase 2: Attack surface mapping ### Phase 2: Attack surface mapping
Map the externally reachable attack surfaces. Map the externally reachable attack surfaces within the assigned area.
For each important surface, determine: For each important surface, determine:
@@ -166,6 +209,7 @@ Before including any finding in the final output, confirm:
* The severity matches the impact. * The severity matches the impact.
* The finding is not a duplicate of another finding. * The finding is not a duplicate of another finding.
* The finding is not excluded. * The finding is not excluded.
* The finding is tied to the assigned target or security surface.
If you cannot provide this level of specificity, do not report the finding. If you cannot provide this level of specificity, do not report the finding.
@@ -218,6 +262,8 @@ The value of `files_reviewed` should reflect the number of files substantially i
Do not inflate this number by counting files that were only listed or briefly skimmed. Do not inflate this number by counting files that were only listed or briefly skimmed.
The value `review_completed: true` means the assigned scan area was meaningfully reviewed, not that the entire repository was reviewed.
## SEVERITY GUIDELINES ## SEVERITY GUIDELINES
Use these severity definitions. Use these severity definitions.
@@ -313,10 +359,9 @@ Take into account the project-specific filtering and reporting precedents found
## DIRECTORY AND FILE EXCLUSIONS ## DIRECTORY AND FILE EXCLUSIONS
Do not analyze or report findings from the following paths unless they are directly used by production/library code: Only analyze and report findings from files under `src/`.
- .github/
- resources/ Do not analyze or count code outside `src/` as part of the security review.
- test/
## OUTPUT REQUIREMENTS ## OUTPUT REQUIREMENTS
@@ -336,4 +381,4 @@ Do not include commentary.
Do not reply again after outputting the JSON. Do not reply again after outputting the JSON.
Begin the full-codebase security analysis now. Begin the assigned-area security analysis now.
+83 -2
View File
@@ -36,7 +36,7 @@
#endif #endif
#if MG_ENABLE_TCPIP && !defined(MG_ENABLE_DRIVER_IMXRT10) && \ #if MG_ENABLE_TCPIP && !defined(MG_ENABLE_DRIVER_IMXRT10) && \
!defined(MG_ENABLE_DRIVER_IMXRT11) !defined(MG_ENABLE_DRIVER_IMXRT11) && !defined(MG_ENABLE_DRIVER_NETC)
#if defined(CPU_MIMXRT1021DAG5A) || defined(CPU_MIMXRT1024DAG5A) || \ #if defined(CPU_MIMXRT1021DAG5A) || defined(CPU_MIMXRT1024DAG5A) || \
defined(CPU_MIMXRT1042XJM5B) || defined(CPU_MIMXRT1052DVL6B) || \ defined(CPU_MIMXRT1042XJM5B) || defined(CPU_MIMXRT1052DVL6B) || \
defined(CPU_MIMXRT1062DVL6B) || defined(CPU_MIMXRT1064DVL6B) || \ defined(CPU_MIMXRT1062DVL6B) || defined(CPU_MIMXRT1064DVL6B) || \
@@ -51,6 +51,8 @@
defined(_MIMXRT1176_cm7_H_) || defined(_MIMXRT1176_cm4_H_) || \ defined(_MIMXRT1176_cm7_H_) || defined(_MIMXRT1176_cm4_H_) || \
defined(_MIMXRT1176_H_) defined(_MIMXRT1176_H_)
#define MG_ENABLE_DRIVER_IMXRT11 1 #define MG_ENABLE_DRIVER_IMXRT11 1
#elif defined(CPU_MIMXRT1186CVJ8C_cm33) || defined(MIMXRT1186_cm33_H_)
#define MG_ENABLE_DRIVER_NETC 1
#else #else
#error Select an Ethernet driver in mongoose_config.h #error Select an Ethernet driver in mongoose_config.h
#endif #endif
@@ -82,13 +84,31 @@
defined(_MIMXRT1176_cm7_H_) || defined(_MIMXRT1176_cm4_H_) || \ defined(_MIMXRT1176_cm7_H_) || defined(_MIMXRT1176_cm4_H_) || \
defined(_MIMXRT1176_H_)) defined(_MIMXRT1176_H_))
#define MG_OTA MG_OTA_RT1170 #define MG_OTA MG_OTA_RT1170
#elif !defined(MG_OTA) && \
(defined(CPU_MIMXRT1186CVJ8C_cm33) || defined(MIMXRT1186_cm33_H_))
#define MG_OTA MG_OTA_RT1180
#endif #endif
#ifndef MG_IRAM #ifndef MG_IRAM
#if defined(CPU_MIMXRT1186CVJ8C_cm33) || defined(MIMXRT1186_cm33_H_)
#define MG_IRAM \
__attribute__((noinline, section(".ramfunc.$SRAM_ITC_cm33")))
#else
#define MG_IRAM __attribute__((noinline, section(".data_RAM2"))) #define MG_IRAM __attribute__((noinline, section(".data_RAM2")))
#endif #endif
#endif
#ifndef MG_ETH_RAM
#if defined(CPU_MIMXRT1186CVJ8C_cm33) || defined(MIMXRT1186_cm33_H_)
#define MG_ETH_RAM __attribute__((section(".bss.$ETH_RAM")))
#endif
#endif
#ifndef MG_SET_MAC_ADDRESS #ifndef MG_SET_MAC_ADDRESS
#if defined(MG_ENABLE_DRIVER_NETC) && MG_ENABLE_DRIVER_NETC
// No OCOTP_FUSES present on RT1186 to generate the UUID
#define MG_SET_MAC_ADDRESS(mac) (void) (mac)
#else
#if defined(MG_ENABLE_DRIVER_IMXRT11) && MG_ENABLE_DRIVER_IMXRT11 #if defined(MG_ENABLE_DRIVER_IMXRT11) && MG_ENABLE_DRIVER_IMXRT11
#define MG_OCOTP_FUSES ((volatile uint32_t *) 0x40cac900) #define MG_OCOTP_FUSES ((volatile uint32_t *) 0x40cac900)
#else #else
@@ -104,11 +124,70 @@
mac[5] = (MG_OCOTP_FUSES[4] >> 0) & 255; \ mac[5] = (MG_OCOTP_FUSES[4] >> 0) & 255; \
} while (0) } while (0)
#endif #endif
#endif
#ifndef MG_IMXRT_WDOG1_TIMEOUT_MS #ifndef MG_IMXRT_WDOG1_TIMEOUT_MS
#define MG_IMXRT_WDOG1_TIMEOUT_MS 10000 #define MG_IMXRT_WDOG1_TIMEOUT_MS 10000U
#endif #endif
#if defined(CPU_MIMXRT1186CVJ8C_cm33) || defined(MIMXRT1186_cm33_H_)
#define MG_IMXRT_RTWDOG1_UNLOCK() \
do { \
if ((RTWDOG1->CS & RTWDOG_CS_CMD32EN_MASK) != 0U) { \
RTWDOG1->CNT = RTWDOG_UPDATE_KEY; \
} else { \
RTWDOG1->CNT = RTWDOG_UPDATE_KEY & 0xffffU; \
RTWDOG1->CNT = RTWDOG_UPDATE_KEY >> 16; \
} \
while ((RTWDOG1->CS & RTWDOG_CS_ULK_MASK) == 0U) (void) 0; \
} while (0)
#define MG_IMXRT_RTWDOG1_FEED() \
do { \
uint32_t primask_ = __get_PRIMASK(); \
__disable_irq(); \
if ((RTWDOG1->CS & RTWDOG_CS_CMD32EN_MASK) != 0U) { \
RTWDOG1->CNT = RTWDOG_REFRESH_KEY; \
} else { \
RTWDOG1->CNT = RTWDOG_REFRESH_KEY & 0xffffU; \
RTWDOG1->CNT = RTWDOG_REFRESH_KEY >> 16; \
} \
if (primask_ == 0U) __enable_irq(); \
} while (0)
#ifndef MG_OTA_ROLLBACK_TIMER_START
#define MG_OTA_ROLLBACK_TIMER_START() \
do { \
uint32_t primask_ = __get_PRIMASK(); \
SRC_GENERAL_REG->SRMASK &= ~SRC_GENERAL_SRMASK_WDOG1_MASK_MASK; \
__disable_irq(); \
MG_IMXRT_RTWDOG1_UNLOCK(); \
RTWDOG1->WIN = 0U; \
RTWDOG1->TOVAL = (MG_IMXRT_WDOG1_TIMEOUT_MS + 7U) / 8U; \
RTWDOG1->CS = RTWDOG_CS_EN_MASK | RTWDOG_CS_UPDATE_MASK | \
RTWDOG_CS_CLK(1U) | RTWDOG_CS_PRES_MASK | \
RTWDOG_CS_CMD32EN_MASK; \
while ((RTWDOG1->CS & RTWDOG_CS_RCS_MASK) == 0U) (void) 0; \
if (primask_ == 0U) __enable_irq(); \
} while (0)
#endif
#ifndef MG_OTA_ROLLBACK_TIMER_FEED
#define MG_OTA_ROLLBACK_TIMER_FEED() MG_IMXRT_RTWDOG1_FEED()
#endif
// GPR5 survives RTWDOG global resets and is not reserved by the boot ROM.
#ifndef MG_OTA_STATE_GET
#define MG_OTA_STATE_GET() SRC_GENERAL_REG->GPR[5]
#endif
#ifndef MG_OTA_STATE_SET
#define MG_OTA_STATE_SET(v) (SRC_GENERAL_REG->GPR[5] = (uint32_t) (v))
#endif
#else
#define MG_IMXRT_WDOG1_FEED() \ #define MG_IMXRT_WDOG1_FEED() \
do { \ do { \
WDOG1->WSR = 0x5555; \ WDOG1->WSR = 0x5555; \
@@ -150,3 +229,5 @@
#endif #endif
#endif #endif
#endif
+8 -4
View File
@@ -175,6 +175,14 @@
#define MG_ENABLE_TCPIP_DRIVER_INIT 1 // enabled built-in driver for #define MG_ENABLE_TCPIP_DRIVER_INIT 1 // enabled built-in driver for
#endif // Mongoose built-in network stack #endif // Mongoose built-in network stack
#ifndef MG_ENABLE_TCPIP_TCPRTX
#define MG_ENABLE_TCPIP_TCPRTX 0 // TCP retransmission queue
#endif
#ifndef MG_TCPIP_DHCPNAME_SIZE
#define MG_TCPIP_DHCPNAME_SIZE 18 // struct mg_tcpip_if :: dhcp_name size
#endif
#ifndef MG_TCPIP_IP // e.g. MG_IPV4(192, 168, 0, 223) #ifndef MG_TCPIP_IP // e.g. MG_IPV4(192, 168, 0, 223)
#define MG_TCPIP_IP MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0 (DHCP) #define MG_TCPIP_IP MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0 (DHCP)
#endif #endif
@@ -211,10 +219,6 @@
#define MG_SET_MAC_ADDRESS(mac) #define MG_SET_MAC_ADDRESS(mac)
#endif #endif
#ifndef MG_TCPIP_DHCPNAME_SIZE
#define MG_TCPIP_DHCPNAME_SIZE 18 // struct mg_tcpip_if :: dhcp_name size
#endif
#ifndef MG_SET_WIFI_CONFIG #ifndef MG_SET_WIFI_CONFIG
#define MG_SET_WIFI_CONFIG(ifp, driver_data) #define MG_SET_WIFI_CONFIG(ifp, driver_data)
#endif #endif
+5
View File
@@ -170,6 +170,11 @@ void mg_dash_send_change(struct mg_mgr *mgr, struct mg_field_set *set) {
struct mg_dash_cdata *d; struct mg_dash_cdata *d;
struct mg_dash_user *u; struct mg_dash_user *u;
if (!c->is_websocket) continue; if (!c->is_websocket) continue;
if (c->send.len > MG_DASH_MAX_SEND_BUF_SIZE) { // Skip slow consumers
MG_ERROR(("skipping, send buf %lu > %d", (unsigned long) c->send.len,
MG_DASH_MAX_SEND_BUF_SIZE));
continue;
}
d = (struct mg_dash_cdata *) c->data; d = (struct mg_dash_cdata *) c->data;
u = d->u; u = d->u;
if (u == NULL) continue; if (u == NULL) continue;
+468
View File
@@ -0,0 +1,468 @@
#include "net_builtin.h"
#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP && \
defined(MG_ENABLE_DRIVER_NETC) && MG_ENABLE_DRIVER_NETC
// IMXRT1180 RM - Chapter 53 - Ethernet Controller (NETC)
#define ETH_DESC_CNT 8 // Descriptor count must be multiple of 8 (53.4.6.11.97)
#define ETH_PKT_SIZE 1536
struct netc_pci_function { // 53.4.6.1.1
volatile uint32_t DID_VID;
volatile uint16_t CMD, STAT;
volatile uint8_t reserved0[0x40];
volatile uint16_t DEV_CTL, DEV_STAT;
volatile uint8_t reserved1[0x34];
volatile uint16_t MSIX_CAP_LIST, MSIX_MSG_CTL;
};
struct netc_emdio { // 53.4.6.12
volatile uint32_t EMDIO_CFG, EMDIO_CTL, EMDIO_DATA, EMDIO_ADDR, EMDIO_STAT;
};
struct netc_enetc { // 53.4.6.6
volatile uint32_t ECAPR0, ECAPR1, ECAPR2, reserved0, PMR;
volatile uint8_t reserved1[0x1ec];
volatile uint32_t PSIPMMR, PSIPVMR;
volatile uint8_t reserved2[0x5f8];
volatile uint32_t SMCAPR;
volatile uint8_t reserved3[0x17fc];
struct {
volatile uint32_t PSIPMAR0, PSIPMAR1, PSIVLANR, reserved0, PSICFGR0,
PSICFGR1, PSICFGR2;
volatile uint8_t reserved1[0x3c];
volatile uint32_t PSIMMHFR0, PSIMMHFR1;
volatile uint8_t reserved2[0x20];
} PSI[1];
};
struct netc_port { // 53.4.6.8
volatile uint32_t PCAPR, PMCAPR, PIOCAPR, reserved0, PCR;
volatile uint8_t reserved1[0x0c];
volatile uint32_t PMAR0, PMAR1;
volatile uint8_t reserved2[0xd8];
volatile uint32_t POR, PSR;
volatile uint8_t reserved3[0x30];
volatile uint32_t PDGSR;
volatile uint8_t reserved4[0xc4];
struct {
volatile uint32_t PTGSTCSR, reserved0, PTCTMSDUR, reserved1, PTCCBSR0,
PTCCBSR1;
volatile uint8_t reserved2[8];
} TCT[8];
volatile uint8_t reserved5[0x220];
volatile uint32_t BPSTGSR;
};
struct netc_port_mac { // 53.4.6.9
volatile uint8_t reserved0[8];
volatile uint32_t COMMAND_CONFIG, MAC_ADDR_0, MAC_ADDR_1, MAXFRM, MINFRM;
volatile uint8_t reserved1[0x24];
volatile uint32_t IEVENT;
volatile uint8_t reserved2[0x1dc];
volatile uint64_t TFRMN;
volatile uint8_t reserved3[0xd8];
volatile uint32_t IF_MODE;
volatile uint8_t reserved4[0xfc];
};
struct netc_eth_link {
struct netc_port_mac PM[2];
};
struct netc_pseudo_link {
volatile uint32_t PPMSR;
volatile uint8_t reserved0[0x0c];
volatile uint32_t PPMCR;
};
struct netc_switch { // 53.4.6.5
volatile uint8_t reserved0[0x400];
volatile uint32_t MPCR;
volatile uint8_t reserved1[0x1c0c];
volatile uint32_t VFHTDECR0, VFHTDECR1, VFHTDECR2;
};
struct netc_bdr {
volatile uint32_t TBMR, TBSR;
volatile uint8_t reserved0[8];
volatile uint32_t TBBAR0, TBBAR1, TBPIR, TBCIR, TBLENR;
volatile uint8_t reserved1[0x7c];
volatile uint32_t TBIER, TBIDR, TBICR0, TBICR1;
volatile uint8_t reserved2[0x50];
volatile uint32_t RBMR, RBSR, RBBSR, RBCIR, RBBAR0, RBBAR1, RBPIR,
reserved3, RBLENR;
volatile uint8_t reserved4[0x5c];
volatile uint32_t RBDCR;
volatile uint8_t reserved5[0x1c];
volatile uint32_t RBIER, RBIDR, RBICR0, RBICR1;
volatile uint8_t reserved6[0x50];
};
struct netc_si { // 53.4.6.11
volatile uint32_t SIMR, SISR;
volatile uint8_t reserved0[0x30];
volatile uint32_t SIRBGCR;
volatile uint8_t reserved1[0x2ec];
volatile uint32_t SITFRM0;
volatile uint8_t reserved2[0x6fc];
volatile uint32_t SIRXIDR0;
volatile uint8_t reserved3[0x154];
volatile uint32_t SIMSIRRVR[1];
volatile uint8_t reserved4[0x747c];
struct netc_bdr BDR[1];
};
struct netc_msix_entry { // 53.4.2.11
volatile uint32_t MSG_ADDR, MSG_UPPER_ADDR, MSG_DATA, VECTOR_CTRL;
};
struct netc_msgintr { // 54.4
struct {
volatile uint32_t MSIIR, MSIR;
} MSI[3];
};
#define NETC_EMDIO_PCI \
((struct netc_pci_function *) (uintptr_t) 0x60001000U)
#define NETC_SWITCH_PCI \
((struct netc_pci_function *) (uintptr_t) 0x60002000U)
#define NETC_ENETC1_PCI \
((struct netc_pci_function *) (uintptr_t) 0x60004000U)
#define NETC_EMDIO ((struct netc_emdio *) (uintptr_t) 0x60ba1c00U)
#define NETC_ENETC1 ((struct netc_enetc *) (uintptr_t) 0x60b50000U)
#define NETC_ENETC1_PORT ((struct netc_port *) (uintptr_t) 0x60b54000U)
#define NETC_ENETC1_PSEUDO_LINK \
((struct netc_pseudo_link *) (uintptr_t) 0x60b55000U)
#define NETC_ENETC1_SI0 ((struct netc_si *) (uintptr_t) 0x60b40000U)
#define NETC_ENETC1_MSIX \
((struct netc_msix_entry *) (uintptr_t) 0x60c00000U)
#define NETC_SWITCH ((struct netc_switch *) (uintptr_t) 0x60a00000U)
#define NETC_SW0_PORT(link) \
((struct netc_port *) (uintptr_t) (0x60a04000U + \
(uint32_t) (link) * 0x4000U))
#define NETC_SW0_ETH(link) \
((struct netc_eth_link *) (uintptr_t) (0x60a05000U + \
(uint32_t) (link) * 0x4000U))
#define NETC_SW0_PORT4 ((struct netc_port *) (uintptr_t) 0x60a14000U)
#define NETC_MSGINTR1 \
((struct netc_msgintr *) (uintptr_t) 0x428a0000U)
enum {
NETC_RX_MSIX_ENTRY = 0,
NETC_RX_MSG_INDEX = 2,
};
struct netc_tx_bd {
uint64_t buffer_address;
uint16_t buffer_length, frame_length;
uint32_t control;
};
struct netc_rx_descriptor {
uint64_t buffer_address, reserved;
};
struct netc_rx_writeback {
uint16_t internet_checksum, parser_summary;
uint32_t rss_hash;
uint16_t buffer_length, vlan;
uint32_t status;
};
union netc_rx_bd {
struct netc_rx_descriptor descriptor;
struct netc_rx_writeback writeback;
};
static volatile struct netc_tx_bd s_txdesc[ETH_DESC_CNT] MG_ETH_RAM
__attribute__((aligned(128)));
static volatile union netc_rx_bd s_rxdesc[ETH_DESC_CNT] MG_ETH_RAM
__attribute__((aligned(128)));
static uint8_t s_txbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_ETH_RAM
__attribute__((aligned(64)));
static uint8_t s_rxbuf[ETH_DESC_CNT][ETH_PKT_SIZE] MG_ETH_RAM
__attribute__((aligned(64)));
static uint16_t s_tx_prod;
static uint16_t s_tx_clean;
static uint16_t s_rx_cons;
static struct mg_tcpip_if *s_ifp;
static uint16_t enet_read_phy(uint8_t addr, uint8_t reg) {
NETC_EMDIO->EMDIO_CFG &= ~MG_BIT(6); // ENC45: use Clause 22
NETC_EMDIO->EMDIO_CTL = MG_BIT(15) | ((uint32_t) (addr & 0x1fU) << 5) |
(reg & 0x1fU); // READ, PORT_ADDR, DEV_ADDR
while (NETC_EMDIO->EMDIO_CFG & MG_BIT(31)) (void) 0; // BSY1
return (uint16_t) (NETC_EMDIO->EMDIO_DATA & 0xffffU);
}
static void enet_write_phy(uint8_t addr, uint8_t reg, uint16_t value) {
NETC_EMDIO->EMDIO_CFG &= ~MG_BIT(6); // ENC45: use Clause 22
NETC_EMDIO->EMDIO_CTL = ((uint32_t) (addr & 0x1fU) << 5) |
(reg & 0x1fU); // PORT_ADDR, DEV_ADDR
NETC_EMDIO->EMDIO_DATA = value;
while (NETC_EMDIO->EMDIO_CFG & MG_BIT(31)) (void) 0; // BSY1
}
static bool mg_tcpip_driver_netc_init(struct mg_tcpip_if *ifp) {
struct mg_tcpip_driver_netc_data *d =
(struct mg_tcpip_driver_netc_data *) ifp->driver_data;
struct mg_phy phy = {enet_read_phy, enet_write_phy};
struct netc_port *switch_port;
struct netc_eth_link *eth_link;
uint64_t tx_base = (uint64_t) (uintptr_t) s_txdesc;
uint64_t rx_base = (uint64_t) (uintptr_t) s_rxdesc;
uint32_t mac0 = (uint32_t) ifp->mac[0] |
((uint32_t) ifp->mac[1] << 8) |
((uint32_t) ifp->mac[2] << 16) |
((uint32_t) ifp->mac[3] << 24);
uint32_t mac1 = (uint32_t) ifp->mac[4] |
((uint32_t) ifp->mac[5] << 8);
uint32_t mdc_cr =
(uint32_t) (d->mdc_cr < 0 ? MG_DRIVER_MDC_CR : d->mdc_cr);
switch_port = NETC_SW0_PORT(d->link);
eth_link = NETC_SW0_ETH(d->link);
s_ifp = ifp;
for (int i = 0; i < ETH_DESC_CNT; i++) {
s_txdesc[i].buffer_address = (uint64_t) (uintptr_t) s_txbuf[i];
s_rxdesc[i].descriptor.buffer_address =
(uint64_t) (uintptr_t) s_rxbuf[i];
}
NETC_EMDIO_PCI->DEV_CTL |= MG_BIT(15); // INIT_FLR
while (NETC_EMDIO_PCI->DEV_CTL & MG_BIT(15)) (void) 0;
NETC_EMDIO_PCI->CMD |= MG_BIT(2) | MG_BIT(1); // BUS_MASTER_EN, MEM_ACCESS
NETC_EMDIO->EMDIO_CFG =
((mdc_cr & 0x1ffU) << 7) | MG_BIT(2); // MDIO_CLK_DIV, MDIO_HOLD
mg_phy_init(&phy, d->phy_addr, 0);
// Initialize ENETC1 PSI0 and port
NETC_ENETC1_PCI->CMD |= MG_BIT(2) | MG_BIT(1); // BUS_MASTER_EN, MEM_ACCESS
NETC_ENETC1_PCI->MSIX_MSG_CTL = MG_BIT(14); // FUNC_MASK
NETC_ENETC1_PORT->POR |= MG_BIT(1) | MG_BIT(0); // RXDIS, TXDIS
NETC_ENETC1_PCI->DEV_CTL |= MG_BIT(15); // INIT_FLR
while (NETC_ENETC1_PCI->DEV_CTL & MG_BIT(15)) (void) 0;
NETC_ENETC1_PCI->CMD |= MG_BIT(2) | MG_BIT(1); // BUS_MASTER_EN, MEM_ACCESS
NETC_ENETC1_PCI->MSIX_MSG_CTL = MG_BIT(14); // FUNC_MASK
NETC_ENETC1_PORT->PCR = 99U << 16; // PSPEED: 1 Gbps
NETC_ENETC1_PORT->PMAR0 = mac0;
NETC_ENETC1_PORT->PMAR1 = mac1;
NETC_ENETC1_PSEUDO_LINK->PPMCR = MG_BIT(16); // TXPAD
NETC_ENETC1->PSIPMMR &= ~(MG_BIT(16) | MG_BIT(0));
NETC_ENETC1_SI0->SIMR &=
~(MG_BIT(31) | MG_BIT(3) | MG_BIT(2) | MG_BIT(1));
NETC_ENETC1_SI0->SIRBGCR = 0;
NETC_ENETC1->PSI[0].PSICFGR0 =
MG_BIT(16) | MG_BIT(0); // one Rx BDR, one Tx BDR
NETC_ENETC1->PSI[0].PSICFGR2 = 0; // NUM_MSIX: one entry
NETC_ENETC1_MSIX[NETC_RX_MSIX_ENTRY].VECTOR_CTRL = MG_BIT(0); // MASK
NETC_ENETC1_MSIX[NETC_RX_MSIX_ENTRY].MSG_ADDR =
(uint32_t) (uintptr_t) &NETC_MSGINTR1->MSI[0].MSIIR;
NETC_ENETC1_MSIX[NETC_RX_MSIX_ENTRY].MSG_UPPER_ADDR = 0;
NETC_ENETC1_MSIX[NETC_RX_MSIX_ENTRY].MSG_DATA = NETC_RX_MSG_INDEX;
NETC_ENETC1_SI0->SIMSIRRVR[0] = NETC_RX_MSIX_ENTRY; // Rx ring 0 -> entry 0
// Initialize switch function 2 to drive the selected physical port
NETC_SWITCH_PCI->CMD |= MG_BIT(2) | MG_BIT(1);
NETC_SWITCH_PCI->MSIX_MSG_CTL = MG_BIT(14); // FUNC_MASK
switch_port->POR |= MG_BIT(1) | MG_BIT(0);
eth_link->PM[0].COMMAND_CONFIG |= MG_BIT(26); // SWR
while (eth_link->PM[0].COMMAND_CONFIG & MG_BIT(26)) (void) 0;
eth_link->PM[1].COMMAND_CONFIG |= MG_BIT(26); // SWR
while (eth_link->PM[1].COMMAND_CONFIG & MG_BIT(26)) (void) 0;
NETC_SWITCH_PCI->DEV_CTL |= MG_BIT(15); // INIT_FLR
while (NETC_SWITCH_PCI->DEV_CTL & MG_BIT(15)) (void) 0;
NETC_SWITCH_PCI->CMD |= MG_BIT(2) | MG_BIT(1);
NETC_SWITCH_PCI->MSIX_MSG_CTL = MG_BIT(14); // FUNC_MASK
// Configure switch physical port and ethernet link (ETH_X)
switch_port->PCR = 99U << 16; // PSPEED: 1 Gbps
switch_port->PMAR0 = mac0;
switch_port->PMAR1 = mac1;
switch_port->TCT[0].PTCTMSDUR = MG_BIT(24) | MG_BIT(16) | ETH_PKT_SIZE;
eth_link->PM[0].MAXFRM = ETH_PKT_SIZE;
eth_link->PM[0].MINFRM = 64;
eth_link->PM[0].IF_MODE = MG_BIT(14) | 4U; // 1G RGMII
eth_link->PM[0].COMMAND_CONFIG =
MG_BIT(15) | MG_BIT(1) | MG_BIT(0); // TXP, RX_EN, TX_EN
eth_link->PM[1].MAXFRM = ETH_PKT_SIZE;
eth_link->PM[1].MINFRM = 64;
eth_link->PM[1].IF_MODE = MG_BIT(14) | 4U;
eth_link->PM[1].COMMAND_CONFIG =
MG_BIT(15) | MG_BIT(1) | MG_BIT(0);
// Bridge the selected physical port with management port 4 (54.4.1.1)
NETC_SWITCH->VFHTDECR0 = MG_BIT(4) | MG_BIT(d->link);
NETC_SWITCH->VFHTDECR2 = (1UL << 27) | (1UL << 24); // MFO=1, MLO=1
switch_port->BPSTGSR = 2U; // STG0: forwarding
NETC_SW0_PORT4->BPSTGSR = 2U; // STG0: forwarding
switch_port->POR &= ~(MG_BIT(1) | MG_BIT(0));
NETC_SW0_PORT4->POR &= ~(MG_BIT(1) | MG_BIT(0));
// Initialize Tx BD ring 0 (direct switch enqueue)
NETC_ENETC1_SI0->BDR[0].TBBAR0 = (uint32_t) tx_base;
NETC_ENETC1_SI0->BDR[0].TBBAR1 = (uint32_t) (tx_base >> 32);
NETC_ENETC1_SI0->BDR[0].TBLENR = ETH_DESC_CNT;
// Initialize Rx BD ring 0
NETC_ENETC1_SI0->BDR[0].RBICR0 =
MG_BIT(31) | MG_BIT(0); // ICEN, interrupt after one frame
NETC_ENETC1_SI0->BDR[0].RBBAR0 = (uint32_t) rx_base;
NETC_ENETC1_SI0->BDR[0].RBBAR1 = (uint32_t) (rx_base >> 32);
NETC_ENETC1_SI0->BDR[0].RBBSR = ETH_PKT_SIZE;
NETC_ENETC1_SI0->BDR[0].RBLENR = ETH_DESC_CNT;
// Clear stale device and MSGINTR state before the ring becomes active
NETC_ENETC1_SI0->SIRXIDR0 = MG_BIT(0); // RX0, W1C
(void) NETC_MSGINTR1->MSI[0].MSIR; // clear pending messages
NETC_ENETC1_SI0->BDR[0].RBIER = MG_BIT(0); // RXTIE
NETC_ENETC1_SI0->BDR[0].TBMR = MG_BIT(31);
NETC_ENETC1_SI0->BDR[0].RBMR = MG_BIT(31); // EN, CRC=0: strip FCS
NETC_ENETC1_PORT->POR &= ~(MG_BIT(1) | MG_BIT(0));
NETC_ENETC1->PMR |= MG_BIT(16); // SI0EN
NETC_ENETC1_SI0->SIMR |= MG_BIT(31); // EN
MG_DSB();
if ((NETC_ENETC1->SMCAPR & MG_BIT(0)) == 0U ||
(NETC_SWITCH->MPCR & 0x1fU) != 4U) {
MG_ERROR(("NETC fixed management path unavailable: SMCAPR=%08lx MPCR=%08lx",
(unsigned long) NETC_ENETC1->SMCAPR,
(unsigned long) NETC_SWITCH->MPCR));
return false;
}
NETC_ENETC1_PCI->MSIX_MSG_CTL =
MG_BIT(15); // Enable RX interrupt, MSIX_EN=1, FUNC_MASK=0
MG_DSB();
NETC_ENETC1_MSIX[NETC_RX_MSIX_ENTRY].VECTOR_CTRL = 0; // MASK=0, last
MG_DSB();
return true;
}
static size_t mg_tcpip_driver_netc_tx(const void *buf, size_t len,
struct mg_tcpip_if *ifp) {
struct mg_tcpip_driver_netc_data *d =
(struct mg_tcpip_driver_netc_data *) ifp->driver_data;
uint16_t next;
s_tx_clean =
(uint16_t) (NETC_ENETC1_SI0->BDR[0].TBCIR & 0xffffU); // BDR_INDEX
next = (uint16_t) (s_tx_prod + 1U);
if (next >= ETH_DESC_CNT) next = 0;
if (len > sizeof(s_txbuf[0])) {
MG_ERROR(("Frame too big, %ld", (long) len));
len = (size_t) -1; // fail
} else if (next == s_tx_clean) {
ifp->nerr++;
MG_ERROR(("No descriptors available"));
len = 0; // retry later
} else {
memcpy(s_txbuf[s_tx_prod], buf, len); // Copy data
s_txdesc[s_tx_prod].buffer_address =
(uint64_t) (uintptr_t) s_txbuf[s_tx_prod];
s_txdesc[s_tx_prod].buffer_length = (uint16_t) len; // BUFF_LEN
s_txdesc[s_tx_prod].frame_length = (uint16_t) len; // FRAME_LEN
s_txdesc[s_tx_prod].control =
MG_BIT(31) | (2UL << 24) | MG_BIT(23) | ((uint32_t) d->link << 16);
// F=1, FLQ=2, SMSO=1, EGR_PORT=link, IPV=0, DR=0
MG_DSB();
if (++s_tx_prod >= ETH_DESC_CNT) s_tx_prod = 0;
NETC_ENETC1_SI0->BDR[0].TBPIR = s_tx_prod; // BDR_INDEX
}
return len;
}
static void mg_tcpip_driver_netc_update_hash_table(struct mg_tcpip_if *ifp) {
// mcast address has NETC XOR hash index 56 (RM 53.4.2.3.1.3.8.4.1)
NETC_ENETC1->PSI[0].PSIMMHFR0 = 0;
NETC_ENETC1->PSI[0].PSIMMHFR1 = MG_BIT(24);
(void) ifp;
}
static bool mg_tcpip_driver_netc_poll(struct mg_tcpip_if *ifp, bool s1) {
if (ifp->update_mac_hash_table) {
mg_tcpip_driver_netc_update_hash_table(ifp);
ifp->update_mac_hash_table = false;
}
if (!s1) return false;
struct mg_tcpip_driver_netc_data *d =
(struct mg_tcpip_driver_netc_data *) ifp->driver_data;
struct netc_port *switch_port = NETC_SW0_PORT(d->link);
struct netc_eth_link *eth_link = NETC_SW0_ETH(d->link);
uint8_t speed = MG_PHY_SPEED_10M;
bool up, full_duplex = false;
struct mg_phy phy = {enet_read_phy, enet_write_phy};
up = mg_phy_up(&phy, d->phy_addr, &full_duplex, &speed);
if ((ifp->state == MG_TCPIP_STATE_DOWN) && up) {
uint16_t port_speed = speed == MG_PHY_SPEED_1000M ? 99U
: speed == MG_PHY_SPEED_100M ? 9U
: 0U;
uint32_t config = switch_port->PCR;
uint32_t mode = eth_link->PM[0].IF_MODE;
config &= ~(0x3fffU << 16); // PCR[29:16] PSPEED
config |= (uint32_t) port_speed << 16;
mode &= ~(MG_BIT(14) | MG_BIT(13) | MG_BIT(6)); // SSP, HD
if (speed == MG_PHY_SPEED_1000M) {
mode |= MG_BIT(14); // SSP: 1 Gbps, full duplex
} else {
if (speed == MG_PHY_SPEED_10M) mode |= MG_BIT(13); // SSP
if (!full_duplex) mode |= MG_BIT(6); // HD
}
switch_port->PCR = config;
eth_link->PM[0].IF_MODE = mode;
eth_link->PM[1].IF_MODE = mode;
// TX_CSD, RX_CSD, and SPD_DUP are latched while the PHY is reset/down.
eth_link->PM[0].IEVENT = MG_BIT(23) | MG_BIT(22) | MG_BIT(21);
MG_DSB();
MG_DEBUG(("Link is %uM %s-duplex",
speed == MG_PHY_SPEED_1000M ? 1000U
: speed == MG_PHY_SPEED_100M ? 100U
: 10U,
full_duplex ? "full" : "half"));
}
return up;
}
void MSGINTR1_IRQHandler(void);
void MSGINTR1_IRQHandler(void) {
if (NETC_MSGINTR1->MSI[0].MSIR & MG_BIT(NETC_RX_MSG_INDEX)) {
NETC_ENETC1_SI0->SIRXIDR0 = MG_BIT(0); // RX0, W1C
MG_DSB();
for (int i = 0; i < 10; i++) { // Read as they arrive but not forever
uint32_t status;
size_t len;
if (s_rx_cons ==
(uint16_t) (NETC_ENETC1_SI0->BDR[0].RBPIR & 0xffffU))
break;
status = s_rxdesc[s_rx_cons].writeback.status;
if (!(status & MG_BIT(30))) break; // R: writeback is ready
len = s_rxdesc[s_rx_cons].writeback.buffer_length;
if ((status & (MG_BIT(31) | MG_BIT(30))) ==
(MG_BIT(31) | MG_BIT(30)) && // F, R
!(status & (0xffU << 16)) && // ERROR
!(status & (0xfU << 2)) && // HR=0: regular frame
len > 0 && len <= sizeof(s_rxbuf[0])) {
mg_tcpip_qwrite(s_rxbuf[s_rx_cons], len, s_ifp); // FCS stripped
}
s_rxdesc[s_rx_cons].descriptor.buffer_address =
(uint64_t) (uintptr_t) s_rxbuf[s_rx_cons];
s_rxdesc[s_rx_cons].descriptor.reserved = 0;
MG_DSB();
if (++s_rx_cons >= ETH_DESC_CNT) s_rx_cons = 0;
NETC_ENETC1_SI0->BDR[0].RBCIR = s_rx_cons;
}
}
MG_DSB();
}
struct mg_tcpip_driver mg_tcpip_driver_netc = {mg_tcpip_driver_netc_init,
mg_tcpip_driver_netc_tx, NULL,
mg_tcpip_driver_netc_poll};
#endif
+49
View File
@@ -0,0 +1,49 @@
#pragma once
#if defined(MG_ENABLE_TCPIP) && MG_ENABLE_TCPIP && \
defined(MG_ENABLE_DRIVER_NETC) && MG_ENABLE_DRIVER_NETC
struct mg_tcpip_driver_netc_data {
int mdc_cr;
uint8_t phy_addr;
// Selects the physical Ethernet link (ETH0 through ETH3)
// Its switch port is bridged to pseudo-port 4,
// which connects internally to ENETC1
// IMXRT1180 RM 53.4.1.1 NETC Block diagram
uint8_t link;
};
#ifndef MG_TCPIP_PHY_ADDR
#define MG_TCPIP_PHY_ADDR 5
#endif
#ifndef MG_DRIVER_MDC_CR
#define MG_DRIVER_MDC_CR 48
#endif
#ifndef MG_TCPIP_NETC_LINK
#define MG_TCPIP_NETC_LINK 0
#endif
struct mg_tcpip_driver;
extern struct mg_tcpip_driver mg_tcpip_driver_netc;
#define MG_TCPIP_DRIVER_INIT(mgr) \
do { \
static struct mg_tcpip_driver_netc_data driver_data_; \
static struct mg_tcpip_if mif_; \
driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \
driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \
driver_data_.link = MG_TCPIP_NETC_LINK; \
mif_.ip = MG_TCPIP_IP; \
mif_.mask = MG_TCPIP_MASK; \
mif_.gw = MG_TCPIP_GW; \
mif_.driver = &mg_tcpip_driver_netc; \
mif_.driver_data = &driver_data_; \
MG_SET_MAC_ADDRESS(mif_.mac); \
mg_tcpip_init(mgr, &mif_); \
MG_INFO(("Driver: netc, MAC: %M", mg_print_mac, mif_.mac)); \
} while (0)
#endif
+30 -2
View File
@@ -11,7 +11,9 @@ enum { // ID1 ID2
MG_PHY_RTL8201 = 0xc816, // 001c c816 - RTL8201F MG_PHY_RTL8201 = 0xc816, // 001c c816 - RTL8201F
MG_PHY_RTL8211 = 0xc916, // 001c c916 - RTL8211F MG_PHY_RTL8211 = 0xc916, // 001c c916 - RTL8211F
MG_PHY_ICS1894x = 0x15, MG_PHY_ICS1894x = 0x15,
MG_PHY_ICS189432 = 0xf450 // 0015 f450 - ICS1894 MG_PHY_ICS189432 = 0xf450, // 0015 f450 - ICS1894
MG_PHY_YT85x = 0x4f51,
MG_PHY_YT8531 = 0xe91a // 4f51 e91a - Motorcomm YT8531
}; };
enum { enum {
@@ -29,7 +31,10 @@ enum {
MG_PHY_RTL82x_REG_PAGESEL = 31, MG_PHY_RTL82x_REG_PAGESEL = 31,
MG_PHY_RTL8201_REG_RMSR = 16, // in page 7 MG_PHY_RTL8201_REG_RMSR = 16, // in page 7
MG_PHY_RTL8211_REG_PHYSR = 26, // in page a43 MG_PHY_RTL8211_REG_PHYSR = 26, // in page a43
MG_PHY_ICS189432_REG_POLL = 17 MG_PHY_ICS189432_REG_POLL = 17,
MG_PHY_YT8531_REG_PHYSTS = 17,
MG_PHY_YT8531_REG_EXT_ADDR = 30,
MG_PHY_YT8531_REG_EXT_DATA = 31
}; };
static const char *mg_phy_id_to_str(uint16_t id1, uint16_t id2) { static const char *mg_phy_id_to_str(uint16_t id1, uint16_t id2) {
@@ -60,6 +65,8 @@ static const char *mg_phy_id_to_str(uint16_t id1, uint16_t id2) {
} }
case MG_PHY_ICS1894x: case MG_PHY_ICS1894x:
return "ICS1894x"; return "ICS1894x";
case MG_PHY_YT85x:
return id2 == MG_PHY_YT8531 ? "YT8531" : "YT85x";
default: default:
return "unknown"; return "unknown";
} }
@@ -75,6 +82,18 @@ void mg_phy_init(struct mg_phy *phy, uint8_t phy_addr, uint8_t config) {
id2 = phy->read_reg(phy_addr, MG_PHY_REG_ID2); id2 = phy->read_reg(phy_addr, MG_PHY_REG_ID2);
MG_INFO(("PHY ID: %#04x %#04x (%s)", id1, id2, mg_phy_id_to_str(id1, id2))); MG_INFO(("PHY ID: %#04x %#04x (%s)", id1, id2, mg_phy_id_to_str(id1, id2)));
if (id1 == MG_PHY_YT85x && id2 == MG_PHY_YT8531) {
uint16_t value;
// TODO (robertc2000): Autonegotiation remains disabled after PHY is reset
// We temporarily force enabling autoneg, until a workaround is found.
phy->write_reg(phy_addr, MG_PHY_REG_BCR, MG_BIT(12));
phy->write_reg(phy_addr, MG_PHY_YT8531_REG_EXT_ADDR, 0xa003);
value = phy->read_reg(phy_addr, MG_PHY_YT8531_REG_EXT_DATA);
value &= (uint16_t) ~(MG_BIT(3) | MG_BIT(2) | MG_BIT(1) | MG_BIT(0));
phy->write_reg(phy_addr, MG_PHY_YT8531_REG_EXT_DATA,
(uint16_t) (value | MG_BIT(1))); // TXDLY: 2 * 150 ps
}
if (id1 == MG_PHY_DP83x && id2 == MG_PHY_DP83867) { if (id1 == MG_PHY_DP83x && id2 == MG_PHY_DP83867) {
phy->write_reg(phy_addr, 0x0d, 0x1f); // write 0x10d to IO_MUX_CFG (0x0170) phy->write_reg(phy_addr, 0x0d, 0x1f); // write 0x10d to IO_MUX_CFG (0x0170)
phy->write_reg(phy_addr, 0x0e, 0x170); phy->write_reg(phy_addr, 0x0e, 0x170);
@@ -168,6 +187,15 @@ bool mg_phy_up(struct mg_phy *phy, uint8_t phy_addr, bool *full_duplex,
uint16_t poll_reg = phy->read_reg(phy_addr, MG_PHY_ICS189432_REG_POLL); uint16_t poll_reg = phy->read_reg(phy_addr, MG_PHY_ICS189432_REG_POLL);
*full_duplex = poll_reg & MG_BIT(14); *full_duplex = poll_reg & MG_BIT(14);
*speed = (poll_reg & MG_BIT(15)) ? MG_PHY_SPEED_100M : MG_PHY_SPEED_10M; *speed = (poll_reg & MG_BIT(15)) ? MG_PHY_SPEED_100M : MG_PHY_SPEED_10M;
} else if (id1 == MG_PHY_YT85x) {
uint16_t id2 = phy->read_reg(phy_addr, MG_PHY_REG_ID2);
if (id2 == MG_PHY_YT8531) {
uint16_t physts = phy->read_reg(phy_addr, MG_PHY_YT8531_REG_PHYSTS);
*full_duplex = physts & MG_BIT(13);
*speed = (physts & MG_BIT(15)) ? MG_PHY_SPEED_1000M
: (physts & MG_BIT(14)) ? MG_PHY_SPEED_100M
: MG_PHY_SPEED_10M;
}
} }
} }
return up; return up;
+2 -2
View File
@@ -34,8 +34,8 @@ typedef size_t (*mg_pm_t)(mg_pfn_t fn, void *arg, va_list *);
// - mg_print_base64 - prints a buffer as a base64-encoded string // - mg_print_base64 - prints a buffer as a base64-encoded string
// - mg_print_esc - prints a JSON-escaped string // - mg_print_esc - prints a JSON-escaped string
// - mg_print_hex - prints a buffer as a hex string // - mg_print_hex - prints a buffer as a hex string
// - mg_print_ip - prints an IP address in a struct mg_str // - mg_print_ip - prints an IP address in a struct mg_addr
// - mg_print_ip_port - prints IP address and port in a struct mg_str // - mg_print_ip_port - prints IP address and port in a struct mg_addr
// - mg_print_ip4 - prints an IPv4 address // - mg_print_ip4 - prints an IPv4 address
// - mg_print_ip6 - prints an IPv6 address // - mg_print_ip6 - prints an IPv6 address
// - mg_print_mac - prints a MAC address // - mg_print_mac - prints a MAC address
+4 -2
View File
@@ -67,8 +67,10 @@ bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...) {
va_start(ap, fmt); va_start(ap, fmt);
data = mg_vmprintf(fmt, &ap); data = mg_vmprintf(fmt, &ap);
va_end(ap); va_end(ap);
result = mg_file_write(fs, path, data, strlen(data)); if (data != NULL) {
mg_free(data); result = mg_file_write(fs, path, data, strlen(data));
mg_free(data);
}
return result; return result;
} }
+2 -2
View File
@@ -50,9 +50,9 @@ static void *ff_open(const char *path, int flags) {
if (flags & MG_FS_WRITE) { if (flags & MG_FS_WRITE) {
mode |= FA_WRITE; mode |= FA_WRITE;
if (flags & MG_FS_EXCL) { if (flags & MG_FS_EXCL) {
mode |= FA_OPEN_ALWAYS | FA_OPEN_APPEND;
} else {
mode |= FA_CREATE_NEW; mode |= FA_CREATE_NEW;
} else {
mode |= FA_OPEN_ALWAYS | FA_OPEN_APPEND;
} }
} }
if ((fp = mg_calloc(1, sizeof(*fp))) != NULL && if ((fp = mg_calloc(1, sizeof(*fp))) != NULL &&
+21
View File
@@ -0,0 +1,21 @@
#include "health.h"
#include "health_cortex.h"
struct mg_health mg_health_record;
void mg_health_init(void) {
static const char magic[] = MG_HEALTH_MAGIC;
memset(&mg_health_record, 0, sizeof(mg_health_record));
memcpy(mg_health_record.magic, magic, sizeof(mg_health_record.magic));
#if MG_HEALTH == MG_HEALTH_CORTEX
mg_health_cortex_init(&mg_health_cortex);
mg_health_record.get_blob = mg_health_cortex_get_blob;
mg_health_record.fn_data = &mg_health_cortex;
#endif
}
struct mg_str mg_health_get_blob(void) {
return !mg_health_valid() || mg_health_record.get_blob == NULL
? mg_str("")
: mg_health_record.get_blob(mg_health_record.fn_data);
}
+89
View File
@@ -0,0 +1,89 @@
// Device health monitoring
//
// A health provider owns the report format and returns it as an opaque blob.
// Applications and transports use mg_health_get_blob() without knowing how the
// report was collected or encoded.
#pragma once
#include "str.h"
#define MG_HEALTH_NONE 0
#define MG_HEALTH_CORTEX 1
#ifndef MG_HEALTH
#define MG_HEALTH MG_HEALTH_NONE
#endif
#ifndef MG_HEALTH_MAGIC
#define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version
#endif
// Why the device restarted. Hardware usually reports several flags at once,
// so these are ordered by how much they tell you: the most specific cause a
// device reports wins
enum mg_health_reason {
MG_HEALTH_RESET_UNKNOWN = 0, // Nothing reported, or no masks configured
MG_HEALTH_RESET_POWER, // Power-on reset, RAM contents are lost
MG_HEALTH_RESET_BROWNOUT, // Supply dipped below the brown-out level
MG_HEALTH_RESET_PIN, // External reset pin, e.g. the reset button
MG_HEALTH_RESET_SOFTWARE, // Firmware asked for it, e.g. NVIC_SystemReset
MG_HEALTH_RESET_LOWPOWER, // Illegal or failed low-power transition
MG_HEALTH_RESET_WATCHDOG, // Watchdog fired: the application stopped
MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack
};
typedef struct mg_str mg_health_blob;
struct mg_health {
char magic[4];
mg_health_blob (*get_blob)(void *);
void *fn_data;
uint32_t reset_reason;
};
extern struct mg_health mg_health_record;
// Return true if the record holds valid data, i.e. it survived the reset and
// was written by this firmware layout
static inline bool mg_health_valid(void) {
static const char magic[] = MG_HEALTH_MAGIC;
return memcmp(mg_health_record.magic, magic,
sizeof(mg_health_record.magic)) == 0;
}
// Select and initialise the configured health provider. Call after
// initializing the board-specific persistent-storage
void mg_health_init(void);
// Return the provider's opaque report. The returned memory is provider-owned
// and remains valid until the next call to this function
struct mg_str mg_health_get_blob(void);
static inline int mg_health_reason(void) {
return mg_health_valid() ? (int) mg_health_record.reset_reason
: MG_HEALTH_RESET_UNKNOWN;
}
static inline const char *mg_health_reason_str(int reason) {
switch (reason) {
case MG_HEALTH_RESET_POWER:
return "power";
case MG_HEALTH_RESET_BROWNOUT:
return "brownout";
case MG_HEALTH_RESET_PIN:
return "pin";
case MG_HEALTH_RESET_SOFTWARE:
return "software";
case MG_HEALTH_RESET_LOWPOWER:
return "lowpower";
case MG_HEALTH_RESET_WATCHDOG:
return "watchdog";
case MG_HEALTH_RESET_FAULT:
return "fault";
default:
return "unknown";
}
}
#define MG_HEALTH_INIT() mg_health_init()
+344
View File
@@ -0,0 +1,344 @@
#include "health_cortex.h"
#include "printf.h"
#if MG_HEALTH == MG_HEALTH_CORTEX
#define MG_SCB_CPUID (*(volatile uint32_t *) 0xe000ed00)
#define MG_SCB_CFSR (*(volatile uint32_t *) 0xe000ed28)
#define MG_SCB_HFSR (*(volatile uint32_t *) 0xe000ed2c)
#define MG_SCB_DFSR (*(volatile uint32_t *) 0xe000ed30)
#define MG_SCB_MMFAR (*(volatile uint32_t *) 0xe000ed34)
#define MG_SCB_BFAR (*(volatile uint32_t *) 0xe000ed38)
#define MG_SCB_AFSR (*(volatile uint32_t *) 0xe000ed3c)
#define MG_SCB_ABFSR (*(volatile uint32_t *) 0xe000efa8)
#define MG_CPU_CORTEX_M0 0xc20
#define MG_CPU_CORTEX_M23 0xd20
#define MG_CPU_CORTEX_M3 0xc23
#define MG_CPU_CORTEX_M4 0xc24
#define MG_CPU_CORTEX_M7 0xc27
#define MG_CPU_CORTEX_M0P 0xc60
#define MG_CPU_CORTEX_M33 0xd21
#define MG_CPU_CORTEX_M35P 0xd31
#define MG_CPU_CORTEX_M52 0xd24
#define MG_CPU_CORTEX_M55 0xd22
#define MG_CPU_CORTEX_M85 0xd23
extern uint32_t __StackLimit __attribute__((weak));
extern uint32_t __StackTop __attribute__((weak));
extern uint32_t _sstack __attribute__((weak));
extern uint32_t _estack __attribute__((weak));
extern bool mg_health_cortex_stack_range(const void *, uintptr_t *, uintptr_t *)
__attribute__((weak));
static struct mg_health_cortex_record s_record;
static char s_report[MG_HEALTH_CORTEX_REPORT_SIZE];
static bool storage_init(void *storage, size_t size) {
// Note: actual init is handled in main()
(void) storage;
(void) size;
return true;
}
static bool storage_read(const void *storage, size_t offset, void *buf,
size_t len) {
volatile const uint8_t *src = (volatile const uint8_t *) storage + offset;
uint8_t *dst = (uint8_t *) buf;
size_t i;
for (i = 0; i < len; i++) dst[i] = src[i];
return true;
}
static bool storage_write(void *storage, size_t offset, const void *buf,
size_t len) {
volatile uint8_t *dst = (volatile uint8_t *) storage + offset;
const uint8_t *src = (const uint8_t *) buf;
size_t i;
for (i = 0; i < len; i++) dst[i] = src[i];
return true;
}
struct mg_health_cortex mg_health_cortex = {
MG_HEALTH_CORTEX_STORAGE,
MG_HEALTH_CORTEX_STORAGE_SIZE,
storage_init,
storage_read,
storage_write,
};
static bool storage_bounds(const struct mg_health_cortex *ctx, size_t offset,
size_t len) {
return ctx != NULL && ctx->storage != NULL && offset <= ctx->storage_size &&
len <= ctx->storage_size - offset;
}
static bool storage_configured(const struct mg_health_cortex *ctx) {
return ctx != NULL && ctx->storage != NULL && ctx->storage_size != 0 &&
ctx->storage_size >= sizeof(struct mg_health_cortex_record) &&
ctx->init != NULL && ctx->read != NULL && ctx->write != NULL;
}
static bool record_read(struct mg_health_cortex *ctx,
struct mg_health_cortex_record *record) {
static const char magic[] = MG_HEALTH_MAGIC;
if (ctx == NULL || ctx->read == NULL ||
!storage_bounds(ctx, 0, sizeof(*record)) ||
!ctx->read(ctx->storage, 0, record, sizeof(*record))) {
return false;
}
return memcmp(record->magic, magic, sizeof(record->magic)) == 0 &&
record->version == MG_HEALTH_CORTEX_VERSION &&
record->stack_word_count <= MG_HEALTH_CORTEX_STACK_WORDS;
}
static bool record_write(struct mg_health_cortex *ctx,
const struct mg_health_cortex_record *record) {
char invalid[sizeof(record->magic)] = {0};
size_t offset = sizeof(record->magic);
if (ctx == NULL || ctx->write == NULL ||
!storage_bounds(ctx, 0, sizeof(*record)) ||
!ctx->write(ctx->storage, 0, &invalid, sizeof(invalid))) {
return false;
}
if (!ctx->write(ctx->storage, offset, (const uint8_t *) record + offset,
sizeof(*record) - offset)) {
return false;
}
if (!ctx->write(ctx->storage, 0, record->magic, sizeof(record->magic)))
return false;
return true;
}
static bool stack_range(const void *ptr, uintptr_t *lo, uintptr_t *hi) {
if (mg_health_cortex_stack_range != NULL &&
mg_health_cortex_stack_range(ptr, lo, hi)) {
return *lo < *hi;
} else if (&__StackLimit != NULL && &__StackTop != NULL) {
*lo = (uintptr_t) &__StackLimit;
*hi = (uintptr_t) &__StackTop;
} else if (&_sstack != NULL && &_estack != NULL) {
*lo = (uintptr_t) &_sstack;
*hi = (uintptr_t) &_estack;
} else {
return false;
}
return *lo < *hi;
}
static int stack_valid(const void *ptr, size_t words) {
uintptr_t lo, hi, p = (uintptr_t) ptr;
if ((p & 3U) != 0) return 0;
if (!stack_range(ptr, &lo, &hi)) return -1;
return p >= lo && p <= hi && words <= (hi - p) / sizeof(uint32_t);
}
static bool has_fault_status(uint32_t part) {
return part == MG_CPU_CORTEX_M3 || part == MG_CPU_CORTEX_M4 ||
part == MG_CPU_CORTEX_M7 || part == MG_CPU_CORTEX_M33 ||
part == MG_CPU_CORTEX_M35P || part == MG_CPU_CORTEX_M52 ||
part == MG_CPU_CORTEX_M55 || part == MG_CPU_CORTEX_M85;
}
static const char *core_name(uint32_t part) {
switch (part) {
case MG_CPU_CORTEX_M0:
return "cortex-m0";
case MG_CPU_CORTEX_M0P:
return "cortex-m0+";
case MG_CPU_CORTEX_M23:
return "cortex-m23";
case MG_CPU_CORTEX_M3:
return "cortex-m3";
case MG_CPU_CORTEX_M4:
return "cortex-m4";
case MG_CPU_CORTEX_M7:
return "cortex-m7";
case MG_CPU_CORTEX_M33:
return "cortex-m33";
case MG_CPU_CORTEX_M35P:
return "cortex-m35p";
case MG_CPU_CORTEX_M52:
return "cortex-m52";
case MG_CPU_CORTEX_M55:
return "cortex-m55";
case MG_CPU_CORTEX_M85:
return "cortex-m85";
default:
return "unknown";
}
}
static const char *arch_name(uint32_t part) {
if (part == MG_CPU_CORTEX_M0 || part == MG_CPU_CORTEX_M0P) return "armv6-m";
if (part == MG_CPU_CORTEX_M3) return "armv7-m";
if (part == MG_CPU_CORTEX_M4 || part == MG_CPU_CORTEX_M7) return "armv7e-m";
return "armv8-m";
}
static bool json_append(char *buf, size_t size, size_t *offset, const char *fmt,
...) {
va_list ap;
size_t available, n;
if (*offset >= size) return false;
available = size - *offset;
va_start(ap, fmt);
n = mg_vsnprintf(buf + *offset, available, fmt, &ap);
va_end(ap);
if (n >= available) {
*offset = size;
return false;
}
*offset += n;
return true;
}
void mg_health_cortex_init(struct mg_health_cortex *ctx) {
if (storage_configured(ctx)) {
(void) ctx->init(ctx->storage, ctx->storage_size);
}
}
struct mg_str mg_health_cortex_get_blob(void *data) {
struct mg_health_cortex *ctx = (struct mg_health_cortex *) data;
struct mg_health_cortex_record *r = &s_record;
uint32_t part;
size_t n = 0, i;
bool ok;
if (!storage_configured(ctx)) {
return mg_str(
"{\"valid\":false,\"error\":\"health storage is not configured\"}");
}
if (!record_read(ctx, r)) return mg_str("{\"valid\":false}");
part = (r->cpuid >> 4) & 0xfffU;
ok = json_append(
s_report, sizeof(s_report), &n,
"{\"valid\":true,\"version\":%lu,\"arch\":\"%s\","
"\"core\":\"%s\",\"image\":\"firmware.elf\","
"\"map\":\"firmware.elf.map\",\"binary\":\"firmware.bin\","
"\"cpuid\":\"0x%08lx\",\"exception_sp\":\"0x%08lx\","
"\"exc_return\":\"0x%08lx\",\"exception_valid\":%s,"
"\"stack_valid\":%s,\"regs\":{"
"\"r0\":\"0x%08lx\",\"r1\":\"0x%08lx\","
"\"r2\":\"0x%08lx\",\"r3\":\"0x%08lx\","
"\"r4\":\"0x%08lx\",\"r5\":\"0x%08lx\","
"\"r6\":\"0x%08lx\",\"r7\":\"0x%08lx\","
"\"r8\":\"0x%08lx\",\"r9\":\"0x%08lx\","
"\"r10\":\"0x%08lx\",\"r11\":\"0x%08lx\","
"\"r12\":\"0x%08lx\",\"sp\":\"0x%08lx\","
"\"lr\":\"0x%08lx\",\"pc\":\"0x%08lx\","
"\"xpsr\":\"0x%08lx\"},\"fault\":{"
"\"cfsr\":\"0x%08lx\",\"hfsr\":\"0x%08lx\","
"\"dfsr\":\"0x%08lx\",\"afsr\":\"0x%08lx\","
"\"mmfar\":\"0x%08lx\",\"bfar\":\"0x%08lx\","
"\"abfsr\":\"0x%08lx\"},\"stack\":{"
"\"addr\":\"0x%08lx\",\"word_size\":4,\"words\":[",
(unsigned long) r->version, arch_name(part), core_name(part),
(unsigned long) r->cpuid, (unsigned long) r->exception_sp,
(unsigned long) r->exc_return, r->exception_valid > 0 ? "true" : "false",
r->stack_valid > 0 ? "true" : "false", (unsigned long) r->r0,
(unsigned long) r->r1, (unsigned long) r->r2, (unsigned long) r->r3,
(unsigned long) r->r4, (unsigned long) r->r5, (unsigned long) r->r6,
(unsigned long) r->r7, (unsigned long) r->r8, (unsigned long) r->r9,
(unsigned long) r->r10, (unsigned long) r->r11, (unsigned long) r->r12,
(unsigned long) r->sp, (unsigned long) r->lr, (unsigned long) r->pc,
(unsigned long) r->psr, (unsigned long) r->cfsr, (unsigned long) r->hfsr,
(unsigned long) r->dfsr, (unsigned long) r->afsr,
(unsigned long) r->mmfar, (unsigned long) r->bfar,
(unsigned long) r->abfsr, (unsigned long) r->sp);
for (i = 0; ok && i < r->stack_word_count; i++) {
ok = json_append(s_report, sizeof(s_report), &n, "%s\"0x%08lx\"",
i == 0 ? "" : ",", (unsigned long) r->stack_words[i]);
}
if (ok) ok = json_append(s_report, sizeof(s_report), &n, "]}}");
return ok ? mg_str_n(s_report, n)
: mg_str("{\"valid\":false,\"error\":\"JSON overflow\"}");
}
static void __attribute__((used)) mg_health_cortex_handler(
uint32_t *exception_stack, uint32_t lr,
const uint32_t *callee_saved) {
static const char magic[] = MG_HEALTH_MAGIC;
uint32_t cpuid = MG_SCB_CPUID;
uint32_t part = (cpuid >> 4) & 0xfffU;
size_t fp_words = (lr & (1U << 4)) == 0 ? 18U : 0U;
int valid = stack_valid(exception_stack, fp_words + 8U);
struct mg_health_cortex_record *r = &s_record;
volatile uint32_t *words = (volatile uint32_t *) r;
size_t i;
for (i = 0; i < sizeof(*r) / sizeof(*words); i++) words[i] = 0;
r->version = MG_HEALTH_CORTEX_VERSION;
r->cpuid = cpuid;
r->exception_sp = (uint32_t) (uintptr_t) exception_stack;
r->exc_return = lr;
r->exception_valid = valid;
r->r4 = callee_saved[0];
r->r5 = callee_saved[1];
r->r6 = callee_saved[2];
r->r7 = callee_saved[3];
r->r8 = callee_saved[4];
r->r9 = callee_saved[5];
r->r10 = callee_saved[6];
r->r11 = callee_saved[7];
if (valid > 0) {
uint32_t *core_frame = exception_stack + fp_words;
uint32_t *fault_sp;
uintptr_t lo, hi, p;
size_t count;
r->r0 = core_frame[0];
r->r1 = core_frame[1];
r->r2 = core_frame[2];
r->r3 = core_frame[3];
r->r12 = core_frame[4];
r->lr = core_frame[5];
r->pc = core_frame[6];
r->psr = core_frame[7];
fault_sp = core_frame + 8;
if (r->psr & (1U << 9)) fault_sp++;
r->sp = (uint32_t) (uintptr_t) fault_sp;
r->stack_valid = stack_valid(fault_sp, 0);
p = (uintptr_t) fault_sp;
if (r->stack_valid > 0 && stack_range(fault_sp, &lo, &hi)) {
(void) lo;
count = (hi - p) / sizeof(uint32_t);
if (count > MG_HEALTH_CORTEX_STACK_WORDS)
count = MG_HEALTH_CORTEX_STACK_WORDS;
r->stack_word_count = (uint32_t) count;
for (i = 0; i < count; i++) r->stack_words[i] = fault_sp[i];
}
}
if (has_fault_status(part)) {
r->cfsr = MG_SCB_CFSR;
r->hfsr = MG_SCB_HFSR;
r->dfsr = MG_SCB_DFSR;
r->afsr = MG_SCB_AFSR;
r->bfar = MG_SCB_BFAR;
r->mmfar = MG_SCB_MMFAR;
if (part == MG_CPU_CORTEX_M7) r->abfsr = MG_SCB_ABFSR;
}
memcpy(r->magic, magic, sizeof(r->magic));
(void) record_write(&mg_health_cortex, r);
for (;;) (void) 0;
}
void HardFault_Handler(void);
__attribute__((naked)) void HardFault_Handler(void) {
__asm volatile(
"mov r0, lr \n"
"movs r1, #4 \n"
"tst r0, r1 \n"
"beq 1f \n"
"mrs r0, psp \n"
"b 2f \n"
"1: \n"
"mrs r0, msp \n"
"2: \n"
"mov r1, lr \n"
"stmdb sp!, {r4-r11} \n"
"mov r2, sp \n"
"b mg_health_cortex_handler \n");
}
#endif
+77
View File
@@ -0,0 +1,77 @@
#pragma once
#include "health.h"
#define MG_HEALTH_CORTEX_VERSION 1U
#ifndef MG_HEALTH_CORTEX_STORAGE
#define MG_HEALTH_CORTEX_STORAGE NULL
#endif
#ifndef MG_HEALTH_CORTEX_STORAGE_SIZE
#define MG_HEALTH_CORTEX_STORAGE_SIZE 0
#endif
#ifndef MG_HEALTH_CORTEX_STACK_WORDS
#define MG_HEALTH_CORTEX_STACK_WORDS 980U
#endif
#ifndef MG_HEALTH_CORTEX_REPORT_SIZE
#define MG_HEALTH_CORTEX_REPORT_SIZE 16384U
#endif
// Storage operations are deliberately format-agnostic. H723 uses the default
// direct-memory implementation; boards with different retained storage can
// replace these callbacks before calling MG_HEALTH_INIT()
struct mg_health_cortex {
void *storage;
size_t storage_size;
bool (*init)(void *, size_t);
bool (*read)(const void *, size_t, void *, size_t);
bool (*write)(void *, size_t, const void *, size_t);
};
struct mg_health_cortex_record {
char magic[4];
uint32_t version;
uint32_t cpuid;
uint32_t exception_sp;
uint32_t sp;
uint32_t exc_return;
int32_t exception_valid;
int32_t stack_valid;
uint32_t r0;
uint32_t r1;
uint32_t r2;
uint32_t r3;
uint32_t r4;
uint32_t r5;
uint32_t r6;
uint32_t r7;
uint32_t r8;
uint32_t r9;
uint32_t r10;
uint32_t r11;
uint32_t r12;
uint32_t lr;
uint32_t pc;
uint32_t psr;
uint32_t cfsr;
uint32_t hfsr;
uint32_t dfsr;
uint32_t afsr;
uint32_t bfar;
uint32_t mmfar;
uint32_t abfsr;
uint32_t stack_word_count;
uint32_t stack_words[MG_HEALTH_CORTEX_STACK_WORDS];
};
extern struct mg_health_cortex mg_health_cortex;
void mg_health_cortex_init(struct mg_health_cortex *);
struct mg_str mg_health_cortex_get_blob(void *);
+57 -6
View File
@@ -170,6 +170,7 @@ int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len,
mg_str_to_num(mg_str_n(src + i + 1, 2), 16, &dst[j], sizeof(uint8_t)); mg_str_to_num(mg_str_n(src + i + 1, 2), 16, &dst[j], sizeof(uint8_t));
i += 2; i += 2;
} else { } else {
dst[0] = '\0';
return -1; return -1;
} }
} else if (is_form_url_encoded && src[i] == '+') { } else if (is_form_url_encoded && src[i] == '+') {
@@ -818,9 +819,11 @@ static int uri_to_path2(struct mg_connection *c, struct mg_http_message *hm,
path[path_size - 1] = '\0'; path[path_size - 1] = '\0';
// Terminate root dir with slash // Terminate root dir with slash
if (n > 0 && path[n - 1] != '/') path[n++] = '/', path[n] = '\0'; if (n > 0 && path[n - 1] != '/') path[n++] = '/', path[n] = '\0';
if (url.len < hm->uri.len) { if (url.len < hm->uri.len &&
mg_url_decode(hm->uri.buf + url.len, hm->uri.len - url.len, path + n, mg_url_decode(hm->uri.buf + url.len, hm->uri.len - url.len, path + n,
path_size - n, 0); path_size - n, 0) < 0) {
mg_http_reply(c, 400, "", "Invalid path");
return -1;
} }
path[path_size - 1] = '\0'; // Double-check path[path_size - 1] = '\0'; // Double-check
n = strlen(path); n = strlen(path);
@@ -972,8 +975,14 @@ long mg_http_upload(struct mg_connection *c, struct mg_http_message *hm,
struct mg_fs *fs, const char *dir, size_t max_size) { struct mg_fs *fs, const char *dir, size_t max_size) {
char buf[20] = "0", file[MG_PATH_MAX], path[MG_PATH_MAX]; char buf[20] = "0", file[MG_PATH_MAX], path[MG_PATH_MAX];
long res = 0, offset; long res = 0, offset;
mg_http_get_var(&hm->query, "offset", buf, sizeof(buf)); if (mg_http_get_var(&hm->query, "offset", buf, sizeof(buf)) == -3) {
mg_http_get_var(&hm->query, "file", file, sizeof(file)); mg_http_reply(c, 400, "", "invalid offset");
return -3;
}
if (mg_http_get_var(&hm->query, "file", file, sizeof(file)) == -3) {
mg_http_reply(c, 400, "", "invalid file");
return -2;
}
offset = strtol(buf, NULL, 0); offset = strtol(buf, NULL, 0);
mg_snprintf(path, sizeof(path), "%s%c%s", dir, MG_DIRSEP, file); mg_snprintf(path, sizeof(path), "%s%c%s", dir, MG_DIRSEP, file);
if (hm->body.len == 0) { if (hm->body.len == 0) {
@@ -1108,6 +1117,47 @@ void mg_http_start_ota(struct mg_connection *c, struct mg_http_message *hm,
mg_call(c, MG_EV_READ, &c->recv.len); mg_call(c, MG_EV_READ, &c->recv.len);
} }
void mg_http_stream_body(struct mg_connection *c, int ev, void *ev_data,
struct mg_str uri_pattern,
void (*cb)(struct mg_http_message *, struct mg_str *,
void **user_data)) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
// Catch upload requests early, without buffering whole body
// When we receive MG_EV_HTTP_HDRS event, that means we've received all
// HTTP headers but not necessarily full HTTP body
if (ev == MG_EV_HTTP_HDRS &&
(mg_strcmp(hm->method, mg_str("POST")) == 0 ||
mg_strcmp(hm->method, mg_str("PUT")) == 0) &&
hm->body.len != (size_t) ~0 && mg_match(hm->uri, uri_pattern, NULL)) {
c->pfn = NULL; // Silence HTTP protocol handler, we'll use MG_EV_READ
c->pfn_data = (void *) (ptrdiff_t) hm->body.len; // Record expected len
cb(hm, NULL, &c->fn_data); // Call handler before deleting headers
if (mg_http_get_header(hm, "Expect") != NULL) {
mg_http_reply(c, 100, NULL, ""); // If curl expects, we continue
}
mg_iobuf_del(&c->recv, 0, hm->head.len); // Delete HTTP headers
}
if (c->pfn == NULL && ev != MG_EV_OPEN) {
if (c->pfn_data != NULL && c->recv.len > 0) {
// Got some uploaded data. Feed by 512-byte aligned chunks, for OTA
size_t left = (size_t) (ptrdiff_t) c->pfn_data, r = c->recv.len;
size_t aligned = r < left ? MG_ROUND_DOWN(r, 512) : left;
struct mg_str data = mg_str_n((char *) c->recv.buf, aligned);
if (data.len) cb(NULL, &data, &c->fn_data);
c->pfn_data = (void *) (ptrdiff_t) (left - aligned);
c->recv.len -= aligned;
}
if (c->pfn_data == NULL && c->is_draining == 0) {
// Finished upload
mg_http_reply(c, 200, NULL, "ok\n");
c->is_draining = 1;
cb(NULL, NULL, &c->fn_data);
}
}
}
static bool is_hex_digit(int c) { static bool is_hex_digit(int c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F'); (c >= 'A' && c <= 'F');
@@ -1183,8 +1233,9 @@ static void http_cb(struct mg_connection *c, int ev, void *ev_data) {
// contain a Content-length header (unless the body length is 0, in // contain a Content-length header (unless the body length is 0, in
// which case it can be omitted). Other requests can also contain a // which case it can be omitted). Other requests can also contain a
// body, but their content has no defined semantics (RFC 7231) // body, but their content has no defined semantics (RFC 7231)
// It is a non-chunked request, so the offset (ofs) is updated after
// exiting this branch.
if (hm.body.len != 0) require_content_len = true; if (hm.body.len != 0) require_content_len = true;
ofs += (size_t) n; // this request has been processed
} else if (is_response) { } else if (is_response) {
// HTTP spec 7.2 Entity body: All other responses must include a body // HTTP spec 7.2 Entity body: All other responses must include a body
// or Content-Length header field defined with a value of 0. // or Content-Length header field defined with a value of 0.
+23
View File
@@ -201,3 +201,26 @@ size_t mg_http_next_multipart(struct mg_str, size_t, struct mg_http_part *);
// Returns the HTTP status code from a parsed response message (e.g. 200, 404). // Returns the HTTP status code from a parsed response message (e.g. 200, 404).
int mg_http_status(const struct mg_http_message *hm); int mg_http_status(const struct mg_http_message *hm);
// Streams a POST/PUT body to a callback as it arrives, without buffering it.
// Call it from your connection handler on every event.
//
// Callback upload_fn(hm, data, user_data) is called:
// - at start: hm is set, data is NULL. Open the file, set user_data.
// - per chunk: hm is NULL, data is set. Chunks are 512 bytes aligned
// except the last one.
// - at end: both NULL. Close the file.
// After the last chunk, "200 ok" is sent and the connection is drained.
// Example:
// mg_http_stream_body(c, ev, ev_data, mg_str("/fs/*"), upload);
// Full example:
// tutorials/http/file-upload-single-post
// Related APIs:
// mg_http_start_upload(), mg_http_start_ota()
// Notes:
// Requires Content-Length, chunked bodies are not streamed. user_data is
// &c->fn_data. While streaming, c->pfn is NULL
void mg_http_stream_body(struct mg_connection *c, int ev, void *ev_data,
struct mg_str uri_pattern,
void (*upload_fn)(struct mg_http_message *,
struct mg_str *, void **user_data));
+1
View File
@@ -135,6 +135,7 @@ bool mg_l2_eth_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto,
} }
pay->buf = ((char *) eth) + hdrlen; pay->buf = ((char *) eth) + hdrlen;
pay->len = len - hdrlen; pay->len = len - hdrlen;
if (len > ifp->framesize) return false; // Oversized
for (i = 0; i < sizeof(eth_types) / sizeof(uint16_t); i++) { for (i = 0; i < sizeof(eth_types) / sizeof(uint16_t); i++) {
if (type == eth_types[i]) break; if (type == eth_types[i]) break;
} }
+4 -1
View File
@@ -101,7 +101,7 @@ static uint16_t s_id;
void mg_l2_ppp_init(struct mg_tcpip_if *ifp) { void mg_l2_ppp_init(struct mg_tcpip_if *ifp) {
ifp->l2mtu = 1500; ifp->l2mtu = 1500;
ifp->framesize = 1500 + sizeof(struct ppp) + sizeof(struct hdlc_); ifp->framesize = ifp->l2mtu + sizeof(struct ppp) + sizeof(struct hdlc_) + 2;
} }
extern void mg_l2_eth_init(struct mg_tcpip_if *); extern void mg_l2_eth_init(struct mg_tcpip_if *);
@@ -466,6 +466,7 @@ bool mg_l2_ppp_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto,
pay->buf = (char *) raw->buf; pay->buf = (char *) raw->buf;
pay->len = raw->len - 2; pay->len = raw->len - 2;
} }
if (pay->len > ifp->l2mtu + sizeof(struct ppp)) return false; // Oversized
return ppp_rx(ifp, proto, pay, raw); return ppp_rx(ifp, proto, pay, raw);
} }
@@ -548,6 +549,8 @@ bool mg_l2_pppoe_rx(struct mg_tcpip_if *ifp, enum mg_l2proto *proto,
if (!mg_l2_eth_rx(ifp, &eth_proto, pay, raw)) return false; if (!mg_l2_eth_rx(ifp, &eth_proto, pay, raw)) return false;
pppoe = (struct pppoe *) pay->buf; // here we handle pay, not raw pppoe = (struct pppoe *) pay->buf; // here we handle pay, not raw
if (pay->len < sizeof(*pppoe)) return false; // Truncated if (pay->len < sizeof(*pppoe)) return false; // Truncated
if (pay->len - sizeof(*pppoe) > ifp->l2mtu + sizeof(struct ppp))
return false; // Oversized
if (eth_proto == MG_TCPIP_L2PROTO_PPPoE_DISC) { if (eth_proto == MG_TCPIP_L2PROTO_PPPoE_DISC) {
MG_VERBOSE(("PPPoE_DISC")); MG_VERBOSE(("PPPoE_DISC"));
if (s_state == MG_PPPoE_ST_DISC && pppoe->code == MG_PPPoE_PADO && if (s_state == MG_PPPoE_ST_DISC && pppoe->code == MG_PPPoE_PADO &&
+173
View File
@@ -0,0 +1,173 @@
#include "mdash.h"
#if MG_ENABLE_MDASH
#include "base64.h"
#include "health.h"
#include "log.h"
#include "ota.h"
#include "printf.h"
#include "rpc.h"
#include "sntp.h"
#include "tls.h"
#include "url.h"
#include "ws.h"
static const char *mg_mdash_ca_pem =
"-----BEGIN CERTIFICATE-----\n"
"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n"
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n"
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n"
"WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n"
"ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n"
"MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n"
"h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n"
"0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n"
"A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n"
"T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n"
"B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n"
"B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n"
"KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n"
"OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n"
"jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n"
"qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n"
"rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n"
"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n"
"hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n"
"ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n"
"3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n"
"NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n"
"ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n"
"TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n"
"jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n"
"oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n"
"4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n"
"mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n"
"emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n"
"-----END CERTIFICATE-----\n";
static struct mg_connection *mg_mdash_conn = NULL;
// static struct mg_str mg_mdash_key;
// static void mg_connection *mg_mdash_conn = NULL;
static void mg_mdash_fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_CONNECT) {
// c->is_hexdumping = true;
if (mg_url_is_ssl(MG_MDASH_URL)) {
struct mg_str host = mg_url_host(MG_MDASH_URL);
struct mg_tls_opts opts = {.ca = mg_str(mg_mdash_ca_pem), .name = host};
mg_tls_init(c, &opts);
}
}
if (ev == MG_EV_WS_OPEN) {
MG_INFO(("Connected to %s", MG_MDASH_URL));
// s_mDashState = MDASH_STATE_CLOUD_CONNECTED;
// mDashTriggerEvent(s_mDashState, NULL);
} else if (ev == MG_EV_WS_MSG) {
struct mg_iobuf io = {0, 0, 0, 512};
struct mg_ws_message *wm = ev_data;
struct mg_str s = wm->data;
while (s.len > 0 && isspace((uint8_t) s.buf[s.len - 1])) s.len--;
MG_INFO(("RPC: %.*s", (int) s.len, s.buf));
struct mg_rpc_req r = {.head = &c->mgr->rpcs,
.rpc = NULL,
.pfn = mg_pfn_iobuf,
.pfn_data = &io,
.req_data = NULL,
.frame = s};
mg_rpc_process(&r);
if (io.buf != NULL) {
MG_INFO((" %s", io.buf));
mg_ws_send(c, (const char *) io.buf, io.len, WEBSOCKET_OP_TEXT);
} else {
MG_DEBUG(("%s", "RPC unhandled"));
}
mg_iobuf_free(&io);
} else if (ev == MG_EV_CLOSE) {
mg_mdash_conn = NULL;
MG_INFO(("disconnected, ram %lu", 0));
}
(void) c;
}
static void mg_mdash_rpc_get_info(struct mg_rpc_req *r) {
struct mg_str blob = mg_health_get_blob();
if (blob.buf == NULL) blob.len = 0;
mg_rpc_ok(r, "{%m:%m,%m:%llu,%m:%m,%m:\"mws.%d\",%m:{%m:%m,%m:%llu,%m:%m}}",
MG_ESC("fw_version"), MG_ESC(MG_MDASH_FIRMWARE_VERSION), MG_ESC("uptime"),
(uint64_t) (mg_millis() / 1000), MG_ESC("reboot_reason"),
MG_ESC(mg_health_reason_str(mg_health_reason())), MG_ESC("arch"), MG_ARCH,
MG_ESC("health"), MG_ESC("encoding"), MG_ESC("base64"), MG_ESC("size"),
(uint64_t) blob.len, MG_ESC("data"), mg_print_base64, (int) blob.len,
(uint8_t *) (blob.buf == NULL ? "" : blob.buf));
}
static void mg_mdash_rpc_ota_begin(struct mg_rpc_req *r) {
size_t total = (size_t) mg_json_get_long(r->frame, "$.params.size", -1);
if (mg_ota_begin(total)) {
mg_rpc_ok(r, "true");
} else {
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
}
}
static void mg_mdash_rpc_ota_write(struct mg_rpc_req *r) {
int len = 0, ofs = 0;
if ((ofs = mg_json_get(r->frame, "$.params.data", &len)) <= 0 ||
r->frame.buf[ofs] != '"') {
mg_rpc_err(r, 500, "%m", MG_ESC("data required"));
} else {
struct mg_str s = mg_str_n(&r->frame.buf[ofs] + 1, (size_t) (len - 2));
size_t n = mg_base64_decode(s.buf, s.len, s.buf, s.len); // Decode in-place
if (n == 0) {
mg_rpc_ok(r, "%m", MG_ESC("finished"));
mg_ota_end();
} else if (mg_ota_write(s.buf, n)) {
mg_rpc_ok(r, "{%m:%d}", MG_ESC("queued"), n);
} else {
mg_rpc_err(r, 500, "%m", MG_ESC("write failed"));
mg_ota_end();
}
// mDashNotify("State.Set", "{%m:{%m:%d}}", MG_ESC("ota"),
// MG_ESC("percent"), (int) (s_ota_written * 100 / s_ota_size));
}
}
static void mg_mdash_rpc_ota_end(struct mg_rpc_req *r) {
if (mg_ota_end()) {
mg_rpc_ok(r, "true");
} else {
mg_rpc_err(r, 500, "%m", MG_ESC("failed"));
}
// mDashNotify("State.Set", "{%m:{%m:null}}", MG_ESC("ota"),
// MG_ESC("percent"));
}
void mg_mdash_init(struct mg_mgr *mgr) {
MG_INFO(("Initialising mdash agent, key [%s]", MG_MDASH_KEY));
mg_rpc_add(&mgr->rpcs, mg_str("RPC.List"), mg_rpc_list, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("Sys.GetInfo"), mg_mdash_rpc_get_info, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("OTA.Begin"), mg_mdash_rpc_ota_begin, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("OTA.Write"), mg_mdash_rpc_ota_write, NULL);
mg_rpc_add(&mgr->rpcs, mg_str("OTA.End"), mg_mdash_rpc_ota_end, NULL);
}
void mg_mdash_poll(struct mg_mgr *mgr) {
static uint64_t timer = 0;
if (mg_mdash_conn == NULL && mg_timer_expired(&timer, 1000, mg_millis())) {
MG_INFO(("Connecting to %s", MG_MDASH_URL));
mg_mdash_conn = mg_ws_connect(mgr, MG_MDASH_URL, mg_mdash_fn, NULL,
"Origin: %s\r\n"
"Authorization: Bearer %s\r\n",
MG_MDASH_URL, MG_MDASH_KEY);
}
}
#else
void mg_mdash_init(struct mg_mgr *mgr) {
(void) mgr;
}
void mg_mdash_poll(struct mg_mgr *mgr) {
(void) mgr;
}
#endif
+24
View File
@@ -0,0 +1,24 @@
// Cloud connector API to https://mdash.net - device management cloud
#pragma once
#include "net.h"
#ifndef MG_MDASH_URL
#define MG_MDASH_URL "wss://mdash.net/api/v2/rpc"
#endif
#ifndef MG_MDASH_KEY
#define MG_MDASH_KEY ""
#endif
#ifndef MG_MDASH_FIRMWARE_VERSION
#define MG_MDASH_FIRMWARE_VERSION "1.0.0"
#endif
#ifndef MG_ENABLE_MDASH
#define MG_ENABLE_MDASH 0
#endif
void mg_mdash_init(struct mg_mgr *);
void mg_mdash_poll(struct mg_mgr *);
+10 -4
View File
@@ -2,7 +2,7 @@
#include "dns.h" #include "dns.h"
#include "fmt.h" #include "fmt.h"
#include "log.h" #include "log.h"
#include "ota.h" #include "mdash.h"
#include "printf.h" #include "printf.h"
#include "profile.h" #include "profile.h"
#include "timer.h" #include "timer.h"
@@ -163,10 +163,13 @@ static bool mg_aton6(struct mg_str str, struct mg_addr *addr) {
return true; return true;
} }
bool mg_aton_(struct mg_str str, struct mg_addr *addr);
bool mg_aton_(struct mg_str str, struct mg_addr *addr) {
return mg_aton4(str, addr) || mg_aton6(str, addr);
}
bool mg_aton(struct mg_str str, struct mg_addr *addr) { bool mg_aton(struct mg_str str, struct mg_addr *addr) {
// MG_INFO(("[%.*s]", (int) str.len, str.buf)); return mg_atone(str, addr) || mg_atonl(str, addr) || mg_aton_(str, addr);
return mg_atone(str, addr) || mg_atonl(str, addr) || mg_aton4(str, addr) ||
mg_aton6(str, addr);
} }
struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) { struct mg_connection *mg_alloc_conn(struct mg_mgr *mgr) {
@@ -347,6 +350,9 @@ void mg_mgr_init(struct mg_mgr *mgr) {
: MG_TLS == MG_TLS_BUILTIN ? "builtin" : MG_TLS == MG_TLS_BUILTIN ? "builtin"
: MG_TLS == MG_TLS_WOLFSSL ? "WolfSSL" : MG_TLS == MG_TLS_WOLFSSL ? "WolfSSL"
: "custom")); : "custom"));
#if MG_ENABLE_MDASH
mg_mdash_init(mgr);
#endif
} }
#if MG_ENABLE_TCPIP #if MG_ENABLE_TCPIP
+1
View File
@@ -50,6 +50,7 @@ struct mg_mgr {
struct mg_tcpip_if *ifp; // Builtin TCP/IP stack: network interface pointer struct mg_tcpip_if *ifp; // Builtin TCP/IP stack: network interface pointer
size_t extraconnsize; // Builtin TCP/IP stack: extra bytes allocated per connection size_t extraconnsize; // Builtin TCP/IP stack: extra bytes allocated per connection
union mg_pipe pipe; // Socketpair write-end / queue, used by mg_wakeup() union mg_pipe pipe; // Socketpair write-end / queue, used by mg_wakeup()
struct mg_rpc *rpcs; // Linked list of registered RPC handlers
#if MG_ENABLE_FREERTOS_TCP #if MG_ENABLE_FREERTOS_TCP
SocketSet_t ss; // FreeRTOS-TCP socket set SocketSet_t ss; // FreeRTOS-TCP socket set
#endif #endif
+215 -71
View File
@@ -13,17 +13,32 @@
#define MG_TCPIP_ARP_MS 100 // Timeout for ARP response #define MG_TCPIP_ARP_MS 100 // Timeout for ARP response
#define MG_TCPIP_SYN_MS 15000 // Timeout for connection establishment #define MG_TCPIP_SYN_MS 15000 // Timeout for connection establishment
#define MG_TCPIP_FIN_MS 1000 // Timeout for closing connection #define MG_TCPIP_FIN_MS 1000 // Timeout for closing connection
#define MG_TCPIP_RTX_MS 1000 // Initial retransmission timeout (RFC-6298, 2.1)
#define MG_TCPIP_RTX_MAX_MS 60000 // Minimum permitted RTO cap (RFC-6298, 2.5)
#define MG_TCPIP_RTX_R1 3 // Delivery-problem threshold (RFC-9293, 3.8.3)
// No R2: MG_TCPIP_KEEPALIVE_MS eventually closes non-responsive peers
#ifndef MG_TCPIP_WIN #ifndef MG_TCPIP_WIN
#define MG_TCPIP_WIN 6000 // TCP window size #define MG_TCPIP_WIN 6000 // TCP window size
#endif #endif
#ifndef MG_TCPIP_MIN_MSS
#define MG_TCPIP_MIN_MSS 64 // Minimum peer MSS
#endif
#ifndef MG_TCPIP_TXQ_ALIGN
#define MG_TCPIP_TXQ_ALIGN 1024 // Retransmit queue allocation quantum
#endif
struct connstate { struct connstate {
uint32_t seq, ack; // TCP seq/ack counters uint32_t seq, ack; // TCP seq/ack counters
uint64_t timer; // TCP timer (see 'ttype' below) uint64_t timer; // TCP timer (see 'ttype' below)
uint64_t txq_timer; // TCP retransmission timer (RFC-6298, 5)
uint32_t acked; // Last ACK-ed number uint32_t acked; // Last ACK-ed number
size_t unacked; // Not acked bytes size_t unacked; // Not acked bytes
uint32_t maxseq; // Max send seq (ack + window) uint32_t maxseq; // Max send seq (ack + window)
uint32_t txq_seq; // Sequence of first txq record (RFC-9293, 3.8)
uint32_t txq_una; // Oldest unacknowledged sequence (RFC-9293, 3.4)
uint16_t win; // destination current window size uint16_t win; // destination current window size
uint16_t dmss; // destination MSS (from TCP opts) uint16_t dmss; // destination MSS (from TCP opts)
uint8_t mac[sizeof(struct mg_l2addr)]; // Peer hw address uint8_t mac[sizeof(struct mg_l2addr)]; // Peer hw address
@@ -34,8 +49,12 @@ struct connstate {
#define MIP_TTYPE_SYN 3 // SYN sent, waiting for response #define MIP_TTYPE_SYN 3 // SYN sent, waiting for response
#define MIP_TTYPE_FIN 4 // FIN sent, waiting until terminating the connection #define MIP_TTYPE_FIN 4 // FIN sent, waiting until terminating the connection
uint8_t tmiss; // Number of keep-alive misses uint8_t tmiss; // Number of keep-alive misses
uint8_t txq_retries; // Retransmissions since ACK progress (RFC-9293, 3.8.3)
bool fin_rcvd; // We have received FIN from the peer bool fin_rcvd; // We have received FIN from the peer
bool twclosure; // 3-way closure done bool twclosure; // 3-way closure done
bool retransmit; // Retain sent data until acknowledged
bool is_full; // Advertise a zero receive window
struct mg_iobuf txq; // Length-prefixed sent TCP segments (RFC-9293, 3.8)
}; };
#if defined(__DCC__) #if defined(__DCC__)
@@ -292,7 +311,7 @@ static bool tcpcsum_ok(void *d, void *t) {
} }
#if MG_ENABLE_IPV6 #if MG_ENABLE_IPV6
static uint16_t p6csum(void *d, void *p, size_t plen) { static uint16_t p6csum(void *d, void *p, uint8_t proto, size_t plen) {
uint32_t sum; uint32_t sum;
struct ip6 *ip6 = (struct ip6 *) d; struct ip6 *ip6 = (struct ip6 *) d;
#if defined(__DCC__) #if defined(__DCC__)
@@ -303,7 +322,7 @@ static uint16_t p6csum(void *d, void *p, size_t plen) {
pip6.dst[0] = ip6->dst[0], pip6.dst[1] = ip6->dst[1]; pip6.dst[0] = ip6->dst[0], pip6.dst[1] = ip6->dst[1];
pip6.zero[0] = 0, pip6.zero[1] = 0, pip6.zero[2] = 0; pip6.zero[0] = 0, pip6.zero[1] = 0, pip6.zero[2] = 0;
pip6.plen = mg_htonl((uint32_t) plen); pip6.plen = mg_htonl((uint32_t) plen);
pip6.next = ip6->next; pip6.next = proto;
sum = csumup(0, &pip6, sizeof(pip6)); // even length sum = csumup(0, &pip6, sizeof(pip6)); // even length
sum = csumup(sum, p, plen); // possibly odd length: last sum = csumup(sum, p, plen); // possibly odd length: last
return csumfin(sum); return csumfin(sum);
@@ -313,17 +332,15 @@ static bool udp6csum_ok(void *d, void *u) {
struct udp *udp = (struct udp *) u; struct udp *udp = (struct udp *) u;
if (udp->csum == 0) return false; // mandatory in IPv6 if (udp->csum == 0) return false; // mandatory in IPv6
if (udp->csum == 0xFFFF) udp->csum = 0; if (udp->csum == 0xFFFF) udp->csum = 0;
return (p6csum(d, u, (size_t) mg_ntohs(udp->len)) == 0); return (p6csum(d, u, 17, (size_t) mg_ntohs(udp->len)) == 0);
} }
static bool tcp6csum_ok(void *d, void *t) { static bool tcp6csum_ok(void *d, void *t, size_t len) {
struct ip6 *ip6 = (struct ip6 *) d; return p6csum(d, t, 6, len) == 0;
return (p6csum(d, t, (size_t) mg_ntohs(ip6->plen)) == 0);
}
static bool icmp6csum_ok(void *d, void *i) {
struct ip6 *ip6 = (struct ip6 *) d;
return (p6csum(d, i, (size_t) mg_ntohs(ip6->plen)) == 0);
} }
static bool icmp6csum_ok(void *d, void *i, size_t len) {
return p6csum(d, i, 58, len) == 0;
}
static void ip6sn(uint64_t *addr, uint64_t *sn_addr) { static void ip6sn(uint64_t *addr, uint64_t *sn_addr) {
// Build solicited-node multicast address from a given unicast IP // Build solicited-node multicast address from a given unicast IP
// RFC-4291 2.7 // RFC-4291 2.7
@@ -468,7 +485,7 @@ static bool tx_udp(struct mg_tcpip_if *ifp, uint8_t *l2_dst,
memmove(udp + 1, buf, len); memmove(udp + 1, buf, len);
#if MG_ENABLE_IPV6 #if MG_ENABLE_IPV6
if (ip_dst->is_ip6) { if (ip_dst->is_ip6) {
udp->csum = p6csum(ip6, udp, sizeof(*udp) + len); udp->csum = p6csum(ip6, udp, 17, sizeof(*udp) + len);
} else } else
#endif #endif
{ {
@@ -816,7 +833,7 @@ static void tx_icmp6(struct mg_tcpip_if *ifp, uint8_t *l2_dst, uint64_t *ip_src,
icmp6->code = code; icmp6->code = code;
memcpy(icmp6 + 1, buf, len); // Copy payload memcpy(icmp6 + 1, buf, len); // Copy payload
icmp6->csum = 0; // RFC-4443 2.3, RFC-8200 8.1 icmp6->csum = 0; // RFC-4443 2.3, RFC-8200 8.1
icmp6->csum = p6csum(ip6, icmp6, sizeof(*icmp6) + len); icmp6->csum = p6csum(ip6, icmp6, 58, sizeof(*icmp6) + len);
driver_output(ifp, driver_output(ifp,
mg_l2_trailer(ifp, sizeof(*ip6) + sizeof(*icmp6) + len, mg_l2_trailer(ifp, sizeof(*ip6) + sizeof(*icmp6) + len,
(uint8_t *) (ip6 + 1) + sizeof(*icmp6) + len)); (uint8_t *) (ip6 + 1) + sizeof(*icmp6) + len));
@@ -1055,7 +1072,8 @@ static void rx_ndp_ra(struct mg_tcpip_if *ifp, struct pkt *pkt) {
} }
static void rx_icmp6(struct mg_tcpip_if *ifp, struct pkt *pkt) { static void rx_icmp6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
if (!icmp6csum_ok(pkt->ip6, pkt->icmp6)) return; if (!icmp6csum_ok(pkt->ip6, pkt->icmp6, sizeof(*pkt->icmp6) + pkt->pay.len))
return;
switch (pkt->icmp6->type) { switch (pkt->icmp6->type) {
case 128: { // Echo Request, RFC-4443 4.1 case 128: { // Echo Request, RFC-4443 4.1
uint64_t target[2]; uint64_t target[2];
@@ -1211,7 +1229,7 @@ static bool rx_udp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *l2_dst, static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *l2_dst,
struct mg_addr *ip_src, struct mg_addr *ip_dst, struct mg_addr *ip_src, struct mg_addr *ip_dst,
uint8_t dscp, uint8_t flags, uint32_t seq, uint32_t ack, uint8_t dscp, uint8_t flags, uint32_t seq, uint32_t ack,
const void *buf, size_t len) { uint16_t win, const void *buf, size_t len) {
uint8_t *l3p; uint8_t *l3p;
struct ip *ip = NULL; struct ip *ip = NULL;
struct tcp *tcp; struct tcp *tcp;
@@ -1256,11 +1274,11 @@ static size_t tx_tcp(struct mg_tcpip_if *ifp, uint8_t *l2_dst,
tcp->seq = seq; tcp->seq = seq;
tcp->ack = ack; tcp->ack = ack;
tcp->flags = flags; tcp->flags = flags;
tcp->win = mg_htons(MG_TCPIP_WIN); tcp->win = mg_htons(win);
tcp->off = (uint8_t) (hlen / 4 << 4); tcp->off = (uint8_t) (hlen / 4 << 4);
#if MG_ENABLE_IPV6 #if MG_ENABLE_IPV6
if (ip_dst->is_ip6) { if (ip_dst->is_ip6) {
tcp->csum = p6csum(ip6, tcp, hlen + len); tcp->csum = p6csum(ip6, tcp, 6, hlen + len);
} else } else
#endif #endif
{ {
@@ -1294,7 +1312,8 @@ static size_t tx_tcp_ctrlresp(struct mg_tcpip_if *ifp, struct pkt *pkt,
ipd.port = pkt->tcp->sport; ipd.port = pkt->tcp->sport;
if ((l2addr = get_return_l2addr(ifp, &ipd, false, pkt)) == NULL) if ((l2addr = get_return_l2addr(ifp, &ipd, false, pkt)) == NULL)
return 0; // safety net for lousy networks return 0; // safety net for lousy networks
return tx_tcp(ifp, l2addr, &ips, &ipd, 0, flags, seqno, ackno, NULL, 0); return tx_tcp(ifp, l2addr, &ips, &ipd, 0, flags, seqno, ackno, MG_TCPIP_WIN,
NULL, 0);
} }
static size_t tx_tcp_rst(struct mg_tcpip_if *ifp, struct pkt *pkt, bool toack) { static size_t tx_tcp_rst(struct mg_tcpip_if *ifp, struct pkt *pkt, bool toack) {
@@ -1313,6 +1332,8 @@ static struct mg_connection *accept_conn(struct mg_connection *lsn,
return NULL; return NULL;
} }
s = (struct connstate *) (c + 1); s = (struct connstate *) (c + 1);
s->retransmit = lsn->mgr->ifp->enable_tcp_retransmit;
s->txq.align = MG_TCPIP_TXQ_ALIGN;
s->dmss = mss; // from options in client SYN s->dmss = mss; // from options in client SYN
s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq); s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq);
s->win = mg_ntohs(pkt->tcp->win), s->maxseq = (uint32_t) (s->seq + s->win); s->win = mg_ntohs(pkt->tcp->win), s->maxseq = (uint32_t) (s->seq + s->win);
@@ -1403,6 +1424,26 @@ static bool udp_send(struct mg_connection *c, const void *buf, size_t len) {
return tx_udp(ifp, s->mac, &ips, &c->rem, c->dscp, buf, len); return tx_udp(ifp, s->mac, &ips, &c->rem, c->dscp, buf, len);
} }
static size_t txq_next(struct connstate *s, uint8_t **buf) {
uint32_t len;
if (s->txq.len == 0) return 0;
assert(s->txq.len >= sizeof(len));
memcpy(&len, s->txq.buf, sizeof(len));
assert(len > 0 && len <= s->txq.len - sizeof(len));
if (buf != NULL) *buf = s->txq.buf + sizeof(len);
return len;
}
static bool txq_add(struct connstate *s, const void *buf, size_t len) {
uint32_t n = (uint32_t) len;
size_t off = s->txq.len, total = sizeof(n) + len;
assert(len > 0);
if (mg_iobuf_add(&s->txq, off, NULL, total) != total) return false;
memcpy(s->txq.buf + off, &n, sizeof(n));
memcpy(s->txq.buf + off + sizeof(n), buf, len);
return true;
}
long mg_io_send(struct mg_connection *c, const void *buf, size_t len) { long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
struct connstate *s = (struct connstate *) (c + 1); struct connstate *s = (struct connstate *) (c + 1);
len = trim_len(c, len); len = trim_len(c, len);
@@ -1412,19 +1453,35 @@ long mg_io_send(struct mg_connection *c, const void *buf, size_t len) {
struct mg_tcpip_if *ifp = c->mgr->ifp; struct mg_tcpip_if *ifp = c->mgr->ifp;
size_t sent; size_t sent;
uint32_t room = s->maxseq - s->seq; uint32_t room = s->maxseq - s->seq;
if (room == 0) return MG_IO_WAIT; bool was_empty = s->txq.len == 0;
if (len > s->dmss) len = s->dmss; // RFC-6691: reduce if sending opts if (s->retransmit) {
if ((uint32_t) len > room) len = room; uint32_t in_flight = s->txq.len > 0 ? s->seq - s->txq_una : 0;
sent = tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_PUSH | TH_ACK, uint32_t qroom = in_flight < MG_TCPIP_WIN ? MG_TCPIP_WIN - in_flight : 0;
mg_htonl(s->seq), mg_htonl(s->ack), buf, len); room = s->win > in_flight ? s->win - in_flight : 0;
if (sent == 0) { if (room > qroom) room = qroom;
return MG_IO_WAIT;
} else if (sent == (size_t) -1) {
return MG_IO_ERR;
} else {
s->seq += (uint32_t) len;
if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE);
} }
if (room == 0) return MG_IO_WAIT;
if (len > s->dmss) len = s->dmss; // RFC-6691, reduce if sending opts
if (len > room) len = room;
if (s->retransmit) {
if (was_empty) s->txq_seq = s->txq_una = s->seq;
if (!txq_add(s, buf, len)) return MG_IO_WAIT;
}
sent = tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_PUSH | TH_ACK,
mg_htonl(s->seq), mg_htonl(s->ack),
s->is_full ? 0 : MG_TCPIP_WIN, buf, len);
if (sent == 0 || sent == (size_t) -1) {
if (s->retransmit)
mg_iobuf_del(&s->txq, s->txq.len - len - sizeof(uint32_t),
len + sizeof(uint32_t));
return sent == 0 ? MG_IO_WAIT : MG_IO_ERR;
}
s->seq += (uint32_t) len;
if (s->retransmit && was_empty) {
s->txq_timer = ifp->now + MG_TCPIP_RTX_MS;
s->txq_retries = 0;
}
if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE);
} }
return (long) len; return (long) len;
} }
@@ -1452,22 +1509,66 @@ static void handle_tls_recv(struct mg_connection *c) {
} }
} }
static void handle_ack(struct connstate *s, uint32_t ackno, uint16_t win) { static void handle_ack(struct connstate *s, uint32_t ackno, uint16_t win,
if (ackno < (s->seq - s->win) || ackno > s->seq) return; uint64_t now) {
if (ackno > s->seq) return;
if (s->retransmit && s->txq.len > 0) {
if (ackno < s->txq_una) return;
if (ackno > s->txq_una) {
size_t len;
// Remove only entirely acknowledged segments (RFC-9293, 3.10.7.4)
while ((len = txq_next(s, NULL)) > 0 && ackno >= s->txq_seq + len) {
mg_iobuf_del(&s->txq, 0, len + sizeof(uint32_t));
s->txq_seq += (uint32_t) len;
}
s->txq_una = ackno;
s->txq_retries = 0;
// Restart RTO when an ACK acknowledges new data (RFC-6298, 5.3)
s->txq_timer = now + MG_TCPIP_RTX_MS;
} // else: dup ack, ignore it but handle its window upddate
} else if (ackno < (s->seq - s->win)) {
return;
}
s->maxseq = (uint32_t) (ackno + win); s->maxseq = (uint32_t) (ackno + win);
s->win = win; s->win = win;
} }
static void retransmit(struct mg_connection *c) {
struct connstate *s = (struct connstate *) (c + 1);
struct mg_tcpip_if *ifp = c->mgr->ifp;
uint8_t *buf, i;
uint64_t rto;
size_t len = txq_next(s, &buf);
// Send the front retransmission-queue segment only (RFC-9293, 3.10.8)
if (len == 0 ||
tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_PUSH | TH_ACK,
mg_htonl(s->txq_seq), mg_htonl(s->ack),
s->is_full ? 0 : MG_TCPIP_WIN, buf, len) == (size_t) -1) {
mg_error(c, "retransmit");
return;
}
// Back off RTO after every timeout (RFC-6298, 5.5)
for (i = 0, rto = MG_TCPIP_RTX_MS;
i < s->txq_retries && rto < MG_TCPIP_RTX_MAX_MS; i++) {
rto *= 2;
if (rto > MG_TCPIP_RTX_MAX_MS) rto = MG_TCPIP_RTX_MAX_MS;
}
s->txq_timer = ifp->now + rto;
}
static void read_conn(struct mg_connection *c, struct pkt *pkt) { static void read_conn(struct mg_connection *c, struct pkt *pkt) {
struct connstate *s = (struct connstate *) (c + 1); struct connstate *s = (struct connstate *) (c + 1);
struct mg_iobuf *io = c->is_tls ? &c->rtls : &c->recv; struct mg_iobuf *io = c->is_tls ? &c->rtls : &c->recv;
uint32_t seq = mg_ntohl(pkt->tcp->seq); uint32_t seq = mg_ntohl(pkt->tcp->seq);
// Evaluation order is important: 1) process FIN, 2) process keep-alive,
// 3) discard invalid SEG.SEQ, 4) process ACK and payload.
if (pkt->tcp->flags & TH_FIN) { if (pkt->tcp->flags & TH_FIN) {
uint8_t flags = TH_ACK; uint8_t flags = TH_ACK;
if (mg_ntohl(pkt->tcp->seq) != s->ack) { if (mg_ntohl(pkt->tcp->seq) != s->ack) {
MG_VERBOSE(("ignoring FIN, %x != %x", mg_ntohl(pkt->tcp->seq), s->ack)); MG_VERBOSE(("ignoring FIN, %x != %x", mg_ntohl(pkt->tcp->seq), s->ack));
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK, tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK,
mg_htonl(s->seq), mg_htonl(s->ack), "", 0); mg_htonl(s->seq), mg_htonl(s->ack), s->is_full ? 0 : MG_TCPIP_WIN,
"", 0);
return; return;
} }
// If we initiated the closure, we reply with ACK upon receiving FIN // If we initiated the closure, we reply with ACK upon receiving FIN
@@ -1489,36 +1590,40 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) {
c->is_draining = 1; c->is_draining = 1;
} }
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, flags, tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, flags,
mg_htonl(s->seq), mg_htonl(s->ack), "", 0); mg_htonl(s->seq), mg_htonl(s->ack), s->is_full ? 0 : MG_TCPIP_WIN,
if (pkt->pay.len == 0) return; // if no data, we're done "", 0);
} else if (pkt->pay.len <= 1 && mg_ntohl(pkt->tcp->seq) == s->ack - 1) { } else if (pkt->pay.len <= 1 && mg_ntohl(pkt->tcp->seq) == s->ack - 1) {
// Keep-Alive (RFC-9293 3.8.4, allow erroneous implementations) // Keep-Alive (RFC-9293 3.8.4, allow erroneous implementations)
MG_VERBOSE(("%lu keepalive ACK", c->id)); MG_VERBOSE(("%lu keepalive ACK", c->id));
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK, tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK,
mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0); mg_htonl(s->seq), mg_htonl(s->ack), s->is_full ? 0 : MG_TCPIP_WIN,
return; // no data to process NULL, 0);
} else if (pkt->pay.len == 0) { // this is an ACK return; // RFC-9293 3.10.7.4 discard (incorrect) payload, ACK, window
if (pkt->tcp->flags & TH_ACK)
handle_ack(s, mg_ntohl(pkt->tcp->ack), mg_ntohs(pkt->tcp->win));
if (s->fin_rcvd && s->ttype == MIP_TTYPE_FIN) s->twclosure = true;
return; // no data to process
} else if (seq != s->ack) { } else if (seq != s->ack) {
uint32_t ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len); uint32_t ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
if (s->ack == ack) { if (s->ack == ack) {
MG_VERBOSE(("ignoring duplicate pkt")); MG_VERBOSE(("ignoring duplicate pkt"));
} else { } else {
MG_VERBOSE(("SEQ != ACK: %x %x %x", seq, s->ack, ack)); MG_VERBOSE(("SEQ != ACK: %x %x %x", seq, s->ack, ack));
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK,
mg_htonl(s->seq), mg_htonl(s->ack), "", 0);
} }
return; // drop it tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK,
} else if (io->size - io->len < pkt->pay.len && mg_htonl(s->seq), mg_htonl(s->ack), s->is_full ? 0 : MG_TCPIP_WIN,
!mg_iobuf_resize(io, io->len + pkt->pay.len)) { "", 0);
return; // drop it, RFC-9293 3.10.7.4: ignore ACKno
}
// Now process the segment for ACK and payload
if (pkt->tcp->flags & TH_ACK) {
handle_ack(s, mg_ntohl(pkt->tcp->ack), mg_ntohs(pkt->tcp->win),
c->mgr->ifp->now);
if (pkt->pay.len == 0 && s->fin_rcvd && s->ttype == MIP_TTYPE_FIN)
s->twclosure = true;
}
if (pkt->pay.len == 0) return;
if (io->size - io->len < pkt->pay.len &&
!mg_iobuf_resize(io, io->len + pkt->pay.len)) {
mg_error(c, "oom"); mg_error(c, "oom");
return; // drop it return; // drop it
} }
if (pkt->tcp->flags & TH_ACK)
handle_ack(s, mg_ntohl(pkt->tcp->ack), mg_ntohs(pkt->tcp->win));
// Copy TCP payload into the IO buffer. If the connection is plain text, // Copy TCP payload into the IO buffer. If the connection is plain text,
// we copy to c->recv. If the connection is TLS, this data is encrypted, // we copy to c->recv. If the connection is TLS, this data is encrypted,
// therefore we copy that encrypted data to the c->rtls iobuffer instead, // therefore we copy that encrypted data to the c->rtls iobuffer instead,
@@ -1527,15 +1632,18 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) {
memcpy(&io->buf[io->len], pkt->pay.buf, pkt->pay.len); memcpy(&io->buf[io->len], pkt->pay.buf, pkt->pay.len);
io->len += pkt->pay.len; io->len += pkt->pay.len;
MG_VERBOSE(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq), s->ack)); MG_VERBOSE(("%lu SEQ %x -> %x", c->id, mg_htonl(pkt->tcp->seq), s->ack));
// Advance ACK counter if (!(pkt->tcp->flags & TH_FIN)) { // FIN already advanced s->ack
s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len); // Advance ACK counter
s->unacked += pkt->pay.len; s->ack = (uint32_t) (mg_htonl(pkt->tcp->seq) + pkt->pay.len);
s->unacked += pkt->pay.len;
}
// size_t diff = s->acked <= s->ack ? s->ack - s->acked : s->ack; // size_t diff = s->acked <= s->ack ? s->ack - s->acked : s->ack;
if (s->unacked > MG_TCPIP_WIN / 2 && s->acked != s->ack) { if (s->unacked > MG_TCPIP_WIN / 2 && s->acked != s->ack) {
// Send ACK immediately // Send ACK immediately
MG_VERBOSE(("%lu imm ACK %lu", c->id, s->acked)); MG_VERBOSE(("%lu imm ACK %lu", c->id, s->acked));
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK, tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK,
mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0); mg_htonl(s->seq), mg_htonl(s->ack), s->is_full ? 0 : MG_TCPIP_WIN,
NULL, 0);
s->unacked = 0; s->unacked = 0;
s->acked = s->ack; s->acked = s->ack;
if (s->ttype != MIP_TTYPE_KEEPALIVE) settmout(c, MIP_TTYPE_KEEPALIVE); if (s->ttype != MIP_TTYPE_KEEPALIVE) settmout(c, MIP_TTYPE_KEEPALIVE);
@@ -1612,8 +1720,11 @@ static bool handle_opt(struct connstate *s, struct tcp *tcp, bool ip6) {
if (kind == 0) break; // End of Option List if (kind == 0) break; // End of Option List
if (len < 2 || opts[1] == 0 || opts[1] > len) return false; // Malformed if (len < 2 || opts[1] == 0 || opts[1] > len) return false; // Malformed
optlen = opts[1]; optlen = opts[1];
if (kind == 2 && optlen == 4) // set received MSS if (kind == 2 && optlen == 4) { // set received MSS
s->dmss = (uint16_t) (((uint16_t) opts[2] << 8) + opts[3]); uint16_t mss = (uint16_t) (((uint16_t) opts[2] << 8) + opts[3]);
if (mss == 0 || mss < MG_TCPIP_MIN_MSS) return false;
s->dmss = mss;
}
} }
MG_VERBOSE(("kind: %u, optlen: %u, len: %d\n", kind, optlen, len)); MG_VERBOSE(("kind: %u, optlen: %u, len: %d\n", kind, optlen, len));
opts += optlen; opts += optlen;
@@ -1626,7 +1737,10 @@ static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
struct mg_connection *c = getpeer(ifp->mgr, pkt, false); struct mg_connection *c = getpeer(ifp->mgr, pkt, false);
struct connstate *s = c == NULL ? NULL : (struct connstate *) (c + 1); struct connstate *s = c == NULL ? NULL : (struct connstate *) (c + 1);
#if MG_ENABLE_IPV6 // matching of v4/v6 to dest is done by getpeer() #if MG_ENABLE_IPV6 // matching of v4/v6 to dest is done by getpeer()
if (pkt->ip6 != NULL && !tcp6csum_ok(pkt->ip6, pkt->tcp)) return; if (pkt->ip6 != NULL &&
!tcp6csum_ok(pkt->ip6, pkt->tcp,
PDIFF(pkt->tcp, pkt->pay.buf) + pkt->pay.len))
return;
#endif #endif
if (pkt->ip != NULL && !tcpcsum_ok(pkt->ip, pkt->tcp)) return; if (pkt->ip != NULL && !tcpcsum_ok(pkt->ip, pkt->tcp)) return;
pkt->tcp->flags &= TH_STDFLAGS; // tolerate creative usage (ECN, ?) pkt->tcp->flags &= TH_STDFLAGS; // tolerate creative usage (ECN, ?)
@@ -1657,6 +1771,12 @@ static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) {
settmout(c, settmout(c,
MIP_TTYPE_KEEPALIVE); // unless a former ACK timeout is pending MIP_TTYPE_KEEPALIVE); // unless a former ACK timeout is pending
read_conn(c, pkt); // Override timer with ACK timeout if needed read_conn(c, pkt); // Override timer with ACK timeout if needed
if (c->is_full && !s->is_full) {
// The receive handler paused the connection: advertise zero window now
s->is_full = true;
tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK, mg_htonl(s->seq),
mg_htonl(s->ack), 0, NULL, 0);
}
} else } else
// - we don't listen on that port; RFC-9293 3.5.2 Group 1 // - we don't listen on that port; RFC-9293 3.5.2 Group 1
// - check listening connections; RFC-9293 3.5.2 Group 2 // - check listening connections; RFC-9293 3.5.2 Group 2
@@ -1761,7 +1881,8 @@ static void rx_ip(struct mg_tcpip_if *ifp, struct pkt *pkt) {
pkt->tcp = (struct tcp *) (pkt->pay.buf); pkt->tcp = (struct tcp *) (pkt->pay.buf);
if (pkt->pay.len < sizeof(*pkt->tcp)) return; if (pkt->pay.len < sizeof(*pkt->tcp)) return;
off = pkt->tcp->off >> 4; // account for opts off = pkt->tcp->off >> 4; // account for opts
if (pkt->pay.len < (uint16_t) (4 * off)) return; if (off < sizeof(*pkt->tcp) / 4 || pkt->pay.len < (uint16_t) (4 * off))
return;
mkpay(pkt, (uint32_t *) pkt->tcp + off); mkpay(pkt, (uint32_t *) pkt->tcp + off);
MG_VERBOSE(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src, MG_VERBOSE(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip4, &pkt->ip->src,
mg_ntohs(pkt->tcp->sport), mg_print_ip4, &pkt->ip->dst, mg_ntohs(pkt->tcp->sport), mg_print_ip4, &pkt->ip->dst,
@@ -1857,7 +1978,8 @@ static void rx_ip6(struct mg_tcpip_if *ifp, struct pkt *pkt) {
pkt->tcp = (struct tcp *) (pkt->pay.buf); pkt->tcp = (struct tcp *) (pkt->pay.buf);
if (pkt->pay.len < sizeof(*pkt->tcp)) return; if (pkt->pay.len < sizeof(*pkt->tcp)) return;
off = pkt->tcp->off >> 4; // account for opts off = pkt->tcp->off >> 4; // account for opts
if (pkt->pay.len < (uint16_t) (4 * off)) return; if (off < sizeof(*pkt->tcp) / 4 || pkt->pay.len < (uint16_t) (4 * off))
return;
mkpay(pkt, (uint32_t *) pkt->tcp + off); mkpay(pkt, (uint32_t *) pkt->tcp + off);
MG_DEBUG(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip6, &pkt->ip6->src, MG_DEBUG(("TCP %M:%hu -> %M:%hu len %u", mg_print_ip6, &pkt->ip6->src,
mg_ntohs(pkt->tcp->sport), mg_print_ip6, &pkt->ip6->dst, mg_ntohs(pkt->tcp->sport), mg_print_ip6, &pkt->ip6->dst,
@@ -2056,6 +2178,19 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
struct connstate *s = (struct connstate *) (c + 1); struct connstate *s = (struct connstate *) (c + 1);
if ((c->is_udp && !c->is_arplooking) || c->is_listening || c->is_resolving) if ((c->is_udp && !c->is_arplooking) || c->is_listening || c->is_resolving)
continue; continue;
if (!c->is_full && s->is_full) {
// Application resumed the connection: advertise the restored window
s->is_full = false;
tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK, mg_htonl(s->seq),
mg_htonl(s->ack), MG_TCPIP_WIN, NULL, 0);
}
if (s->retransmit && s->txq.len > 0 && ifp->now > s->txq_timer) {
if (s->txq_retries < 255) s->txq_retries++;
// R1 reports a delivery problem; it does not close (RFC-9293, 3.8.3).
if (s->txq_retries == MG_TCPIP_RTX_R1)
MG_ERROR(("%lu retransmit", c->id));
retransmit(c);
}
if (ifp->now > s->timer) { if (ifp->now > s->timer) {
if (s->ttype == MIP_TTYPE_ARP) { if (s->ttype == MIP_TTYPE_ARP) {
mg_error(c, "ARP timeout"); mg_error(c, "ARP timeout");
@@ -2064,7 +2199,7 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
} else if (s->ttype == MIP_TTYPE_ACK && s->acked != s->ack) { } else if (s->ttype == MIP_TTYPE_ACK && s->acked != s->ack) {
MG_VERBOSE(("%lu ack %x %x", c->id, s->seq, s->ack)); MG_VERBOSE(("%lu ack %x %x", c->id, s->seq, s->ack));
tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK, mg_htonl(s->seq), tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK, mg_htonl(s->seq),
mg_htonl(s->ack), NULL, 0); mg_htonl(s->ack), s->is_full ? 0 : MG_TCPIP_WIN, NULL, 0);
s->acked = s->ack; s->acked = s->ack;
} else if (s->ttype == MIP_TTYPE_SYN) { } else if (s->ttype == MIP_TTYPE_SYN) {
mg_error(c, "Connection timeout"); mg_error(c, "Connection timeout");
@@ -2077,7 +2212,8 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) {
} else { } else {
MG_VERBOSE(("%lu keepalive", c->id)); MG_VERBOSE(("%lu keepalive", c->id));
tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK, tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_ACK,
mg_htonl(s->seq - 1), mg_htonl(s->ack), NULL, 0); mg_htonl(s->seq - 1), mg_htonl(s->ack),
s->is_full ? 0 : MG_TCPIP_WIN, NULL, 0);
} }
} }
@@ -2104,6 +2240,7 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) {
// If L2 address is not set, make a random one; fill MTU // If L2 address is not set, make a random one; fill MTU
mg_l2_init(ifp); mg_l2_init(ifp);
ifp->mtu = ifp->l2mtu; ifp->mtu = ifp->l2mtu;
if (MG_ENABLE_TCPIP_TCPRTX) ifp->enable_tcp_retransmit = true;
if (ifp->dhcp_name[0] == '\0') // If DHCP name is not set, use "mip" if (ifp->dhcp_name[0] == '\0') // If DHCP name is not set, use "mip"
memcpy(ifp->dhcp_name, "mip", 4); memcpy(ifp->dhcp_name, "mip", 4);
@@ -2140,8 +2277,8 @@ void mg_tcpip_free(struct mg_tcpip_if *ifp) {
static void send_syn(struct mg_connection *c) { static void send_syn(struct mg_connection *c) {
struct connstate *s = (struct connstate *) (c + 1); struct connstate *s = (struct connstate *) (c + 1);
uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port)); uint32_t isn = mg_htonl((uint32_t) mg_ntohs(c->loc.port));
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_SYN, isn, 0, NULL, tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_SYN, isn, 0,
0); MG_TCPIP_WIN, NULL, 0);
} }
static void l2addr_resolved(struct mg_connection *c) { static void l2addr_resolved(struct mg_connection *c) {
@@ -2157,6 +2294,9 @@ static void l2addr_resolved(struct mg_connection *c) {
void mg_connect_resolved(struct mg_connection *c) { void mg_connect_resolved(struct mg_connection *c) {
struct mg_tcpip_if *ifp = c->mgr->ifp; struct mg_tcpip_if *ifp = c->mgr->ifp;
uint8_t *l2addr; uint8_t *l2addr;
struct connstate *s = (struct connstate *) (c + 1);
s->retransmit = ifp->enable_tcp_retransmit;
s->txq.align = MG_TCPIP_TXQ_ALIGN;
c->is_resolving = 0; c->is_resolving = 0;
if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE; if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE;
c->loc.port = mg_htons(ifp->eport++); c->loc.port = mg_htons(ifp->eport++);
@@ -2178,7 +2318,6 @@ void mg_connect_resolved(struct mg_connection *c) {
mg_call(c, MG_EV_RESOLVE, NULL); mg_call(c, MG_EV_RESOLVE, NULL);
c->is_connecting = 1; c->is_connecting = 1;
if (c->is_udp && (l2addr = tcpip_mapip(ifp, &c->rem)) != NULL) { if (c->is_udp && (l2addr = tcpip_mapip(ifp, &c->rem)) != NULL) {
struct connstate *s = (struct connstate *) (c + 1);
memcpy(s->mac, l2addr, sizeof(s->mac)); memcpy(s->mac, l2addr, sizeof(s->mac));
l2addr_resolved(c); // broadcast or multicast l2addr_resolved(c); // broadcast or multicast
#if MG_ENABLE_IPV6 #if MG_ENABLE_IPV6
@@ -2193,7 +2332,6 @@ void mg_connect_resolved(struct mg_connection *c) {
settmout(c, MIP_TTYPE_ARP); settmout(c, MIP_TTYPE_ARP);
c->is_arplooking = 1; c->is_arplooking = 1;
} else if (ifp->gw6_ready) { } else if (ifp->gw6_ready) {
struct connstate *s = (struct connstate *) (c + 1);
memcpy(s->mac, ifp->gw6mac, sizeof(s->mac)); memcpy(s->mac, ifp->gw6mac, sizeof(s->mac));
l2addr_resolved(c); l2addr_resolved(c);
} else { } else {
@@ -2210,7 +2348,6 @@ void mg_connect_resolved(struct mg_connection *c) {
settmout(c, MIP_TTYPE_ARP); settmout(c, MIP_TTYPE_ARP);
c->is_arplooking = 1; c->is_arplooking = 1;
} else if (ifp->gw_ready) { } else if (ifp->gw_ready) {
struct connstate *s = (struct connstate *) (c + 1);
memcpy(s->mac, ifp->gwmac, sizeof(s->mac)); memcpy(s->mac, ifp->gwmac, sizeof(s->mac));
l2addr_resolved(c); l2addr_resolved(c);
} else { } else {
@@ -2242,18 +2379,18 @@ static void write_conn(struct mg_connection *c) {
static void init_closure(struct mg_connection *c) { static void init_closure(struct mg_connection *c) {
struct connstate *s = (struct connstate *) (c + 1); struct connstate *s = (struct connstate *) (c + 1);
if (c->is_udp == false && c->is_listening == false && if (c->is_listening == false && c->is_connecting == false) {
c->is_connecting == false) { // For TCP conns,
tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_FIN | TH_ACK, tx_tcp(c->mgr->ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_FIN | TH_ACK,
mg_htonl(s->seq), mg_htonl(s->ack), NULL, 0); mg_htonl(s->seq), mg_htonl(s->ack), s->is_full ? 0 : MG_TCPIP_WIN,
NULL, 0);
settmout(c, MIP_TTYPE_FIN); settmout(c, MIP_TTYPE_FIN);
} }
} }
static void close_conn(struct mg_connection *c) { static void close_conn(struct mg_connection *c) {
struct connstate *s = (struct connstate *) (c + 1); struct connstate *s = (struct connstate *) (c + 1);
mg_iobuf_free(&s->txq);
mg_close_conn(c); mg_close_conn(c);
(void) s;
} }
static bool can_write(struct mg_connection *c) { static bool can_write(struct mg_connection *c) {
@@ -2266,10 +2403,12 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
uint64_t now = mg_millis(); uint64_t now = mg_millis();
mg_timer_poll(&mgr->timers, now); mg_timer_poll(&mgr->timers, now);
mg_ota_poll(mgr); mg_ota_poll(mgr);
mg_mdash_poll(mgr);
if (mgr->ifp == NULL || mgr->ifp->driver == NULL) return; if (mgr->ifp == NULL || mgr->ifp->driver == NULL) return;
mg_tcpip_poll(mgr->ifp, now); mg_tcpip_poll(mgr->ifp, now);
for (c = mgr->conns; c != NULL; c = tmp) { for (c = mgr->conns; c != NULL; c = tmp) {
struct connstate *s = (struct connstate *) (c + 1); struct connstate *s = (struct connstate *) (c + 1);
long flush = 0;
bool is_tls = c->is_tls && !c->is_resolving && !c->is_arplooking && bool is_tls = c->is_tls && !c->is_resolving && !c->is_arplooking &&
!c->is_listening && !c->is_connecting; !c->is_listening && !c->is_connecting;
tmp = c->next; tmp = c->next;
@@ -2282,9 +2421,14 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
if (is_tls && (c->rtls.len > 0 || mg_tls_pending(c) > 0)) if (is_tls && (c->rtls.len > 0 || mg_tls_pending(c) > 0))
c->is_tls_hs ? mg_tls_handshake(c) : handle_tls_recv(c); c->is_tls_hs ? mg_tls_handshake(c) : handle_tls_recv(c);
if (can_write(c)) write_conn(c); if (can_write(c)) write_conn(c);
if (is_tls && c->send.len == 0) mg_tls_flush(c); if (is_tls && c->send.len == 0) flush = mg_tls_flush(c);
if (c->is_draining && c->send.len == 0 && s->ttype != MIP_TTYPE_FIN) if (flush == MG_IO_ERR) mg_error(c, "tx err");
init_closure(c); if (c->is_draining && c->send.len == 0) {
if (c->is_udp) c->is_closing = 1;
if (!c->is_udp && flush == 0 && s->txq.len == 0 &&
s->ttype != MIP_TTYPE_FIN)
init_closure(c);
}
// For non-TLS, close immediately upon completing the 3-way closure // For non-TLS, close immediately upon completing the 3-way closure
// For TLS, handle any pending data (above) until MIP_TTYPE_FIN expires // For TLS, handle any pending data (above) until MIP_TTYPE_FIN expires
if (s->twclosure && if (s->twclosure &&
+2
View File
@@ -59,6 +59,7 @@ struct mg_tcpip_if {
bool enable_req_sntp; // DHCP client requests an SNTP server address bool enable_req_sntp; // DHCP client requests an SNTP server address
bool enable_fcs_check; // Verify and strip FCS from received frames bool enable_fcs_check; // Verify and strip FCS from received frames
bool enable_mac_check; // Drop frames not addressed to this MAC bool enable_mac_check; // Drop frames not addressed to this MAC
bool enable_tcp_retransmit; // Enable optional TCP retransmission queue
bool update_mac_hash_table; // Signal driver to refresh MAC multicast hash table bool update_mac_hash_table; // Signal driver to refresh MAC multicast hash table
bool is_ip_changed; // Set by stack when IP changes; triggers connection restart bool is_ip_changed; // Set by stack when IP changes; triggers connection restart
struct mg_tcpip_driver *driver; // Hardware driver; must be set before mg_tcpip_init() struct mg_tcpip_driver *driver; // Hardware driver; must be set before mg_tcpip_init()
@@ -152,6 +153,7 @@ extern struct mg_tcpip_driver mg_tcpip_driver_cyw;
extern struct mg_tcpip_driver mg_tcpip_driver_nxp_wifi; extern struct mg_tcpip_driver mg_tcpip_driver_nxp_wifi;
extern struct mg_tcpip_driver mg_tcpip_driver_st67w6; extern struct mg_tcpip_driver mg_tcpip_driver_st67w6;
extern struct mg_tcpip_driver mg_tcpip_driver_atcmd; extern struct mg_tcpip_driver mg_tcpip_driver_atcmd;
extern struct mg_tcpip_driver mg_tcpip_driver_netc;
// SPI bus abstraction for SPI-attached network chips (e.g. W5500). // SPI bus abstraction for SPI-attached network chips (e.g. W5500).
// Populate and assign to the driver's driver_data field. // Populate and assign to the driver's driver_data field.
+1
View File
@@ -41,6 +41,7 @@
#define MG_OTA_RT1060 302 // IMXRT1060 #define MG_OTA_RT1060 302 // IMXRT1060
#define MG_OTA_RT1064 303 // IMXRT1064 #define MG_OTA_RT1064 303 // IMXRT1064
#define MG_OTA_RT1170 304 // IMXRT1170 #define MG_OTA_RT1170 304 // IMXRT1170
#define MG_OTA_RT1180 305 // IMXRT1180
#define MG_OTA_MCXN 310 // MCXN947 #define MG_OTA_MCXN 310 // MCXN947
#define MG_OTA_RW612 320 // FRDM-RW612 #define MG_OTA_RW612 320 // FRDM-RW612
#define MG_OTA_FLASH 900 // OTA via internal flash #define MG_OTA_FLASH 900 // OTA via internal flash
+41 -10
View File
@@ -2,7 +2,7 @@
#include "log.h" #include "log.h"
#include "ota.h" #include "ota.h"
#if MG_OTA >= MG_OTA_RT1020 && MG_OTA <= MG_OTA_RT1170 #if MG_OTA >= MG_OTA_RT1020 && MG_OTA <= MG_OTA_RT1180
static bool mg_imxrt_write(void *, const void *, size_t); static bool mg_imxrt_write(void *, const void *, size_t);
static bool mg_imxrt_swap(void); static bool mg_imxrt_swap(void);
@@ -13,9 +13,12 @@ static bool mg_imxrt_swap(void);
#elif MG_OTA == MG_OTA_RT1064 #elif MG_OTA == MG_OTA_RT1064
#define MG_IMXRT_FLASH_START 0x70000000 #define MG_IMXRT_FLASH_START 0x70000000
#define FLEXSPI_NOR_INSTANCE 1 #define FLEXSPI_NOR_INSTANCE 1
#else // RT1170 #elif MG_OTA == MG_OTA_RT1170
#define MG_IMXRT_FLASH_START 0x30000000 #define MG_IMXRT_FLASH_START 0x30000000
#define FLEXSPI_NOR_INSTANCE 1 #define FLEXSPI_NOR_INSTANCE 1
#else // RT1180 FlexSPI2
#define MG_IMXRT_FLASH_START 0x04000000
#define FLEXSPI_NOR_INSTANCE 2
#endif #endif
#if MG_OTA == MG_OTA_RT1050 #if MG_OTA == MG_OTA_RT1050
@@ -324,7 +327,7 @@ struct mg_flexspi_nor_driver_interface {
uint32_t *option); uint32_t *option);
}; };
#else #else
// RT117x support ROM API version 1.7 // RT117x and RT118x
struct mg_flexspi_nor_driver_interface { struct mg_flexspi_nor_driver_interface {
uint32_t version; uint32_t version;
int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config); int (*init)(uint32_t instance, struct mg_flexspi_nor_config *config);
@@ -359,11 +362,16 @@ struct mg_flexspi_nor_driver_interface {
#define flexspi_nor \ #define flexspi_nor \
(*((struct mg_flexspi_nor_driver_interface **) (*(uint32_t *) 0x0020001c + \ (*((struct mg_flexspi_nor_driver_interface **) (*(uint32_t *) 0x0020001c + \
16))) 16)))
#else #elif MG_OTA == MG_OTA_RT1170
#define MG_FLEXSPI_BASE 0x400CC000 #define MG_FLEXSPI_BASE 0x400CC000
#define flexspi_nor \ #define flexspi_nor \
(*((struct mg_flexspi_nor_driver_interface **) (*(uint32_t *) 0x0021001c + \ (*((struct mg_flexspi_nor_driver_interface **) (*(uint32_t *) 0x0021001c + \
12))) 12)))
#else
#define MG_FLEXSPI_BASE 0x445E0000
#define flexspi_nor \
(*((struct mg_flexspi_nor_driver_interface **) (*(uint32_t *) 0x1000001c + \
12)))
#endif #endif
static bool s_flash_irq_disabled; static bool s_flash_irq_disabled;
@@ -408,6 +416,13 @@ static struct mg_flexspi_nor_config default_config = {
#else #else
// Note: this QSPI configuration works for RTs supporting QSPI // Note: this QSPI configuration works for RTs supporting QSPI
// Configuration for QSPI memory // Configuration for QSPI memory
#if MG_OTA == MG_OTA_RT1180
#define MG_FLEXSPI_QSPI_SERIAL_CLK_FREQ 5 // 100MHz
#define MG_FLEXSPI_QSPI_FLASH_SIZE (16 * 1024 * 1024)
#else
#define MG_FLEXSPI_QSPI_SERIAL_CLK_FREQ 7 // 133MHz
#define MG_FLEXSPI_QSPI_FLASH_SIZE (8 * 1024 * 1024)
#endif
static struct mg_flexspi_nor_config default_config = { static struct mg_flexspi_nor_config default_config = {
.memConfig = {.tag = MG_FLEXSPI_CFG_BLK_TAG, .memConfig = {.tag = MG_FLEXSPI_CFG_BLK_TAG,
.version = MG_FLEXSPI_CFG_BLK_VERSION, .version = MG_FLEXSPI_CFG_BLK_VERSION,
@@ -417,8 +432,8 @@ static struct mg_flexspi_nor_config default_config = {
.controllerMiscOption = MG_BIT(4), .controllerMiscOption = MG_BIT(4),
.deviceType = 1, // serial NOR .deviceType = 1, // serial NOR
.sflashPadType = 4, .sflashPadType = 4,
.serialClkFreq = 7, // 133MHz .serialClkFreq = MG_FLEXSPI_QSPI_SERIAL_CLK_FREQ,
.sflashA1Size = 8 * 1024 * 1024, .sflashA1Size = MG_FLEXSPI_QSPI_FLASH_SIZE,
.lookupTable = MG_FLEXSPI_QSPI_LUT}, .lookupTable = MG_FLEXSPI_QSPI_LUT},
.pageSize = 256, .pageSize = 256,
.sectorSize = 4 * 1024, .sectorSize = 4 * 1024,
@@ -453,6 +468,7 @@ MG_IRAM static int flexspi_nor_get_config(
} }
#endif #endif
#if MG_OTA == MG_OTA_RT1020 || MG_OTA == MG_OTA_RT1050
MG_IRAM static void mg_spin(volatile uint32_t count) { MG_IRAM static void mg_spin(volatile uint32_t count) {
while (count--) (void) 0; while (count--) (void) 0;
} }
@@ -461,6 +477,7 @@ MG_IRAM static void flash_wait(void) {
while ((*((volatile uint32_t *) (MG_FLEXSPI_BASE + 0xE0)) & MG_BIT(1)) == 0) while ((*((volatile uint32_t *) (MG_FLEXSPI_BASE + 0xE0)) & MG_BIT(1)) == 0)
mg_spin(1); mg_spin(1);
} }
#endif
MG_IRAM static bool flash_erase(struct mg_flexspi_nor_config *config, MG_IRAM static bool flash_erase(struct mg_flexspi_nor_config *config,
void *addr) { void *addr) {
@@ -509,6 +526,8 @@ MG_IRAM static bool mg_imxrt_write(void *addr, const void *buf, size_t len) {
uint32_t *dst = (uint32_t *) addr; uint32_t *dst = (uint32_t *) addr;
uint32_t *src = (uint32_t *) buf; uint32_t *src = (uint32_t *) buf;
uint32_t *end = (uint32_t *) ((char *) buf + len); uint32_t *end = (uint32_t *) ((char *) buf + len);
char *flash_start = (char *) s_mg_flash_imxrt.start;
char *flash_end = flash_start + s_mg_flash_imxrt.size;
ok = true; ok = true;
while (ok && src < end) { while (ok && src < end) {
@@ -516,20 +535,32 @@ MG_IRAM static bool mg_imxrt_write(void *addr, const void *buf, size_t len) {
ok = false; ok = false;
break; break;
} }
uint32_t status; uint32_t status = 0;
uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_imxrt.start; uint32_t dst_ofs = (uint32_t) dst - (uint32_t) s_mg_flash_imxrt.start;
if ((char *) buf >= (char *) s_mg_flash_imxrt.start) { if ((char *) buf >= flash_start && (char *) buf < flash_end) {
// If we copy from FLASH to FLASH, then we first need to copy the source // If we copy from FLASH to FLASH, then we first need to copy the source
// to RAM // to RAM
size_t tmp_buf_size = s_mg_flash_imxrt.align / sizeof(uint32_t); size_t tmp_buf_size = s_mg_flash_imxrt.align / sizeof(uint32_t);
uint32_t tmp[tmp_buf_size]; uint32_t tmp[tmp_buf_size];
#if MG_OTA == MG_OTA_RT1020 || MG_OTA == MG_OTA_RT1050
// ROM API 1.4 does not provide flexspi_nor_flash_read().
// Manually copy byte by byte into the RAM buffer.
for (size_t i = 0; i < tmp_buf_size; i++) { for (size_t i = 0; i < tmp_buf_size; i++) {
flash_wait(); flash_wait();
tmp[i] = src[i]; tmp[i] = src[i];
} }
status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, config_ptr, #else
(uint32_t) dst_ofs, tmp); // When supported, prefer the read API call.
uint32_t src_ofs =
(uint32_t) src - (uint32_t) s_mg_flash_imxrt.start;
status = flexspi_nor->read(FLEXSPI_NOR_INSTANCE, config_ptr, tmp,
src_ofs, s_mg_flash_imxrt.align);
#endif
if (status == 0) {
status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, config_ptr,
dst_ofs, tmp);
}
} else { } else {
status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, config_ptr, status = flexspi_nor->program(FLEXSPI_NOR_INSTANCE, config_ptr,
(uint32_t) dst_ofs, src); (uint32_t) dst_ofs, src);
+3 -3
View File
@@ -67,10 +67,10 @@ MG_IRAM static void flash_unlock(void) {
} }
} }
MG_IRAM static bool flash_page_start(volatile uint32_t *dst) { MG_IRAM static bool flash_page_start(uint32_t *dst) {
char *base = (char *) s_mg_flash_stm32h7.start, char *base = (char *) s_mg_flash_stm32h7.start,
*end = base + s_mg_flash_stm32h7.size; *end = base + s_mg_flash_stm32h7.size;
volatile char *p = (char *) dst; char *p = (char *) dst;
return p >= base && p < end && ((p - base) % s_mg_flash_stm32h7.secsz) == 0; return p >= base && p < end && ((p - base) % s_mg_flash_stm32h7.secsz) == 0;
} }
@@ -101,7 +101,7 @@ MG_IRAM static uint32_t flash_bank(void *addr) {
// read-while-write, no need to disable IRQs for standalone usage // read-while-write, no need to disable IRQs for standalone usage
MG_IRAM static bool mg_stm32h7_erase(void *addr) { MG_IRAM static bool mg_stm32h7_erase(void *addr) {
bool ok = false; bool ok = false;
if (flash_page_start(addr) == false) { if (flash_page_start((uint32_t *) addr) == false) {
MG_ERROR(("%p is not on a sector boundary", addr)); MG_ERROR(("%p is not on a sector boundary", addr));
} else { } else {
uintptr_t diff = (char *) addr - (char *) s_mg_flash_stm32h7.start; uintptr_t diff = (char *) addr - (char *) s_mg_flash_stm32h7.start;
+3 -3
View File
@@ -34,15 +34,15 @@ char *mg_mprintf(const char *fmt, ...);
size_t mg_queue_printf(struct mg_queue *, const char *fmt, ...); size_t mg_queue_printf(struct mg_queue *, const char *fmt, ...);
// Built-in %M/%m printer functions. Each reads its argument(s) from ap. // Built-in %M/%m printer functions. Each reads its argument(s) from ap.
size_t mg_print_base64(mg_pfn_t, void *arg, va_list *ap); // expects: const void *buf, size_t len size_t mg_print_base64(mg_pfn_t, void *arg, va_list *ap); // expects: int len, uint8_t *buf
size_t mg_print_esc(mg_pfn_t, void *arg, va_list *ap); // expects: int len, const char *str -- use MG_ESC() size_t mg_print_esc(mg_pfn_t, void *arg, va_list *ap); // expects: int len, const char *str -- use MG_ESC()
size_t mg_print_hex(mg_pfn_t, void *arg, va_list *ap); // expects: const void *buf, size_t len size_t mg_print_hex(mg_pfn_t, void *arg, va_list *ap); // expects: int len, uint8_t *buf
size_t mg_print_ip(mg_pfn_t, void *arg, va_list *ap); // expects: const struct mg_addr * size_t mg_print_ip(mg_pfn_t, void *arg, va_list *ap); // expects: const struct mg_addr *
size_t mg_print_ip_port(mg_pfn_t, void *arg, va_list *ap); // expects: const struct mg_addr * size_t mg_print_ip_port(mg_pfn_t, void *arg, va_list *ap); // expects: const struct mg_addr *
size_t mg_print_ip4(mg_pfn_t, void *arg, va_list *ap); // expects: uint32_t *ipv4 size_t mg_print_ip4(mg_pfn_t, void *arg, va_list *ap); // expects: uint32_t *ipv4
size_t mg_print_ip6(mg_pfn_t, void *arg, va_list *ap); // expects: uint8_t[16] ipv6 size_t mg_print_ip6(mg_pfn_t, void *arg, va_list *ap); // expects: uint8_t[16] ipv6
size_t mg_print_mac(mg_pfn_t, void *arg, va_list *ap); // expects: uint8_t[6] mac size_t mg_print_mac(mg_pfn_t, void *arg, va_list *ap); // expects: uint8_t[6] mac
size_t mg_print_ieee64(mg_pfn_t, void *arg, va_list *ap); // expects: uint64_t size_t mg_print_ieee64(mg_pfn_t, void *arg, va_list *ap); // expects: uint8_t[8] ieee64
size_t mg_print_l2addr(mg_pfn_t, void *arg, va_list *ap); // expects: uint8_t l2, uint8_t[n] n-byte l2-dependent address size_t mg_print_l2addr(mg_pfn_t, void *arg, va_list *ap); // expects: uint8_t l2, uint8_t[n] n-byte l2-dependent address
size_t mg_print_html_esc(mg_pfn_t, void *arg, va_list *ap); // expects: int len, const char *str -- use MG_ESC() size_t mg_print_html_esc(mg_pfn_t, void *arg, va_list *ap); // expects: int len, const char *str -- use MG_ESC()
+7 -3
View File
@@ -1,6 +1,6 @@
#include "dns.h"
#include "event.h" #include "event.h"
#include "log.h" #include "log.h"
#include "mdash.h"
#include "net.h" #include "net.h"
#include "printf.h" #include "printf.h"
#include "str.h" #include "str.h"
@@ -768,8 +768,10 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
now = mg_millis(); now = mg_millis();
mg_timer_poll(&mgr->timers, now); mg_timer_poll(&mgr->timers, now);
mg_ota_poll(mgr); mg_ota_poll(mgr);
mg_mdash_poll(mgr);
for (c = mgr->conns; c != NULL; c = tmp) { for (c = mgr->conns; c != NULL; c = tmp) {
long flush = 0;
bool is_resp = c->is_resp; bool is_resp = c->is_resp;
tmp = c->next; tmp = c->next;
mg_call(c, MG_EV_POLL, &now); mg_call(c, MG_EV_POLL, &now);
@@ -791,10 +793,12 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
} else { } else {
if (c->is_readable) read_conn(c); if (c->is_readable) read_conn(c);
if (c->is_writable) write_conn(c); if (c->is_writable) write_conn(c);
if (c->is_tls && !c->is_tls_hs && c->send.len == 0) mg_tls_flush(c); if (c->is_tls && !c->is_tls_hs && c->send.len == 0)
flush = mg_tls_flush(c);
} }
if (c->is_draining && c->send.len == 0) c->is_closing = 1; if (flush == MG_IO_ERR) mg_error(c, "tx err");
if (c->is_draining && c->send.len == 0 && flush == 0) c->is_closing = 1;
if (c->is_closing) close_conn(c); if (c->is_closing) close_conn(c);
} }
} }
+1 -1
View File
@@ -81,7 +81,7 @@ void mg_tls_free(struct mg_connection *);
long mg_tls_send(struct mg_connection *, const void *buf, size_t len); long mg_tls_send(struct mg_connection *, const void *buf, size_t len);
long mg_tls_recv(struct mg_connection *, void *buf, size_t len); long mg_tls_recv(struct mg_connection *, void *buf, size_t len);
size_t mg_tls_pending(struct mg_connection *); size_t mg_tls_pending(struct mg_connection *);
void mg_tls_flush(struct mg_connection *); long mg_tls_flush(struct mg_connection *);
void mg_tls_handshake(struct mg_connection *); void mg_tls_handshake(struct mg_connection *);
void mg_tls_ctx_init(struct mg_mgr *); void mg_tls_ctx_init(struct mg_mgr *);
void mg_tls_ctx_free(struct mg_mgr *); void mg_tls_ctx_free(struct mg_mgr *);
+51 -28
View File
@@ -267,9 +267,14 @@ static void mg_der_debug(struct mg_der_tlv *tlv, int depth) {
// Did we receive a full TLS record in the c->rtls buffer? // Did we receive a full TLS record in the c->rtls buffer?
static bool mg_tls_got_record(struct mg_connection *c) { static bool mg_tls_got_record(struct mg_connection *c) {
return c->rtls.len >= (size_t) TLS_RECHDR_SIZE && uint16_t rl;
c->rtls.len >= if (c->rtls.len < (size_t) TLS_RECHDR_SIZE) return false;
(size_t) (TLS_RECHDR_SIZE + MG_LOAD_BE16(c->rtls.buf + 3)); rl = MG_LOAD_BE16(c->rtls.buf + 3);
if (rl > 16384 + 256) { // TLS 1.3 record max length (RFC-9846 5.1)
mg_error(c, "TLS record too large");
return false;
}
return c->rtls.len >= (size_t) (TLS_RECHDR_SIZE + rl);
} }
// Remove a single TLS record from the recv buffer // Remove a single TLS record from the recv buffer
@@ -1620,8 +1625,9 @@ static int countdots(struct mg_str s) {
static int mg_tls_verify_cert_san(const uint8_t *der, size_t dersz, static int mg_tls_verify_cert_san(const uint8_t *der, size_t dersz,
const char *server_name, const char *server_name,
struct mg_addr *server_ip) { struct mg_addr *ref_ip) {
struct mg_der_tlv root, field, name; struct mg_der_tlv root, field, name;
bool is_addr = ref_ip != NULL;
if (mg_der_parse((uint8_t *) der, dersz, &root) < 0) { if (mg_der_parse((uint8_t *) der, dersz, &root) < 0) {
MG_ERROR(("failed to parse certificate")); MG_ERROR(("failed to parse certificate"));
return -1; return -1;
@@ -1635,20 +1641,24 @@ static int mg_tls_verify_cert_san(const uint8_t *der, size_t dersz,
return -1; return -1;
} }
while (mg_der_next(&field, &name) > 0) { while (mg_der_next(&field, &name) > 0) {
if (name.type == 0x87 && name.len == 4) { // this is an IPv4 address if (is_addr) { // match ref IP to IP and name to DNS name (RFC-9525 6.2)
MG_VERBOSE(("Found SAN, IP: %M", mg_print_ip4, name.value)); if (name.type == 0x87 && name.len == 4) { // this is an IPv4 address
if (!server_ip->is_ip6 && uint32_t cert_ip;
*((uint32_t *) name.value) == server_ip->addr.ip4) MG_VERBOSE(("Found SAN, IP: %M", mg_print_ip4, name.value));
return 1; // and matches the one we're connected to memcpy(&cert_ip, name.value, 4);
if (!ref_ip->is_ip6 && cert_ip == ref_ip->addr.ip4) // RFC-9525 6.4
return 1; // and byte-matches the one we wanted to connected to
}
#if MG_ENABLE_IPV6 #if MG_ENABLE_IPV6
} else if (name.type == 0x87 && name.len == 16) { // is an IPv6 address else if (name.type == 0x87 && name.len == 16) { // is an IPv6 address
MG_VERBOSE(("Found SAN, IPv6: %M", mg_print_ip6, name.value)); MG_VERBOSE(("Found SAN, IPv6: %M", mg_print_ip6, name.value));
if (server_ip->is_ip6 && memcmp(name.value, server_ip->addr.ip6, 16) == 0) if (ref_ip->is_ip6 && memcmp(name.value, ref_ip->addr.ip6, 16) == 0)
return 1; // and matches the one we're connected to return 1; // and byte-matches the one we wanted to connected to
}
#endif #endif
} else { // this is a text SAN } else if (name.type == 0x82) { // this is a DNS name
struct mg_str sn, tn; struct mg_str sn, tn;
MG_VERBOSE(("Found SAN, (%u): %.*s", name.type, name.len, name.value)); MG_VERBOSE(("Found SAN, DNS: %.*s", name.len, name.value));
sn = mg_str(server_name), tn = mg_str_n((char *) name.value, name.len); sn = mg_str(server_name), tn = mg_str_n((char *) name.value, name.len);
if (countdots(sn) == countdots(tn) && mg_match(sn, tn, NULL)) if (countdots(sn) == countdots(tn) && mg_match(sn, tn, NULL))
return 1; // and matches the host name return 1; // and matches the host name
@@ -1735,17 +1745,18 @@ static int mg_tls_verify_cert_cn(struct mg_der_tlv *subj, const char *host) {
return matched; return matched;
} }
// 1: found, 0: not found, < 0: parse error: -1: bundle (unused), -2: name
static int tls_bundle_find(struct tls_data *tls, struct mg_der_tlv *name, static int tls_bundle_find(struct tls_data *tls, struct mg_der_tlv *name,
struct mg_tls_cert *cert) { struct mg_tls_cert *cert) {
size_t i; size_t i;
struct mg_der_tlv v; struct mg_der_tlv v;
struct mg_str *p = tls->ca_bundle_der, tgt; struct mg_str *p = tls->ca_bundle_der, tgt;
if (!mg_der_find_oid(name, (uint8_t *) "\x55\x04\x03", 3, &v)) return false; if (mg_der_find_oid(name, (uint8_t *) "\x55\x04\x03", 3, &v) <= 0) return -2;
tgt = mg_str_n((const char *) v.value, v.len); tgt = mg_str_n((const char *) v.value, v.len);
for (i = 0; i < tls->ca_bundle_len; i++, p++) { for (i = 0; i < tls->ca_bundle_len; i++, p++) {
struct mg_str subj; struct mg_str subj;
if (mg_tls_parse_cert_der(p->buf, p->len, cert) < 0 || if (mg_tls_parse_cert_der(p->buf, p->len, cert) < 0 ||
!mg_der_find_oid(&cert->subj, (uint8_t *) "\x55\x04\x03", 3, &v)) { mg_der_find_oid(&cert->subj, (uint8_t *) "\x55\x04\x03", 3, &v) <= 0) {
MG_ERROR(("failed to parse certificate #%u in bundle", i + 1)); MG_ERROR(("failed to parse certificate #%u in bundle", i + 1));
continue; // skip this certificate but don't halt the process continue; // skip this certificate but don't halt the process
} }
@@ -1756,6 +1767,8 @@ static int tls_bundle_find(struct tls_data *tls, struct mg_der_tlv *name,
return 0; return 0;
} }
bool mg_aton_(struct mg_str str, struct mg_addr *addr);
static int mg_tls_recv_cert(struct mg_connection *c, bool is_client) { static int mg_tls_recv_cert(struct mg_connection *c, bool is_client) {
struct tls_data *tls = (struct tls_data *) c->tls; struct tls_data *tls = (struct tls_data *) c->tls;
unsigned char *recv_buf; unsigned char *recv_buf;
@@ -1842,11 +1855,16 @@ static int mg_tls_recv_cert(struct mg_connection *c, bool is_client) {
if (ci == certs) { if (ci == certs) {
// First certificate in the chain is peer cert, check SAN if requested, // First certificate in the chain is peer cert, check SAN if requested,
// and store public key for further CertVerify step // and store public key for further CertVerify step
if (tls->hostname[0] != '\0' && if (tls->hostname[0] != '\0') {
mg_tls_verify_cert_san(cert, certsz, tls->hostname, &c->rem) <= 0 && struct mg_addr addr; // use aton_ so localhost evals as a name
mg_tls_verify_cert_cn(&ci->subj, tls->hostname) <= 0) { bool is_addr = mg_aton_(mg_str_s(tls->hostname), &addr);
mg_error(c, "failed to verify hostname"); if (mg_tls_verify_cert_san(cert, certsz, tls->hostname,
return -1; is_addr ? &addr : NULL) <= 0 &&
(!is_addr || // DNS CN fallback
mg_tls_verify_cert_cn(&ci->subj, tls->hostname) <= 0)) {
mg_error(c, "failed to verify hostname");
return -1;
}
} }
if (ci->pubkey.len > sizeof(tls->pubkey)) { if (ci->pubkey.len > sizeof(tls->pubkey)) {
mg_error(c, "peer public key too large"); mg_error(c, "peer public key too large");
@@ -1865,7 +1883,7 @@ static int mg_tls_recv_cert(struct mg_connection *c, bool is_client) {
int r; int r;
MG_VERBOSE(("Search current cert in bundle")); MG_VERBOSE(("Search current cert in bundle"));
r = tls_bundle_find(tls, &ci->subj, &ca); // find current cert ci r = tls_bundle_find(tls, &ci->subj, &ca); // find current cert ci
if (r < 0) { if (r == -1) {
mg_error(c, "failed to parse CA bundle"); mg_error(c, "failed to parse CA bundle");
return -1; return -1;
} else if (r > 0 && ca.pubkey.len == ci->pubkey.len && } else if (r > 0 && ca.pubkey.len == ci->pubkey.len &&
@@ -1873,7 +1891,7 @@ static int mg_tls_recv_cert(struct mg_connection *c, bool is_client) {
found_ca = true; found_ca = true;
MG_VERBOSE(("CA serial: %M", mg_print_hex, ca.sn.len, ca.sn.buf)); MG_VERBOSE(("CA serial: %M", mg_print_hex, ca.sn.len, ca.sn.buf));
break; break;
} // else either r = 0 => subj not found or pubkey not matching } // else r = 0 or -2 => subj not found or pubkey not matching
} }
if (certnum == sizeof(certs) / sizeof(certs[0]) - 1) { if (certnum == sizeof(certs) / sizeof(certs[0]) - 1) {
@@ -1887,8 +1905,10 @@ static int mg_tls_recv_cert(struct mg_connection *c, bool is_client) {
MG_VERBOSE(("Search bundle for issuer of last cert in chain")); MG_VERBOSE(("Search bundle for issuer of last cert in chain"));
r = tls_bundle_find(tls, &certs[certnum - 1].issuer, &ca); r = tls_bundle_find(tls, &certs[certnum - 1].issuer, &ca);
if (r <= 0) { if (r <= 0) {
mg_error(c, r < 0 ? "failed to parse CA bundle" mg_error(c, "failed to %s",
: "failed to find issuing CA in bundle"); r == 0 ? "find issuing CA in bundle"
: r == -1 ? "parse CA bundle"
: "parse name OID");
return -1; return -1;
} else if (!ca.is_ca || // candidate issuer found } else if (!ca.is_ca || // candidate issuer found
!mg_tls_verify_cert_signature(&certs[certnum - 1], &ca)) { !mg_tls_verify_cert_signature(&certs[certnum - 1], &ca)) {
@@ -2846,13 +2866,16 @@ size_t mg_tls_pending(struct mg_connection *c) {
return tls != NULL ? tls->recv_len : 0; return tls != NULL ? tls->recv_len : 0;
} }
void mg_tls_flush(struct mg_connection *c) { long mg_tls_flush(struct mg_connection *c) {
struct tls_data *tls = (struct tls_data *) c->tls; struct tls_data *tls = (struct tls_data *) c->tls;
long n; long n = 0;
while (tls->send.len > 0 && while (tls->send.len > 0 &&
(n = mg_io_send(c, tls->send.buf, tls->send.len)) > 0) { (n = mg_io_send(c, tls->send.buf, tls->send.len)) > 0) {
mg_iobuf_del(&tls->send, 0, (size_t) n); mg_iobuf_del(&tls->send, 0, (size_t) n);
} }
if (n == MG_IO_ERR) return n;
c->is_tls_throttled = tls->send.len > 0;
return c->is_tls_throttled ? MG_IO_WAIT : 0;
} }
void mg_tls_ctx_init(struct mg_mgr *mgr) { void mg_tls_ctx_init(struct mg_mgr *mgr) {
+2 -1
View File
@@ -21,8 +21,9 @@ size_t mg_tls_pending(struct mg_connection *c) {
(void) c; (void) c;
return 0; return 0;
} }
void mg_tls_flush(struct mg_connection *c) { long mg_tls_flush(struct mg_connection *c) {
(void) c; (void) c;
return 0;
} }
void mg_tls_ctx_init(struct mg_mgr *mgr) { void mg_tls_ctx_init(struct mg_mgr *mgr) {
(void) mgr; (void) mgr;
+7 -4
View File
@@ -249,17 +249,20 @@ long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
return n; return n;
} }
void mg_tls_flush(struct mg_connection *c) { long mg_tls_flush(struct mg_connection *c) {
struct mg_tls *tls = (struct mg_tls *) c->tls; struct mg_tls *tls = (struct mg_tls *) c->tls;
long n = 0;
if (c->is_tls_throttled && c->is_draining) { if (c->is_tls_throttled && c->is_draining) {
long n = n = mbedtls_ssl_write(&tls->ssl, tls->throttled_buf, tls->throttled_len);
mbedtls_ssl_write(&tls->ssl, tls->throttled_buf, tls->throttled_len);
#if defined(MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) #if defined(MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET)
if (n == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) return; if (n == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) return MG_IO_WAIT;
#endif #endif
c->is_tls_throttled = c->is_tls_throttled =
(n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE); (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE);
if (c->is_tls_throttled) return MG_IO_WAIT;
if (n <= 0) return MG_IO_ERR;
} }
return 0;
} }
void mg_tls_ctx_init(struct mg_mgr *mgr) { void mg_tls_ctx_init(struct mg_mgr *mgr) {
+2 -1
View File
@@ -335,8 +335,9 @@ long mg_tls_send(struct mg_connection *c, const void *buf, size_t len) {
return n; return n;
} }
void mg_tls_flush(struct mg_connection *c) { long mg_tls_flush(struct mg_connection *c) {
(void) c; (void) c;
return 0;
} }
void mg_tls_ctx_init(struct mg_mgr *mgr) { void mg_tls_ctx_init(struct mg_mgr *mgr) {
+6
View File
@@ -182,6 +182,12 @@ static void mg_ws_cb(struct mg_connection *c, int ev, void *ev_data) {
op = msg.flags & 15; op = msg.flags & 15;
// MG_VERBOSE ("fin %d op %d len %d [%.*s]", final, op, // MG_VERBOSE ("fin %d op %d len %d [%.*s]", final, op,
// (int) m.data.len, (int) m.data.len, m.data.buf)); // (int) m.data.len, (int) m.data.len, m.data.buf));
if ((op == WEBSOCKET_OP_CONTINUE && ofs == 0) ||
((op == WEBSOCKET_OP_TEXT || op == WEBSOCKET_OP_BINARY) &&
ofs != 0)) {
mg_error(c, "invalid WS fragment sequence");
return;
}
switch (op) { switch (op) {
case WEBSOCKET_OP_CONTINUE: case WEBSOCKET_OP_CONTINUE:
mg_call(c, MG_EV_WS_CTL, &m); mg_call(c, MG_EV_WS_CTL, &m);
+1 -1
View File
@@ -106,7 +106,7 @@ mongoose.c: Makefile $(wildcard ../src/*.c) $(wildcard ../src/drivers/*.c)
cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/*.c src/drivers/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@ cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#include "mongoose.h"' ; (for F in src/*.c src/drivers/*.c ; do echo; echo '#ifdef MG_ENABLE_LINES'; echo "#line 1 \"$$F\""; echo '#endif'; cat $$F | sed -e 's,#include ".*,,'; done))> $@
mongoose.h: $(HDRS) Makefile mongoose.h: $(HDRS) Makefile
cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/os_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/profile.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_x25519.h src/tls_aes128.h src/tls_uecc.h src/tls_chacha20.h src/tls_rsa.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/modbus.h src/json.h src/jwt.h src/rpc.h src/dash.h src/ota.h src/flash.h src/wifi.h src/l2.h src/net_builtin.h src/bsd.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@ cd .. && (export LC_ALL=C ; cat src/license.h; echo; echo '#ifndef MONGOOSE_H'; echo '#define MONGOOSE_H'; echo; cat src/version.h ; echo; echo '#ifdef __cplusplus'; echo 'extern "C" {'; echo '#endif'; cat src/arch.h src/arch_*.h src/os_*.h src/net_ft.h src/net_lwip.h src/net_rl.h src/config.h src/profile.h src/str.h src/queue.h src/fmt.h src/printf.h src/log.h src/timer.h src/fs.h src/util.h src/url.h src/iobuf.h src/base64.h src/md5.h src/sha1.h src/sha256.h src/event.h src/net.h src/http.h src/ssi.h src/tls.h src/tls_x25519.h src/tls_aes128.h src/tls_uecc.h src/tls_chacha20.h src/tls_rsa.h src/tls_mbed.h src/tls_openssl.h src/ws.h src/sntp.h src/mqtt.h src/dns.h src/modbus.h src/json.h src/jwt.h src/rpc.h src/dash.h src/ota.h src/health.h src/health_cortex.h src/mdash.h src/flash.h src/wifi.h src/l2.h src/net_builtin.h src/bsd.h src/drivers/*.h | sed -e '/keep/! s,#include ".*,,' -e 's,^#pragma once,,'; echo; echo '#ifdef __cplusplus'; echo '}'; echo '#endif'; echo '#endif // MONGOOSE_H')> $@
# Check that all external (exported) symbols have "mg_" prefix # Check that all external (exported) symbols have "mg_" prefix
mg_prefix: mongoose.c mongoose.h mg_prefix: mongoose.c mongoose.h
+1 -1
View File
@@ -15,7 +15,7 @@
#define configUSE_MALLOC_FAILED_HOOK 0 #define configUSE_MALLOC_FAILED_HOOK 0
#define configUSE_COUNTING_SEMAPHORES 1 #define configUSE_COUNTING_SEMAPHORES 1
#define configMINIMAL_STACK_SIZE 128 #define configMINIMAL_STACK_SIZE 128
#define configTOTAL_HEAP_SIZE (1024 * 128) #define configTOTAL_HEAP_SIZE (1024 * 256)
#define INCLUDE_vTaskDelay 1 #define INCLUDE_vTaskDelay 1
#define INCLUDE_vTaskDelete 1 #define INCLUDE_vTaskDelete 1
#define INCLUDE_xTaskGetSchedulerState 1 #define INCLUDE_xTaskGetSchedulerState 1
+27
View File
@@ -19,7 +19,33 @@ static size_t sock_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) {
return (size_t) received; return (size_t) received;
} }
// ignore IPv6, testing segment loss under IPv4 is enough
static bool is_tcp_segment(const void *buf, size_t len) {
const struct eth *eth = (const struct eth *) buf;
const struct ip *ip;
const struct tcp *tcp;
size_t hlen;
if (len < sizeof(*eth)) return false;
if (eth->type != mg_htons(0x800)) return false;
ip = (const struct ip *) (eth + 1);
if (len < sizeof(*eth) + sizeof(*ip)) return false;
hlen = (size_t) (ip->ver & 15) * 4;
if ((ip->ver >> 4) != 4 || ip->proto != 6 || hlen < sizeof(*ip) ||
len < sizeof(*eth) + hlen + sizeof(*tcp))
return false;
tcp = (const struct tcp *) ((const char *) ip + hlen);
return (tcp->flags & (TH_SYN | TH_FIN)) == 0;
}
static size_t sock_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) { static size_t sock_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
uint32_t random;
if (ifp->enable_tcp_retransmit && is_tcp_segment(buf, len)) { // introduce a 30% segment loss
mg_random(&random, sizeof(random));
if (random % 10 < 3) {
MG_DEBUG(("dropping TCP segment"));
return len;
}
}
ssize_t res = send(((struct port_driver_data *)ifp->driver_data)->sockfd, buf, len, 0); ssize_t res = send(((struct port_driver_data *)ifp->driver_data)->sockfd, buf, len, 0);
if (res < 0) { if (res < 0) {
MG_ERROR(("sock_tx failed: %d", errno)); MG_ERROR(("sock_tx failed: %d", errno));
@@ -113,6 +139,7 @@ int main(void) {
} }
// RUN TESTS // RUN TESTS
mif.enable_tcp_retransmit = getenv("RTX") != NULL;
result = mip_x_test(&mgr); result = mip_x_test(&mgr);
close(pdd.sockfd); close(pdd.sockfd);
if (!result) return EXIT_FAILURE; if (!result) return EXIT_FAILURE;
+27
View File
@@ -22,7 +22,33 @@ static size_t tap_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) {
return (size_t) received; return (size_t) received;
} }
// ignore IPv6, testing segment loss under IPv4 is enough
static bool is_tcp_segment(const void *buf, size_t len) {
const struct eth *eth = (const struct eth *) buf;
const struct ip *ip;
const struct tcp *tcp;
size_t hlen;
if (len < sizeof(*eth)) return false;
if (eth->type != mg_htons(0x800)) return false;
ip = (const struct ip *) (eth + 1);
if (len < sizeof(*eth) + sizeof(*ip)) return false;
hlen = (size_t) (ip->ver & 15) * 4;
if ((ip->ver >> 4) != 4 || ip->proto != 6 || hlen < sizeof(*ip) ||
len < sizeof(*eth) + hlen + sizeof(*tcp))
return false;
tcp = (const struct tcp *) ((const char *) ip + hlen);
return (tcp->flags & (TH_SYN | TH_FIN)) == 0;
}
static size_t tap_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) { static size_t tap_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
uint32_t random;
if (ifp->enable_tcp_retransmit && is_tcp_segment(buf, len)) { // introduce a 30% segment loss
mg_random(&random, sizeof(random));
if (random % 10 < 3) {
MG_DEBUG(("dropping TCP segment"));
return len;
}
}
ssize_t res = write(*(int *) ifp->driver_data, buf, len); ssize_t res = write(*(int *) ifp->driver_data, buf, len);
if (res < 0) { if (res < 0) {
MG_ERROR(("tap_tx failed: %d", errno)); MG_ERROR(("tap_tx failed: %d", errno));
@@ -125,6 +151,7 @@ int main(void) {
} }
// RUN TESTS // RUN TESTS
mif.enable_tcp_retransmit = getenv("RTX") != NULL;
result = mip_x_test(&mgr); result = mip_x_test(&mgr);
close(fd); close(fd);
if (!result) return EXIT_FAILURE; if (!result) return EXIT_FAILURE;
+290 -11
View File
@@ -57,7 +57,7 @@ static void test_csum(void) {
#if !MG_ENABLE_IPV6 #if !MG_ENABLE_IPV6
#define udp6csum_ok(d, u) true #define udp6csum_ok(d, u) true
#define tcp6csum_ok(d, t) true #define tcp6csum_ok(d, t, l) true
#endif #endif
static bool executed = false; static bool executed = false;
@@ -119,6 +119,33 @@ static void tcpclosure_fn(struct mg_connection *c, int ev, void *ev_data) {
(void) c, (void) ev_data; (void) c, (void) ev_data;
} }
static struct mg_connection *s_drain_conn;
static bool s_drain_tls;
static void tcpdrain_fn(struct mg_connection *c, int ev, void *ev_data) {
static const char data[2 * TCP_TEST_WIN] = {0};
if (ev == MG_EV_ACCEPT) {
s_drain_conn = c;
#if MG_TLS == MG_TLS_BUILTIN
if (s_drain_tls) {
struct mg_tls_opts opts;
struct tls_data *tls;
memset(&opts, 0, sizeof(opts));
mg_tls_init(c, &opts);
tls = (struct tls_data *) c->tls;
ASSERT(tls != NULL);
ASSERT(mg_iobuf_add(&tls->send, 0, data, sizeof(data)) == sizeof(data));
} else
#endif
mg_send(c, data, sizeof(data));
c->is_draining = 1;
} else if (ev == MG_EV_CLOSE && c == s_drain_conn) {
s_drain_conn = NULL;
}
(void) ev_data;
}
static void txwindow_fn(struct mg_connection *c, int ev, void *ev_data) { static void txwindow_fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_ACCEPT) { if (ev == MG_EV_ACCEPT) {
char bigdata[2 * TCP_TEST_WIN + 256]; char bigdata[2 * TCP_TEST_WIN + 256];
@@ -130,6 +157,15 @@ static void txwindow_fn(struct mg_connection *c, int ev, void *ev_data) {
(void) c, (void) ev_data; (void) c, (void) ev_data;
} }
static char s_rtx_data[MG_TCPIP_WIN];
static size_t s_rtx_len;
static uint16_t s_tcp_window;
static void rtx_fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_ACCEPT) mg_send(c, s_rtx_data, s_rtx_len);
(void) ev_data;
}
static void client_fn(struct mg_connection *c, int ev, void *ev_data) { static void client_fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_ERROR || ev == MG_EV_CONNECT) (*(int *) c->fn_data) = ev; if (ev == MG_EV_ERROR || ev == MG_EV_CONNECT) (*(int *) c->fn_data) = ev;
(void) c, (void) ev_data; (void) c, (void) ev_data;
@@ -183,6 +219,7 @@ static void test_poll(void) {
struct driver_data { struct driver_data {
char buf[DRIVER_BUF_SIZE]; char buf[DRIVER_BUF_SIZE];
size_t len; size_t len;
size_t tx_count;
bool tx_ready; // data can be read from tx bool tx_ready; // data can be read from tx
}; };
@@ -192,6 +229,7 @@ static size_t if_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) {
struct driver_data *driver_data = (struct driver_data *) ifp->driver_data; struct driver_data *driver_data = (struct driver_data *) ifp->driver_data;
if (len > DRIVER_BUF_SIZE) len = DRIVER_BUF_SIZE; if (len > DRIVER_BUF_SIZE) len = DRIVER_BUF_SIZE;
driver_data->len = len; driver_data->len = len;
driver_data->tx_count++;
memcpy(driver_data->buf, buf, len); memcpy(driver_data->buf, buf, len);
driver_data->tx_ready = true; driver_data->tx_ready = true;
return len; return len;
@@ -228,7 +266,7 @@ static void create_tcp_seg(struct eth *e, struct ipp *ipp, uint32_t seq,
t.ack = mg_htonl(ack); t.ack = mg_htonl(ack);
t.sport = mg_htons(sport); t.sport = mg_htons(sport);
t.dport = mg_htons(dport); t.dport = mg_htons(dport);
t.win = mg_htons(TCP_TEST_WIN); t.win = mg_htons(s_tcp_window);
t.off = (uint8_t) ((sizeof(t) / 4) << 4) + (uint8_t) ((opts_len / 4) << 4); t.off = (uint8_t) ((sizeof(t) / 4) << 4) + (uint8_t) ((opts_len / 4) << 4);
memcpy(s_driver_data.buf, e, sizeof(*e)); memcpy(s_driver_data.buf, e, sizeof(*e));
#if MG_ENABLE_IPV6 #if MG_ENABLE_IPV6
@@ -242,7 +280,7 @@ static void create_tcp_seg(struct eth *e, struct ipp *ipp, uint32_t seq,
if (opts != NULL && opts_len) if (opts != NULL && opts_len)
memcpy(s_driver_data.buf + sizeof(*e) + sizeof(*ip) + sizeof(t), opts, memcpy(s_driver_data.buf + sizeof(*e) + sizeof(*ip) + sizeof(t), opts,
opts_len); opts_len);
tcp->csum = p6csum(ip, tcp, (4 * (t.off >> 4) + payload_len)); tcp->csum = p6csum(ip, tcp, 6, (4 * (t.off >> 4) + payload_len));
s_driver_data.len = s_driver_data.len =
sizeof(*e) + sizeof(*ip) + sizeof(t) + payload_len + opts_len; sizeof(*e) + sizeof(*ip) + sizeof(t) + payload_len + opts_len;
} else } else
@@ -280,6 +318,7 @@ static void init_tests(struct mg_mgr *mgr, struct eth *e, struct ipp *ipp,
mg_mgr_init(mgr); mg_mgr_init(mgr);
memset(mif, 0, sizeof(*mif)); memset(mif, 0, sizeof(*mif));
memset(&s_driver_data, 0, sizeof(struct driver_data)); memset(&s_driver_data, 0, sizeof(struct driver_data));
s_tcp_window = TCP_TEST_WIN;
driver->init = NULL, driver->tx = if_tx, driver->poll = if_poll, driver->init = NULL, driver->tx = if_tx, driver->poll = if_poll,
driver->rx = if_rx; driver->rx = if_rx;
mif->driver = driver; mif->driver = driver;
@@ -404,7 +443,7 @@ static void test_tcp_basics(bool ipv6) {
if (ipv6) { if (ipv6) {
ASSERT(i6->src[0] == mg_htonll(0x100000000000000) && i6->src[1] == 0 && ASSERT(i6->src[0] == mg_htonll(0x100000000000000) && i6->src[1] == 0 &&
i6->dst[0] == mg_htonll(0x200000000000000) && i6->dst[1] == 0); i6->dst[0] == mg_htonll(0x200000000000000) && i6->dst[1] == 0);
ASSERT(tcp6csum_ok(i6, t)); ASSERT(tcp6csum_ok(i6, t, mg_ntohs(i6->plen)));
} else { } else {
ASSERT(i->src == mg_htonl(0x1000000) && i->dst == mg_htonl(0x2000000)); ASSERT(i->src == mg_htonl(0x1000000) && i->dst == mg_htonl(0x2000000));
ASSERT(tcpcsum_ok(i, t)); ASSERT(tcpcsum_ok(i, t));
@@ -491,6 +530,20 @@ static void test_tcp_basics(bool ipv6) {
// no MSS sent, so it must default to 536/1220 (RFC-9293 3.7.1) // no MSS sent, so it must default to 536/1220 (RFC-9293 3.7.1)
ASSERT(((struct connstate *) (mgr.conns + 1))->dmss == (ipv6 ? 1220 : 536)); ASSERT(((struct connstate *) (mgr.conns + 1))->dmss == (ipv6 ? 1220 : 536));
// check c->is_full advertises a zero window and recovers
mgr.conns->is_full = true;
create_tcp_simpleseg(&e, &ipp, 1001, 2, TH_ACK, 0);
mg_mgr_poll(&mgr, 0);
while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
ASSERT(t->flags == TH_ACK);
ASSERT(t->win == 0);
s_driver_data.len = 0;
mgr.conns->is_full = false;
mg_mgr_poll(&mgr, 0);
ASSERT(received_response(&s_driver_data));
ASSERT(t->flags == TH_ACK);
ASSERT(t->win == mg_htons(MG_TCPIP_WIN));
// segment with seq_no within window // segment with seq_no within window
create_tcp_simpleseg(&e, &ipp, 1010, 2, TH_PUSH, 2); create_tcp_simpleseg(&e, &ipp, 1010, 2, TH_PUSH, 2);
mg_mgr_poll(&mgr, 0); mg_mgr_poll(&mgr, 0);
@@ -536,6 +589,23 @@ static void test_tcp_basics(bool ipv6) {
s_driver_data.len = 0; s_driver_data.len = 0;
mg_mgr_free(&mgr); mg_mgr_free(&mgr);
// Initiate closure with FIN and payload
init_tcp_tests(&mgr, &e, &ipp, &driver, &mif, fn);
init_tcp_handshake(&e, &ipp, &mgr); // starts with seq_no=1000, ackno=2
create_tcp_simpleseg(&e, &ipp, 1001, 2, TH_FIN | TH_ACK, 2);
mg_mgr_poll(&mgr, 0);
while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
ASSERT((t->flags == (TH_FIN | TH_ACK)));
ASSERT((t->seq == mg_htonl(2)));
ASSERT((t->ack == mg_htonl(1004)));
create_tcp_simpleseg(&e, &ipp, 1004, 3, TH_ACK, 0);
mg_mgr_poll(&mgr, 0);
ASSERT(!received_response(&s_driver_data));
ASSERT(mgr.conns->next == NULL); // only one connection: the listener
s_driver_data.len = 0;
mg_mgr_free(&mgr);
// Test client-initiated closure timeout, do not ACK // Test client-initiated closure timeout, do not ACK
init_tcp_tests(&mgr, &e, &ipp, &driver, &mif, fn); init_tcp_tests(&mgr, &e, &ipp, &driver, &mif, fn);
init_tcp_handshake(&e, &ipp, &mgr); // starts with seq_no=1000, ackno=2 init_tcp_handshake(&e, &ipp, &mgr); // starts with seq_no=1000, ackno=2
@@ -773,7 +843,7 @@ static void test_tcp_basics(bool ipv6) {
if (ipv6) { if (ipv6) {
ASSERT(i6->src[0] == mg_htonll(0x100000000000000) && i6->src[1] == 0 && ASSERT(i6->src[0] == mg_htonll(0x100000000000000) && i6->src[1] == 0 &&
i6->dst[0] == mg_htonll(0x200000000000000) && i6->dst[1] == 0); i6->dst[0] == mg_htonll(0x200000000000000) && i6->dst[1] == 0);
ASSERT(tcp6csum_ok(i6, t)); ASSERT(tcp6csum_ok(i6, t, mg_ntohs(i6->plen)));
} else { } else {
ASSERT(i->src == mg_htonl(0x1000000) && i->dst == mg_htonl(0x2000000)); ASSERT(i->src == mg_htonl(0x1000000) && i->dst == mg_htonl(0x2000000));
ASSERT(tcpcsum_ok(i, t)); ASSERT(tcpcsum_ok(i, t));
@@ -862,11 +932,13 @@ static void test_tcp_retransmit(void) {
now = mg_millis() - start; now = mg_millis() - start;
// we wait enough time for a reply // we wait enough time for a reply
if (now > 2 * MG_TCPIP_ACK_MS) { if (now > 2 * MG_TCPIP_ACK_MS) {
response_recv = false; response_recv = false; // response should have been received by now
break; break;
} }
} }
ASSERT((!response_recv)); // replies should not be sent for duplicate packets ASSERT((response_recv)); // retransmit the lost ACK for duplicate segs
ASSERT((t->flags == TH_ACK));
ASSERT((t->ack == mg_htonl(1003))); // dup ACK
// packet with seq_no = 1003 got lost/delayed, send seq_no = 1005 // packet with seq_no = 1003 got lost/delayed, send seq_no = 1005
create_tcp_simpleseg(&e, &ipp, 1005, 2, TH_PUSH | TH_ACK, 2); create_tcp_simpleseg(&e, &ipp, 1005, 2, TH_PUSH | TH_ACK, 2);
@@ -916,6 +988,128 @@ static void test_tcp_retransmit(void) {
mg_mgr_free(&mgr); mg_mgr_free(&mgr);
} }
static void test_tcp_retransmit_queue(void) {
struct mg_mgr mgr;
struct eth e;
struct ip ip;
struct ipp ipp;
struct tcp *t = (struct tcp *) (s_driver_data.buf + sizeof(e) + sizeof(ip));
struct connstate *s;
size_t i, first, off, retired;
uint8_t *data;
uint32_t qlen = 0;
struct mg_tcpip_driver driver;
struct mg_tcpip_if mif;
ipp.ip4 = &ip;
ipp.ip6 = NULL;
for (i = 0; i < sizeof(s_rtx_data); i++) s_rtx_data[i] = (char) i;
s_rtx_len = sizeof(s_rtx_data);
init_tcp_tests(&mgr, &e, &ipp, &driver, &mif, rtx_fn);
mif.enable_tcp_retransmit = true;
s_tcp_window = MG_TCPIP_WIN;
init_tcp_handshake(&e, &ipp, &mgr);
s = (struct connstate *) (mgr.conns + 1);
// Fill the retransmission queue and verify its hard cap
while (s->seq - s->txq_una < s_rtx_len) {
while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
}
ASSERT(s->txq_seq == 2);
ASSERT(s->txq_una == 2);
ASSERT(s->seq - s->txq_una == s_rtx_len);
first = txq_next(s, &data);
ASSERT(first == s->dmss);
ASSERT(memcmp(data, s_rtx_data, first) == 0);
ASSERT(mg_io_send(mgr.conns, s_rtx_data, 1) == MG_IO_WAIT);
// Timeout resends the first retained segment only
s_driver_data.len = 0;
s_driver_data.tx_ready = false;
s_driver_data.tx_count = 0;
s->txq_timer = 0;
mg_mgr_poll(&mgr, 0);
ASSERT(s_driver_data.tx_count == 1);
ASSERT(t->flags == (TH_PUSH | TH_ACK));
ASSERT(t->seq == mg_htonl(2));
ASSERT(s->txq_retries == 1);
ASSERT(txq_next(s, &data) == first);
ASSERT(memcmp(data, s_rtx_data, first) == 0);
// A piggybacked partial ACK advances SND.UNA but retains the segment
retired = first / 2;
create_tcp_simpleseg(&e, &ipp, 1001, 2 + (uint32_t) retired,
TH_PUSH | TH_ACK, 2);
mg_mgr_poll(&mgr, 0);
ASSERT(s->txq_seq == 2);
ASSERT(s->txq_una == 2 + retired);
ASSERT(s->seq - s->txq_una == s_rtx_len - retired);
ASSERT(s->txq_retries == 0);
ASSERT(txq_next(s, &data) == first);
ASSERT(memcmp(data, s_rtx_data, first) == 0);
ASSERT(mgr.conns->recv.len == 2);
// Timeout after partial ACK retains the segment's original SEQ and data
s_driver_data.tx_count = 0;
s->txq_timer = 0;
mg_mgr_poll(&mgr, 0);
ASSERT(s_driver_data.tx_count == 1);
ASSERT(t->seq == mg_htonl(2));
ASSERT(s->txq_retries == 1);
ASSERT(txq_next(s, &data) == first);
ASSERT(memcmp(data, s_rtx_data, first) == 0);
// ACKing record one and part of two retires only record one
create_tcp_simpleseg(&e, &ipp, 1003, 2 + (uint32_t) (first + retired),
TH_ACK, 0);
mg_mgr_poll(&mgr, 0);
ASSERT(s->txq_seq == 2 + first);
ASSERT(s->txq_una == 2 + first + retired);
ASSERT(s->txq_retries == 0);
ASSERT(txq_next(s, &data) == first);
ASSERT(memcmp(data, s_rtx_data + first, first) == 0);
// Timeout retransmits record two with its original SEQ and data
s_driver_data.tx_count = 0;
s->txq_timer = 0;
mg_mgr_poll(&mgr, 0);
ASSERT(s_driver_data.tx_count == 1);
ASSERT(t->seq == mg_htonl(2 + (uint32_t) first));
ASSERT(s->txq_retries == 1);
ASSERT(txq_next(s, &data) == first);
ASSERT(memcmp(data, s_rtx_data + first, first) == 0);
// A complete ACK retires record two; appending restores the full flight
create_tcp_simpleseg(&e, &ipp, 1003, 2 + (uint32_t) (2 * first), TH_ACK, 0);
mg_mgr_poll(&mgr, 0);
ASSERT(s->txq_seq == 2 + 2 * first);
ASSERT(s->txq_una == 2 + 2 * first);
ASSERT(txq_next(s, &data) == first);
ASSERT(memcmp(data, s_rtx_data + 2 * first, first) == 0);
mg_send(mgr.conns, s_rtx_data, 2 * first);
while (s->seq - s->txq_una < s_rtx_len) {
while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
}
for (off = 0; off < s->txq.len; off += sizeof(qlen) + qlen) {
memcpy(&qlen, s->txq.buf + off, sizeof(qlen));
}
ASSERT(off == s->txq.len);
ASSERT(qlen == first);
ASSERT(memcmp(s->txq.buf + off - qlen, s_rtx_data + first, qlen) == 0);
// Draining waits for the rebuilt flight to be acknowledged before FIN
mgr.conns->is_draining = 1;
s_driver_data.len = 0;
mg_mgr_poll(&mgr, 0);
ASSERT(!received_response(&s_driver_data));
create_tcp_simpleseg(&e, &ipp, 1003, s->seq, TH_ACK, 0);
while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
ASSERT(t->flags == (TH_FIN | TH_ACK));
ASSERT(s->txq.len == 0);
s_driver_data.len = 0;
mg_mgr_free(&mgr);
}
static void test_tcp_txwindow(void) { static void test_tcp_txwindow(void) {
struct mg_mgr mgr; struct mg_mgr mgr;
@@ -941,6 +1135,7 @@ static void test_tcp_txwindow(void) {
seq = (uint32_t)(mg_htonl(t->seq) + s_driver_data.len - (size_t)((char *)((uint32_t *)t + (t->off >> 4)) - s_driver_data.buf)); seq = (uint32_t)(mg_htonl(t->seq) + s_driver_data.len - (size_t)((char *)((uint32_t *)t + (t->off >> 4)) - s_driver_data.buf));
} while (seq < (TCP_TEST_WIN + 2)); } while (seq < (TCP_TEST_WIN + 2));
stallcount = count; stallcount = count;
ASSERT(((struct connstate *) (mgr.conns + 1))->txq.buf == NULL);
mg_mgr_poll(&mgr, 0), s_driver_data.len = 0; mg_mgr_poll(&mgr, 0), s_driver_data.len = 0;
mg_mgr_poll(&mgr, 0), s_driver_data.len = 0; mg_mgr_poll(&mgr, 0), s_driver_data.len = 0;
ASSERT((stallcount == count)); ASSERT((stallcount == count));
@@ -964,6 +1159,84 @@ static void test_tcp_txwindow(void) {
mg_mgr_free(&mgr); mg_mgr_free(&mgr);
} }
static void test_tcp_ackseq(void) {
struct mg_mgr mgr;
struct eth e;
struct ip ip;
struct ipp ipp;
struct tcp *t = (struct tcp *) (s_driver_data.buf + sizeof(e) + sizeof(ip));
int count = 0, stallcount;
uint32_t seq;
struct mg_tcpip_driver driver;
struct mg_tcpip_if mif;
ipp.ip4 = &ip;
ipp.ip6 = NULL;
init_tcp_tests(&mgr, &e, &ipp, &driver, &mif, txwindow_fn);
mgr.conns->fn_data = &count;
init_tcp_handshake(&e, &ipp, &mgr);
do {
while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
seq = (uint32_t)(mg_htonl(t->seq) + s_driver_data.len - (size_t)((char *)((uint32_t *)t + (t->off >> 4)) - s_driver_data.buf));
} while (seq < (TCP_TEST_WIN + 2));
stallcount = count;
mg_mgr_poll(&mgr, 0), s_driver_data.len = 0;
ASSERT(stallcount == count);
// Keepalive probe
create_tcp_simpleseg(&e, &ipp, 1000, seq, TH_ACK, 0);
mg_mgr_poll(&mgr, 0);
ASSERT(stallcount == count);
// Invalid SEG.SEQ
create_tcp_simpleseg(&e, &ipp, 1002, seq, TH_ACK, 0);
mg_mgr_poll(&mgr, 0);
ASSERT(stallcount == count);
// Valid FIN+ACK
create_tcp_simpleseg(&e, &ipp, 1001, seq, TH_FIN | TH_ACK, 0);
mg_mgr_poll(&mgr, 0);
ASSERT(stallcount < count);
s_driver_data.len = 0;
mg_mgr_free(&mgr);
}
static void test_tcp_drain(bool tls) {
struct mg_mgr mgr;
struct eth e;
struct ip ip;
struct ipp ipp;
struct tcp *t = (struct tcp *) (s_driver_data.buf + sizeof(e) + sizeof(ip));
struct connstate *s;
struct mg_tcpip_driver driver;
struct mg_tcpip_if mif;
ipp.ip4 = &ip;
ipp.ip6 = NULL;
s_drain_conn = NULL;
s_drain_tls = tls;
init_tcp_tests(&mgr, &e, &ipp, &driver, &mif, tcpdrain_fn);
init_tcp_handshake(&e, &ipp, &mgr); // Send until peer window closes
ASSERT(s_drain_conn != NULL);
while (received_response(&s_driver_data)) {
s_driver_data.len = 0; // Do not feed Mongoose its own TX packet
mg_mgr_poll(&mgr, 0);
}
ASSERT((t->flags & TH_FIN) == 0);
ASSERT(s_drain_conn != NULL);
s = (struct connstate *) (s_drain_conn + 1);
create_tcp_simpleseg(&e, &ipp, 1001, s->seq, TH_ACK, 0); // Reopen window
do {
while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0);
} while (t->flags != (TH_FIN | TH_ACK));
ASSERT(t->flags == (TH_FIN | TH_ACK));
s_driver_data.len = 0;
mg_mgr_free(&mgr);
}
static void test_frag_recv_path(void) { static void test_frag_recv_path(void) {
struct mg_mgr mgr; struct mg_mgr mgr;
struct eth e; struct eth e;
@@ -1124,7 +1397,13 @@ static void test_tcp(bool ipv6) {
if (!ipv6) { if (!ipv6) {
test_tcp_backlog(); test_tcp_backlog();
test_tcp_retransmit(); test_tcp_retransmit();
test_tcp_retransmit_queue();
test_tcp_txwindow(); test_tcp_txwindow();
test_tcp_ackseq();
test_tcp_drain(false);
#if MG_TLS == MG_TLS_BUILTIN
test_tcp_drain(true);
#endif
} }
} }
@@ -1154,7 +1433,7 @@ static void create_udp_dat(struct eth *e, struct ipp *ipp, uint16_t sport,
memcpy(s_driver_data.buf + sizeof(*e), ip, sizeof(*ip)); memcpy(s_driver_data.buf + sizeof(*e), ip, sizeof(*ip));
memcpy(udp, &u, sizeof(u)); memcpy(udp, &u, sizeof(u));
*(s_driver_data.buf + sizeof(*e) + sizeof(*ip) + sizeof(u)) = 'p'; *(s_driver_data.buf + sizeof(*e) + sizeof(*ip) + sizeof(u)) = 'p';
udp->csum = p6csum(ip, udp, (sizeof(u) + payload_len)); udp->csum = p6csum(ip, udp, 17, (sizeof(u) + payload_len));
s_driver_data.len = sizeof(*e) + sizeof(*ip) + sizeof(u) + payload_len; s_driver_data.len = sizeof(*e) + sizeof(*ip) + sizeof(u) + payload_len;
} else } else
#endif #endif
@@ -1331,7 +1610,7 @@ static void create_icmp6_dat(struct eth *e, struct ipp *ipp, uint8_t type,
memcpy(s_driver_data.buf, e, sizeof(*e)); memcpy(s_driver_data.buf, e, sizeof(*e));
memcpy(s_driver_data.buf + sizeof(*e), ip6, sizeof(*ip6)); memcpy(s_driver_data.buf + sizeof(*e), ip6, sizeof(*ip6));
memcpy(icmp6, &i6, sizeof(i6)); memcpy(icmp6, &i6, sizeof(i6));
icmp6->csum = p6csum(ip6, icmp6, sizeof(*icmp6) + payload_len); icmp6->csum = p6csum(ip6, icmp6, 58, sizeof(*icmp6) + payload_len);
s_driver_data.len = sizeof(*e) + sizeof(*ip6) + sizeof(*icmp6) + payload_len; s_driver_data.len = sizeof(*e) + sizeof(*ip6) + sizeof(*icmp6) + payload_len;
if (s_driver_data.len < 64) s_driver_data.len = 64; // add padding if needed if (s_driver_data.len < 64) s_driver_data.len = 64; // add padding if needed
} }
@@ -1367,7 +1646,7 @@ static void test_icmp6_basics(void) {
ASSERT(i->plen == mg_htons(sizeof(*icmp6) + 0)); ASSERT(i->plen == mg_htons(sizeof(*icmp6) + 0));
ASSERT(icmp6->type == 129); // Echo Reply ASSERT(icmp6->type == 129); // Echo Reply
ASSERT(icmp6->code == 0); ASSERT(icmp6->code == 0);
ASSERT(icmp6csum_ok(i, icmp6)); ASSERT(icmp6csum_ok(i, icmp6, mg_ntohs(i->plen)));
create_icmp6_dat(&e, &ipp, 128, 0, NULL, 69); // Echo Request create_icmp6_dat(&e, &ipp, 128, 0, NULL, 69); // Echo Request
mg_mgr_poll(&mgr, 0); // make sure we clean former stuff in buffer mg_mgr_poll(&mgr, 0); // make sure we clean former stuff in buffer
@@ -1378,7 +1657,7 @@ static void test_icmp6_basics(void) {
ASSERT(i->plen == mg_htons(sizeof(*icmp6) + 69)); ASSERT(i->plen == mg_htons(sizeof(*icmp6) + 69));
ASSERT(icmp6->type == 129); // Echo Reply ASSERT(icmp6->type == 129); // Echo Reply
ASSERT(icmp6->code == 0); ASSERT(icmp6->code == 0);
ASSERT(icmp6csum_ok(i, icmp6)); ASSERT(icmp6csum_ok(i, icmp6, mg_ntohs(i->plen)));
// Neighbor Solicitation // Neighbor Solicitation
memset(payload, 0, sizeof(payload)); memset(payload, 0, sizeof(payload));
+19 -6
View File
@@ -75,6 +75,8 @@ static char *host_ip, *host_ip6;
static int s_num_tests = 0; static int s_num_tests = 0;
static bool s_error = false; static bool s_error = false;
#define POLL_LIMIT(n) (mgr->ifp->enable_tcp_retransmit ? (n) * 10 : (n))
#ifdef NO_ABORT #ifdef NO_ABORT
static int s_abort = 0; static int s_abort = 0;
#define ABORT() ++s_abort, s_error = true #define ABORT() ++s_abort, s_error = true
@@ -205,7 +207,7 @@ static int fetch(struct mg_mgr *mgr, char *buf, const char *url,
buf[0] = '\0'; buf[0] = '\0';
// - TLS: multiple (small) records: allow enough loops so mg_mgr_poll can // - TLS: multiple (small) records: allow enough loops so mg_mgr_poll can
// process buffered records when no more frames are coming in // process buffered records when no more frames are coming in
for (i = 0; i < 500 && buf[0] == '\0' && !fd.closed; i++) { for (i = 0; i < POLL_LIMIT(500) && buf[0] == '\0' && !fd.closed; i++) {
mg_mgr_poll(mgr, 0); mg_mgr_poll(mgr, 0);
usleep(5000); // 5 ms. Slow down poll loop to ensure packet transit, but usleep(5000); // 5 ms. Slow down poll loop to ensure packet transit, but
// allow enough loops to get the ARP response, otherwise, // allow enough loops to get the ARP response, otherwise,
@@ -304,6 +306,7 @@ static void mqtt_fn(struct mg_connection *c, int ev, void *ev_data) {
// close on farewell // close on farewell
MG_INFO(("%lu CLOSING", c->id)); MG_INFO(("%lu CLOSING", c->id));
mg_mqtt_disconnect(c, NULL); mg_mqtt_disconnect(c, NULL);
c->is_draining = true;
data->passed = true; data->passed = true;
} else if (mm->data.len == 21098) { } else if (mm->data.len == 21098) {
struct mg_mqtt_opts pub_opts; struct mg_mqtt_opts pub_opts;
@@ -360,7 +363,9 @@ static void test_mqtt_connsubpub(struct mg_mgr *mgr) {
mg_mgr_poll(mgr, 0); mg_mgr_poll(mgr, 0);
s_conn = mg_mqtt_connect(mgr, data.url, &opts, mqtt_fn, &data); s_conn = mg_mqtt_connect(mgr, data.url, &opts, mqtt_fn, &data);
ASSERT(s_conn != NULL); ASSERT(s_conn != NULL);
for (int i = 0; i < 1000 && s_conn != NULL && !s_conn->is_closing; i++) { for (int i = 0;
i < POLL_LIMIT(1000) && s_conn != NULL && !s_conn->is_closing;
i++) {
mg_mgr_poll(mgr, 0); mg_mgr_poll(mgr, 0);
usleep(5000); // 5 ms (*) See fetch() above for reasons usleep(5000); // 5 ms (*) See fetch() above for reasons
} }
@@ -406,7 +411,9 @@ static void test_mqtt_connsubpub(struct mg_mgr *mgr) {
mg_mgr_poll(mgr, 0); mg_mgr_poll(mgr, 0);
s_conn = mg_mqtt_connect(mgr, data.url, &opts, mqtt_fn, &data); s_conn = mg_mqtt_connect(mgr, data.url, &opts, mqtt_fn, &data);
ASSERT(s_conn != NULL); ASSERT(s_conn != NULL);
for (int i = 0; i < 1000 && s_conn != NULL && !s_conn->is_closing; i++) { for (int i = 0;
i < POLL_LIMIT(1000) && s_conn != NULL && !s_conn->is_closing;
i++) {
mg_mgr_poll(mgr, 0); mg_mgr_poll(mgr, 0);
usleep(5000); // 5 ms (*) See fetch() above for reasons usleep(5000); // 5 ms (*) See fetch() above for reasons
} }
@@ -418,10 +425,12 @@ static void test_mqtt_connsubpub(struct mg_mgr *mgr) {
#ifndef NO_HTTPSERVER_TEST #ifndef NO_HTTPSERVER_TEST
#include <pthread.h> #include <pthread.h>
static volatile bool s_poll_done;
static void *poll_thread(void *p) { static void *poll_thread(void *p) {
struct mg_mgr *mgr = (struct mg_mgr *) p; struct mg_mgr *mgr = (struct mg_mgr *) p;
int i; int i;
for (i = 0; i < 300; i++) { for (i = 0; i < POLL_LIMIT(300) && !s_poll_done; i++) {
mg_mgr_poll(mgr, 0); mg_mgr_poll(mgr, 0);
usleep(10000); // 10 ms. Slow down poll loop to ensure packet transit usleep(10000); // 10 ms. Slow down poll loop to ensure packet transit
} }
@@ -436,6 +445,7 @@ static void test_http_server(struct mg_mgr *mgr) {
struct mg_connection *c; struct mg_connection *c;
char *cmd; char *cmd;
pthread_t thread_id = (pthread_t) 0; pthread_t thread_id = (pthread_t) 0;
int rc;
#if MG_TLS #if MG_TLS
struct mg_tls_opts opts; struct mg_tls_opts opts;
memset(&opts, 0, sizeof(opts)); memset(&opts, 0, sizeof(opts));
@@ -455,12 +465,15 @@ static void test_http_server(struct mg_mgr *mgr) {
#endif #endif
ASSERT(c != NULL); ASSERT(c != NULL);
ASSERT (mg_send(c, "NADA", 0)); // check mg_send allows len=0 ASSERT (mg_send(c, "NADA", 0)); // check mg_send allows len=0
s_poll_done = false;
pthread_create(&thread_id, NULL, poll_thread, pthread_create(&thread_id, NULL, poll_thread,
mgr); // simpler this way, no concurrency anyway mgr); // simpler this way, no concurrency anyway
MG_DEBUG(("CURL")); MG_DEBUG(("CURL"));
ASSERT(system(cmd) == 0); // wait for curl rc = system(cmd); // wait for curl
MG_DEBUG(("MONGOOSE")); MG_DEBUG(("MONGOOSE"));
s_poll_done = true;
pthread_join(thread_id, NULL); // wait for Mongoose pthread_join(thread_id, NULL); // wait for Mongoose
ASSERT(rc == 0);
MG_DEBUG(("DONE")); MG_DEBUG(("DONE"));
free(cmd); free(cmd);
#endif #endif
@@ -529,7 +542,7 @@ static bool sntpms(struct mg_mgr *mgr, const char *url) {
int64_t ms = 0; int64_t ms = 0;
int i; int i;
mg_sntp_connect(mgr, url, sntpcb, &ms); mg_sntp_connect(mgr, url, sntpcb, &ms);
for (i = 0; i < 50 && ms == 0; i++) { for (i = 0; i < POLL_LIMIT(50) && ms == 0; i++) {
mg_mgr_poll(mgr, 0); mg_mgr_poll(mgr, 0);
usleep(10000); // 10 ms. Slow down poll loop to ensure packet transit usleep(10000); // 10 ms. Slow down poll loop to ensure packet transit
} }
+27 -1
View File
@@ -156,6 +156,10 @@ static void test_http_get_var(void) {
body = mg_str("key=broken%2x"); body = mg_str("key=broken%2x");
ASSERT(mg_http_get_var(&body, "key", buf, sizeof(buf)) == -3); ASSERT(mg_http_get_var(&body, "key", buf, sizeof(buf)) == -3);
memset(buf, 'x', sizeof(buf));
body = mg_str("access_token=a%zz");
ASSERT(mg_http_get_var(&body, "access_token", buf, sizeof(buf)) == -3);
ASSERT(memchr(buf, '\0', sizeof(buf)) != NULL);
ASSERT(mg_http_get_var(&body, "inexistent", buf, sizeof(buf)) == -4); ASSERT(mg_http_get_var(&body, "inexistent", buf, sizeof(buf)) == -4);
body = mg_str("key=%"); body = mg_str("key=%");
ASSERT(mg_http_get_var(&body, "key", buf, sizeof(buf)) == -3); ASSERT(mg_http_get_var(&body, "key", buf, sizeof(buf)) == -3);
@@ -963,7 +967,7 @@ static void wcb(struct mg_connection *c, int ev, void *ev_data) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data; struct mg_http_message *hm = (struct mg_http_message *) ev_data;
struct mg_str *wsproto = mg_http_get_header(hm, "Sec-WebSocket-Protocol"); struct mg_str *wsproto = mg_http_get_header(hm, "Sec-WebSocket-Protocol");
ASSERT(wsproto != NULL); ASSERT(wsproto != NULL);
mg_ws_send(c, "hello", 0, 0); mg_ws_send(c, "", 0, WEBSOCKET_OP_TEXT);
mg_ws_printf(c, WEBSOCKET_OP_BINARY, "%.3s", "boo!!!!"); mg_ws_printf(c, WEBSOCKET_OP_BINARY, "%.3s", "boo!!!!");
mg_ws_printf(c, WEBSOCKET_OP_BINARY, "%s", "foobar"); mg_ws_printf(c, WEBSOCKET_OP_BINARY, "%s", "foobar");
mg_ws_send(c, "", 0, WEBSOCKET_OP_PING); mg_ws_send(c, "", 0, WEBSOCKET_OP_PING);
@@ -979,6 +983,15 @@ static void wcb(struct mg_connection *c, int ev, void *ev_data) {
} }
} }
static void wbad(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_WS_OPEN) {
mg_ws_send(c, "ABC", 3, WEBSOCKET_OP_CONTINUE);
} else if (ev == MG_EV_CLOSE) {
*(int *) c->fn_data = 1;
}
(void) ev_data;
}
static void ew2(struct mg_connection *c, int ev, void *ev_data) { static void ew2(struct mg_connection *c, int ev, void *ev_data) {
size_t size = 65 * 1024 + 737; size_t size = 65 * 1024 + 737;
if (ev == MG_EV_WS_OPEN) { if (ev == MG_EV_WS_OPEN) {
@@ -1024,6 +1037,11 @@ static void test_ws(void) {
for (i = 0; i < 1000 && done == 0; i++) mg_mgr_poll(&mgr, 1); for (i = 0; i < 1000 && done == 0; i++) mg_mgr_poll(&mgr, 1);
ASSERT(done == 1); ASSERT(done == 1);
done = 0;
mg_ws_connect(&mgr, url, wbad, &done, NULL);
for (i = 0; i < 30 && done == 0; i++) mg_mgr_poll(&mgr, 1);
ASSERT(done == 1);
mg_mgr_free(&mgr); mg_mgr_free(&mgr);
ASSERT(mgr.conns == NULL); ASSERT(mgr.conns == NULL);
} }
@@ -1758,6 +1776,14 @@ static void test_http_pipeline(void) {
for (i = 0; i < 20; i++) mg_mgr_poll(&mgr, 1); for (i = 0; i < 20; i++) mg_mgr_poll(&mgr, 1);
ASSERT(ok == 1); ASSERT(ok == 1);
ASSERT(ok2 == 1); ASSERT(ok2 == 1);
// A bodyless POST must not consume the following pipelined request
ok = ok2 = 0;
c = mg_http_connect(&mgr, url, f6, (void *) &ok2);
mg_printf(c, "POST /x HTTP/1.1\r\nHost: h\r\n\r\n"
"GET /next HTTP/1.1\r\nHost: h\r\n\r\n");
for (i = 0; i < 20; i++) mg_mgr_poll(&mgr, 1);
ASSERT(ok == 2);
ASSERT(ok2 == 2);
// MG_INFO(("-----> [%d] [%d]", ok, ok2)); // MG_INFO(("-----> [%d] [%d]", ok, ok2));
mg_mgr_free(&mgr); mg_mgr_free(&mgr);
ASSERT(mgr.conns == NULL); ASSERT(mgr.conns == NULL);
@@ -255,6 +255,7 @@ void mg_dash_init(struct mg_mgr *mgr) {
mg_mem_files = mg_packed_files; mg_mem_files = mg_packed_files;
mg_http_listen(mgr, MG_HTTP_ADDR, mg_dash_ev_handler, &s_dash); mg_http_listen(mgr, MG_HTTP_ADDR, mg_dash_ev_handler, &s_dash);
mg_http_listen(mgr, MG_HTTPS_ADDR, mg_dash_ev_handler, &s_dash);
} }
void mg_dash_poll(struct mg_mgr *mgr) { void mg_dash_poll(struct mg_mgr *mgr) {
+6
View File
@@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(mongoose-esp32-minimal)
+23
View File
@@ -0,0 +1,23 @@
THISDIR = $(realpath $(CURDIR))
ROOTDIR = $(realpath $(CURDIR)/../../..)
DOCKER ?= docker run --rm $(DA) -v $(ROOTDIR):$(ROOTDIR) -w $(THISDIR) espressif/idf
CMD ?= idf.py build
PORT ?= /dev/ttyUSB0
all: example
example:
true
build: Makefile $(wildcard main/*)
$(DOCKER) $(CMD)
flash:
flash: CMD = idf.py flash
flash: DA = --device $(PORT)
flash: build
clean:
test -d build && $(DOCKER) rm -rf build sdkconfig || true
.PHONY: build
+74
View File
@@ -0,0 +1,74 @@
# ESP32 minimal crash capture
ESP-IDF already captures the ESP32 equivalent of an ARM HardFault. Use the
ESP-IDF panic handler and ESP-IDF core dump support instead of replacing the
low-level exception path.
The panic handler captures the architecture-specific fault frame:
- Xtensa targets: PC, PS, A0..A15, SAR, EXCCAUSE, EXCVADDR, loop registers, and
backtrace.
- RISC-V targets: MEPC, RA, SP, MSTATUS, MCAUSE, MTVAL, MHARTID, GPRs, and
backtrace.
Fatal classes include CPU exceptions, interrupt watchdog, task watchdog, cache
errors, brownout, stack overflow, heap corruption, UBSAN checks, and assertions.
This example enables core dump to flash. No filesystem is involved: ESP-IDF
writes directly to a raw partition table entry of type `data`, subtype
`coredump`.
```csv
coredump, data, coredump,, 128K
```
At panic time, ESP-IDF serializes the crash state into that raw flash partition.
On the next boot, the application can read it with the user-mode coredump APIs:
```c
esp_reset_reason();
esp_core_dump_get_panic_reason(reason, sizeof(reason));
esp_core_dump_get_summary(summary);
esp_core_dump_image_get(&addr, &size);
esp_core_dump_image_erase();
```
This example exposes:
- `/api/crash`: JSON metadata: reset reason, panic reason, crashed task, PC, and
coredump size.
- `/api/crash.bin`: raw ESP-IDF coredump image, suitable for offline decoding
with the application ELF.
The raw coredump can include task stacks, TCBs, selected memory regions, and
therefore customer-private data. Production upload must treat it as sensitive
diagnostic material.
## Wizard integration notes
The Wizard ESP-IDF template should keep its existing `mongoose_init()` /
`mongoose_poll()` structure and register the crash endpoints as custom API
handlers. The relevant ESP-IDF pieces from this tutorial are:
```ini
CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y
CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=y
CONFIG_ESP_COREDUMP_MAX_TASKS_NUM=16
CONFIG_ESP_COREDUMP_STACK_SIZE=2048
```
The ESP-IDF component needs `espcoredump` and `esp_partition`.
Wizard's current 4 MB OTA partition table leaves `0x10000` bytes after SPIFFS,
so a 64 KB coredump partition fits without changing the app or SPIFFS sizes.
The 128 KB partition used by this tutorial requires shrinking SPIFFS or one of
the app slots.
```csv
coredump, data, coredump,, 64K
```
The WiFi code is not materially related to crash capture. Apart from where the
credentials come from, the differences are retry policy, temporary event handler
lifetime, WPA2/PMF station config, and whether the password is logged. Wizard
can keep its existing WiFi code for this feature.
@@ -0,0 +1,14 @@
idf_component_register(SRCS "main.c"
"wifi.c"
"../../../../mongoose.c"
INCLUDE_DIRS "."
"../../../.."
REQUIRES esp_event
esp_netif
esp_partition
esp_wifi
esp_timer
espcoredump
nvs_flash
app_update)
component_compile_options(-DHTTP_URL="http://0.0.0.0:80")
+161
View File
@@ -0,0 +1,161 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include <stdlib.h>
#include "esp_core_dump.h"
#include "esp_err.h"
#include "esp_partition.h"
#include "esp_system.h"
#include "mongoose.h"
#include "sdkconfig.h"
#define WIFI_SSID "YOUR_WIFI_NETWORK_NAME" // SET THIS!
#define WIFI_PASS "YOUR_WIFI_PASSWORD" // SET THIS!
#ifndef HTTP_URL
#define HTTP_URL "http://0.0.0.0:80"
#endif
#ifndef CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
#define CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH 0
#endif
#ifndef ESP_PARTITION_SUBTYPE_DATA_COREDUMP
#define ESP_PARTITION_SUBTYPE_DATA_COREDUMP 0x03
#endif
struct crash_download {
const esp_partition_t *partition;
size_t offset;
size_t size;
size_t sent;
};
static void crash_json(struct mg_connection *c) {
#if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
esp_core_dump_summary_t summary;
char reason[200] = "";
size_t address = 0, size = 0;
esp_err_t err = esp_core_dump_image_get(&address, &size);
esp_err_t sr = err == ESP_OK ? esp_core_dump_get_summary(&summary) : err;
esp_err_t rr = err == ESP_OK ? esp_core_dump_get_panic_reason(
reason, sizeof(reason)) : err;
mg_http_reply(c, 200, "Content-Type: application/json\r\n",
"{%m:%s,%m:%d,%m:%u,%m:%u,%m:%m,%m:%m,%m:%d,%m:%d,%m:%m}\n",
MG_ESC("present"), err == ESP_OK ? "true" : "false",
MG_ESC("reset_reason"), (int) esp_reset_reason(),
MG_ESC("address"), (unsigned) address, MG_ESC("size"),
(unsigned) size, MG_ESC("panic_reason"),
MG_ESC(rr == ESP_OK ? reason : ""), MG_ESC("task"),
MG_ESC(sr == ESP_OK ? summary.exc_task : ""), MG_ESC("pc"),
sr == ESP_OK ? (int) summary.exc_pc : 0, MG_ESC("error"),
(int) err, MG_ESC("binary"), MG_ESC("/api/crash.bin"));
#else
mg_http_reply(c, 501, "Content-Type: application/json\r\n",
"{%m:false,%m:%d,%m:%m}\n", MG_ESC("present"),
MG_ESC("reset_reason"), (int) esp_reset_reason(),
MG_ESC("error"), MG_ESC("coredump to flash is disabled"));
#endif
}
static void crash_send(struct mg_connection *c) {
#if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
struct crash_download *d = (struct crash_download *) c->fn_data;
while (d != NULL && d->sent < d->size && c->send.len < 2048) {
uint8_t buf[512];
size_t left = d->size - d->sent;
size_t len = left > sizeof(buf) ? sizeof(buf) : left;
esp_err_t err = esp_partition_read(d->partition, d->offset + d->sent, buf,
len);
if (err != ESP_OK) {
MG_ERROR(("esp_partition_read: %s", esp_err_to_name(err)));
c->is_draining = 1;
return;
}
mg_send(c, buf, len);
d->sent += len;
}
if (d != NULL && d->sent >= d->size) {
free(d);
c->fn_data = NULL;
c->is_draining = 1;
}
#else
(void) c;
#endif
}
static void crash_bin(struct mg_connection *c) {
#if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
const esp_partition_t *p = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_COREDUMP, NULL);
struct crash_download *d;
size_t address = 0, size = 0, offset = 0;
esp_err_t err = esp_core_dump_image_get(&address, &size);
if (err != ESP_OK) {
mg_http_reply(c, 404, "Content-Type: application/json\r\n",
"{%m:%m}\n", MG_ESC("error"), MG_ESC(esp_err_to_name(err)));
} else if (p == NULL || address < p->address ||
address + size > p->address + p->size) {
mg_http_reply(c, 500, "Content-Type: application/json\r\n",
"{%m:%m}\n", MG_ESC("error"),
MG_ESC("invalid coredump partition"));
} else if ((d = (struct crash_download *) calloc(1, sizeof(*d))) == NULL) {
mg_http_reply(c, 500, "Content-Type: application/json\r\n",
"{%m:%m}\n", MG_ESC("error"), MG_ESC("out of memory"));
} else {
offset = address - p->address;
d->partition = p;
d->offset = offset;
d->size = size;
c->fn_data = d;
mg_printf(c,
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/octet-stream\r\n"
"Content-Length: %u\r\n"
"Content-Disposition: attachment; filename=\"esp32-coredump."
"bin\"\r\n"
"\r\n",
(unsigned) size);
crash_send(c);
}
#else
mg_http_reply(c, 501, "Content-Type: application/json\r\n",
"{%m:%m}\n", MG_ESC("error"),
MG_ESC("coredump to flash is disabled"));
#endif
}
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/crash.bin"), NULL)) {
crash_bin(c);
} else if (mg_match(hm->uri, mg_str("/api/crash"), NULL)) {
crash_json(c);
} else {
mg_http_reply(c, 200, "", "Hi from Mongoose\n");
}
} else if (ev == MG_EV_POLL && c->fn_data != NULL) {
crash_send(c);
} else if (ev == MG_EV_CLOSE && c->fn_data != NULL) {
free(c->fn_data);
c->fn_data = NULL;
}
}
void app_main(void) {
void wifi_init(const char *ssid, const char *pass);
wifi_init(WIFI_SSID, WIFI_PASS);
struct mg_mgr mgr;
mg_log_set(MG_LL_INFO);
mg_mgr_init(&mgr);
MG_INFO(("Mongoose version : v%s", MG_VERSION));
MG_INFO(("Listening on : %s", HTTP_URL));
mg_http_listen(&mgr, HTTP_URL, http_ev_handler, NULL);
for (;;) mg_mgr_poll(&mgr, 1000);
}
@@ -0,0 +1,3 @@
#define MG_ARCH MG_ARCH_ESP32
#define MG_TLS MG_TLS_NONE
+104
View File
@@ -0,0 +1,104 @@
// Code taken from the ESP32 IDF WiFi station Example
#include <string.h>
#include "esp_event.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "mongoose.h"
static EventGroupHandle_t s_wifi_event_group;
/* The event group allows multiple bits for each event, but we only care about
* two events:
* - we are connected to the AP with an IP
* - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static int s_retry_num = 0;
static void event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT &&
event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < 10) {
esp_wifi_connect();
s_retry_num++;
MG_INFO(("retry to connect to the AP"));
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
MG_ERROR(("connect to the AP fail"));
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data;
MG_INFO(("IP ADDRESS:" IPSTR, IP2STR(&event->ip_info.ip)));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
void wifi_init(const char *ssid, const char *pass) {
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(
WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(
IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_got_ip));
wifi_config_t c = {.sta = {.threshold = {.authmode = WIFI_AUTH_WPA2_PSK},
.pmf_cfg = {.capable = true, .required = false}}};
snprintf((char *) c.sta.ssid, sizeof(c.sta.ssid), "%s", ssid);
snprintf((char *) c.sta.password, sizeof(c.sta.password), "%s", pass);
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &c));
ESP_ERROR_CHECK(esp_wifi_start());
MG_DEBUG(("wifi_init_sta finished."));
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE, pdFALSE, portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT) {
MG_INFO(("connected to ap SSID:%s", ssid));
} else if (bits & WIFI_FAIL_BIT) {
MG_ERROR(("Failed to connect to SSID:%s", ssid));
} else {
MG_ERROR(("UNEXPECTED EVENT"));
}
/* The event will not be processed after unregister */
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(
IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(
WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
vEventGroupDelete(s_wifi_event_group);
}
+5
View File
@@ -0,0 +1,5 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, , 0x6000
phy_init, data, phy, , 0x1000
factory, app, factory, , 1M
coredump, data, coredump,, 128K
1 # Name, Type, SubType, Offset, Size, Flags
2 nvs, data, nvs, , 0x6000
3 phy_init, data, phy, , 0x1000
4 factory, app, factory, , 1M
5 coredump, data, coredump,, 128K
@@ -0,0 +1,7 @@
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y
CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=y
CONFIG_ESP_COREDUMP_MAX_TASKS_NUM=16
CONFIG_ESP_COREDUMP_STACK_SIZE=2048
+23 -49
View File
@@ -1,10 +1,11 @@
// Copyright (c) 2020-2024 Cesanta Software Limited // Copyright (c) 2020-2024 Cesanta Software Limited
// All rights reserved // All rights reserved
// //
// Streaming upload example. Demonstrates how to use MG_EV_READ events // Streaming upload example. Demonstrates how to use mg_http_stream_body()
// to save a large file without buffering it fully in memory. // to save a large file without buffering it fully in memory.
// The file is saved as /tmp/a.txt.
// //
// curl http://localhost:8000/upload?name=a.txt --data-binary @large_file.txt // curl http://localhost:8000/fs/a.txt --data-binary @large_file.txt
#include "mongoose.h" #include "mongoose.h"
@@ -28,48 +29,24 @@ static const char *s_tls_key =
"4hf5Gx17YJkq5/z3k6ogPDPpoAYWIw1/sw==\n" "4hf5Gx17YJkq5/z3k6ogPDPpoAYWIw1/sw==\n"
"-----END EC PRIVATE KEY-----\n"; "-----END EC PRIVATE KEY-----\n";
struct upload_state { static void upload(struct mg_http_message *hm, struct mg_str *data, void **p) {
size_t expected; // POST data length, bytes
size_t received; // Already received bytes
void *fp; // Opened uploaded file
};
static void handle_uploads(struct mg_connection *c, int ev, void *ev_data) {
struct upload_state *us = (struct upload_state *) c->data;
struct mg_fs *fs = &mg_fs_posix; struct mg_fs *fs = &mg_fs_posix;
if (hm != NULL) {
// Catch /upload requests early, without buffering whole body char path[MG_PATH_MAX];
// When we receive MG_EV_HTTP_HDRS event, that means we've received all MG_INFO(("UPLOAD BEGIN, URI %.*s ", hm->uri.len, hm->uri.buf));
// HTTP headers but not necessarily full HTTP body mg_snprintf(path, sizeof(path), "%s/%.*s", UPLOAD_DIR, hm->uri.len - 4,
if (ev == MG_EV_HTTP_HDRS) { hm->uri.buf + 4);
struct mg_http_message *hm = (struct mg_http_message *) ev_data; *p = NULL;
if (mg_match(hm->uri, mg_str("/upload/*"), NULL)) { if (mg_path_is_sane(mg_str(path))) {
char path[MG_PATH_MAX]; fs->rm(path); // Delete file if it exists
mg_snprintf(path, sizeof(path), "%s/%.*s", UPLOAD_DIR, hm->uri.len - 8, *p = fs->op(path, MG_FS_WRITE); // Open file for writing
hm->uri.buf + 8);
us->expected = hm->body.len; // Store number of bytes we expect
mg_iobuf_del(&c->recv, 0, hm->head.len); // Delete HTTP headers
c->pfn = NULL; // Silence HTTP protocol handler, we'll use MG_EV_READ
if (mg_path_is_sane(mg_str(path))) {
fs->rm(path); // Delete file if it exists
us->fp = fs->op(path, MG_FS_WRITE); // Open file for writing
}
}
}
// Catch uploaded file data for both MG_EV_READ and MG_EV_HTTP_HDRS
if (us->expected > 0 && c->recv.len > 0) {
us->received += c->recv.len;
if (us->fp) fs->wr(us->fp, c->recv.buf, c->recv.len); // Write to file
c->recv.len = 0; // Delete received data
if (us->received >= us->expected) {
// Uploaded everything. Send response back
MG_INFO(("Uploaded %lu bytes", us->received));
mg_http_reply(c, 200, NULL, "%lu ok\n", us->received);
if (us->fp) fs->cl(us->fp); // Close file
memset(us, 0, sizeof(*us)); // Cleanup upload state
c->is_draining = 1; // Close connection when response gets sent
} }
} else if (data != NULL) {
MG_INFO(("UPLOAD DATA, %zu bytes", data->len));
if (*p) fs->wr(*p, data->buf, data->len); // Write to file
} else {
MG_INFO(("UPLOAD END %p", p));
if (*p) fs->cl(*p), *p = NULL;
} }
} }
@@ -78,16 +55,13 @@ static void fn(struct mg_connection *c, int ev, void *ev_data) {
struct mg_tls_opts opts = {.cert = mg_str(s_tls_cert), struct mg_tls_opts opts = {.cert = mg_str(s_tls_cert),
.key = mg_str(s_tls_key)}; .key = mg_str(s_tls_key)};
mg_tls_init(c, &opts); mg_tls_init(c, &opts);
} } else if (ev == MG_EV_HTTP_MSG && c->pfn != NULL) {
handle_uploads(c, ev, ev_data);
// Non-upload requests, we serve normally
// NOTE: handle_uploads() may delete request and reset c->pfn
if (ev == MG_EV_HTTP_MSG && c->pfn != NULL) {
struct mg_http_serve_opts opts = {.root_dir = "web_root"}; struct mg_http_serve_opts opts = {.root_dir = "web_root"};
mg_http_serve_dir(c, ev_data, &opts); mg_http_serve_dir(c, ev_data, &opts);
} }
// Handle upload requests to /fs/*
mg_http_stream_body(c, ev, ev_data, mg_str("/fs/*"), upload);
} }
int main(void) { int main(void) {
@@ -50,7 +50,7 @@
r.onload = function() { r.onload = function() {
ev.target.value = ''; ev.target.value = '';
document.getElementById('el3').innerText = 'Uploading...'; document.getElementById('el3').innerText = 'Uploading...';
fetch(`/upload/${encodeURIComponent(f.name)}`, { fetch(`/fs/${encodeURIComponent(f.name)}`, {
method: 'POST', method: 'POST',
body: r.result, body: r.result,
}).then(function(res) { }).then(function(res) {
@@ -0,0 +1 @@
See the example at https://github.com/mongoose-examples/frdm-imxrt1186
@@ -0,0 +1,48 @@
# Environment setup: https://mongoose.ws/documentation/tutorials/tools/
CFLAGS = -W -Wall -Wextra -Werror -Wundef -Wshadow -Wdouble-promotion
CFLAGS += -Wformat-truncation -fno-common -Wno-conversion -Wno-sign-conversion
CFLAGS += -g3 -Os -ffunction-sections -fdata-sections
CFLAGS += -Wno-cast-function-type -Wno-old-style-declaration
CFLAGS += -Wno-unused-parameter -Wno-unused-function
CFLAGS += -I. -Imongoose -Icmsis_core/CMSIS/Core/Include/
CFLAGS += -Icmsis_rt1186/devices/MIMXRT1186/ -Icmsis_rt1186/devices/MIMXRT1186/periph/ -Icmsis_rt1186/devices/MIMXRT1186/drivers/
CFLAGS += -DCPU_MIMXRT1186CVJ8C_cm33 -DCPU_MIMXRT1186CVJ8C -DMIMXRT1186_cm33_SERIES
CFLAGS += -DXIP_EXTERNAL_FLASH=1 -DXIP_BOOT_HEADER_ENABLE=1
CFLAGS += -mcpu=cortex-m33 -mthumb -mfpu=fpv5-sp-d16 -mfloat-abi=hard $(CFLAGS_EXTRA)
LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs=nano.specs -lc -lgcc -Wl,--gc-sections -Wl,-Map=$@.map
SOURCES = main.c hal.c flash_image.c mongoose/mongoose.c
SOURCES += cmsis_rt1186/devices/MIMXRT1186/gcc/startup_MIMXRT1186_cm33.S
SOURCES += cmsis_rt1186/devices/MIMXRT1186/drivers/fsl_clock.c
SOURCES += cmsis_rt1186/devices/MIMXRT1186/drivers/fsl_pmu.c
SOURCES += cmsis_rt1186/devices/MIMXRT1186/drivers/fsl_common_arm.c
SOURCES += cmsis_rt1186/devices/MIMXRT1186/drivers/fsl_ele_base_api.c
SOURCES += cmsis_rt1186/devices/MIMXRT1186/drivers/fsl_s3mu.c
SOURCES += cmsis_rt1186/devices/MIMXRT1186/drivers/fsl_trdc.c
CFLAGS += -D__ATOLLIC__ -D__STARTUP_CLEAR_BSS -DMG_HAL_DISABLE_RANDOM
all build: firmware.bin
mongoose/mongoose.c mongoose/mongoose.h:
mkdir -p mongoose
cp ../../../../mongoose.[ch] mongoose/
firmware.bin: firmware.elf
arm-none-eabi-objcopy -O binary $< $@
arm-none-eabi-size --format=berkeley $<
firmware.elf: cmsis_core cmsis_rt1186 $(SOURCES) $(wildcard *.h) link.ld Makefile
arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@
cmsis_core:
git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@
cmsis_rt1186:
curl -sL https://mcuxpresso.nxp.com/cmsis_pack/repo/NXP.MIMXRT1186_DFP.26.06.00.pack -o NXP.MIMXRT1186_DFP.26.06.00.pack
unzip -q NXP.MIMXRT1186_DFP.26.06.00.pack -d $@
rm NXP.MIMXRT1186_DFP.26.06.00.pack
clean:
rm -rf firmware.* cmsis_* mongoose/mongoose.*
@@ -0,0 +1,157 @@
#include <stdint.h>
#define LUT_OPERAND0(x) ((uint32_t) (x) << 0)
#define LUT_NUM_PADS0(x) ((uint32_t) (x) << 8)
#define LUT_OPCODE0(x) ((uint32_t) (x) << 10)
#define LUT_OPERAND1(x) ((uint32_t) (x) << 16)
#define LUT_NUM_PADS1(x) ((uint32_t) (x) << 24)
#define LUT_OPCODE1(x) ((uint32_t) (x) << 26)
#define LUT_SEQ(cmd0, pads0, operand0, cmd1, pads1, operand1) \
(LUT_OPERAND0(operand0) | LUT_NUM_PADS0(pads0) | LUT_OPCODE0(cmd0) | \
LUT_OPERAND1(operand1) | LUT_NUM_PADS1(pads1) | LUT_OPCODE1(cmd1))
enum {
LUT_STOP = 0x00,
LUT_CMD_SDR = 0x01,
LUT_RADDR_SDR = 0x02,
LUT_WRITE_SDR = 0x08,
LUT_READ_SDR = 0x09,
LUT_DUMMY_SDR = 0x0c,
LUT_1_PAD = 0,
LUT_4_PADS = 2,
};
// FlexSPI2 NOR configuration block for the FRDM-IMXRT1186 QSPI flash
// The ROM expects this 512-byte structure at flash offset 0x400
__attribute__((section(".boot_hdr.conf"), used))
const uint32_t qspi_flash_nor_config[128] = {
[0] = 0x42464346, // Tag: "FCFB"
[1] = 0x56010400, // Version 1.4.0
[3] = 0x00030301, // DQS loopback, CS hold/setup = 3
[16] = 0x00000010, // Safe configuration
[17] = 0x00050401, // Serial NOR, 4 pads, 100 MHz
[20] = 16U * 1024U * 1024U,
// Read
[32] =
LUT_SEQ(LUT_CMD_SDR, LUT_1_PAD, 0xeb, LUT_RADDR_SDR, LUT_4_PADS, 0x18),
[33] =
LUT_SEQ(LUT_DUMMY_SDR, LUT_4_PADS, 0x06, LUT_READ_SDR, LUT_4_PADS, 0x04),
// Read status
[36] =
LUT_SEQ(LUT_CMD_SDR, LUT_1_PAD, 0x05, LUT_READ_SDR, LUT_1_PAD, 0x04),
// Write enable
[44] =
LUT_SEQ(LUT_CMD_SDR, LUT_1_PAD, 0x06, LUT_STOP, LUT_1_PAD, 0x00),
// Erase 4-KiB sector
[52] =
LUT_SEQ(LUT_CMD_SDR, LUT_1_PAD, 0x20, LUT_RADDR_SDR, LUT_1_PAD, 0x18),
// Erase 64-KiB block
[64] =
LUT_SEQ(LUT_CMD_SDR, LUT_1_PAD, 0xd8, LUT_RADDR_SDR, LUT_1_PAD, 0x18),
// Program page
[68] =
LUT_SEQ(LUT_CMD_SDR, LUT_1_PAD, 0x02, LUT_RADDR_SDR, LUT_1_PAD, 0x18),
[69] =
LUT_SEQ(LUT_WRITE_SDR, LUT_1_PAD, 0x04, LUT_STOP, LUT_1_PAD, 0x00),
// Erase chip
[76] =
LUT_SEQ(LUT_CMD_SDR, LUT_1_PAD, 0x60, LUT_STOP, LUT_1_PAD, 0x00),
[112] = 256, // Page size
[113] = 4U * 1024U, // Sector size
[114] = 1, // IP-command serial clock
[116] = 64U * 1024U, // Block size
};
typedef struct __attribute__((packed)) {
uint8_t version;
uint16_t length;
uint8_t tag;
uint32_t flags;
uint16_t software_version;
uint8_t fuse_version;
uint8_t image_count;
uint16_t signature_block_offset;
uint16_t reserved;
} container_header_t;
typedef struct __attribute__((packed)) {
uint32_t offset;
uint32_t size;
uint32_t load_address;
uint32_t reserved1;
uint32_t entry_address;
uint32_t reserved2;
uint32_t flags;
uint32_t metadata;
uint8_t hash[64];
uint8_t iv[32];
} container_image_t;
typedef struct __attribute__((packed)) {
uint8_t version;
uint16_t length;
uint8_t tag;
uint16_t certificate_offset;
uint16_t srk_table_offset;
uint16_t signature_offset;
uint16_t blob_offset;
uint32_t reserved;
} signature_block_t;
typedef struct __attribute__((packed)) {
container_header_t header;
container_image_t image[1];
signature_block_t signature;
} boot_container_t;
extern const uint32_t _image_offset[];
extern const uint32_t _image_size[];
extern const uint32_t _image_loadaddr[];
extern const uint32_t Reset_Handler[];
// RT1180 Program Image Container. The ROM expects it at flash offset 0x1000
__attribute__((section(".boot_hdr.container"), used))
const boot_container_t container_data = {
.header = {
.version = 0,
.length = sizeof(boot_container_t),
.tag = 0x87,
.flags = 0,
.software_version = 0,
.fuse_version = 0,
.image_count = 1,
.signature_block_offset =
sizeof(container_header_t) + sizeof(container_image_t),
.reserved = 0,
},
.image = {{
.offset = (uint32_t) _image_offset,
.size = (uint32_t) _image_size,
.load_address = (uint32_t) _image_loadaddr,
.reserved1 = 0,
.entry_address = (uint32_t) Reset_Handler,
.reserved2 = 0,
.flags = 0x00000213, // Plain SHA-512 CM33 executable image
.metadata = 0,
.hash = {0},
.iv = {0},
}},
.signature = {
.version = 0,
.length = sizeof(signature_block_t),
.tag = 0x90,
.certificate_offset = 0,
.srk_table_offset = 0,
.signature_offset = 0,
.blob_offset = 0,
.reserved = 0,
},
};
+125
View File
@@ -0,0 +1,125 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "hal.h"
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now) {
uint64_t diff = now - *t; // Wrap-safe elapsed time since last expiry
if (period == 0) return false; // Avoid division by zero
if (diff < period) return false; // Period has not elapsed yet
*t += (diff / period) * period; // Preserve cadence, skip missed periods
return true;
}
static volatile uint64_t s_ticks; // Milliseconds since boot
void SysTick_Handler(void) { // SyStick IRQ handler, triggered every 1ms
s_ticks++;
}
uint64_t hal_get_tick(void) {
return s_ticks;
};
uint32_t SystemCoreClock = SYS_FREQUENCY;
void SystemInit(void) { // Called automatically by startup code
hal_system_init(); // Enable FPU
hal_clock_init();
SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms
}
void ExitRun0Mode(void) {
}
struct stat;
__attribute__((weak)) int _fstat(int fd, struct stat *st) {
(void) fd, (void) st;
return -1;
}
extern unsigned char _end[]; // End of data section, start of heap. See link.ld
static unsigned char *s_current_heap_end = _end;
size_t hal_ram_used(void) {
return (size_t) (s_current_heap_end - _end);
}
size_t hal_ram_free(void) {
unsigned char endofstack;
return (size_t) (&endofstack - s_current_heap_end);
}
void *_sbrk(int incr) {
unsigned char *prev_heap;
unsigned char *heap_end = (unsigned char *) ((size_t) &heap_end - 256);
prev_heap = s_current_heap_end;
// Check how much space we got from the heap end to the stack end
if (s_current_heap_end + incr > heap_end) return (void *) -1;
s_current_heap_end += incr;
return prev_heap;
}
__attribute__((weak)) int _open(const char *path) {
(void) path;
return -1;
}
__attribute__((weak)) int _close(int fd) {
(void) fd;
return -1;
}
__attribute__((weak)) int _isatty(int fd) {
(void) fd;
return 1;
}
__attribute__((weak)) int _lseek(int fd, int ptr, int dir) {
(void) fd, (void) ptr, (void) dir;
return 0;
}
__attribute__((weak)) void _exit(int status) {
(void) status;
for (;;) asm volatile("BKPT #0");
}
__attribute__((weak)) void _kill(int pid, int sig) {
(void) pid, (void) sig;
}
__attribute__((weak)) int _getpid(void) {
return -1;
}
__attribute__((weak)) int _write(int fd, char *ptr, int len) {
(void) fd, (void) ptr, (void) len;
return -1;
}
__attribute__((weak)) int _read(int fd, char *ptr, int len) {
(void) fd, (void) ptr, (void) len;
return -1;
}
__attribute__((weak)) int _link(const char *a, const char *b) {
(void) a, (void) b;
return -1;
}
__attribute__((weak)) int _unlink(const char *a) {
(void) a;
return -1;
}
__attribute__((weak)) int _stat(const char *path, struct stat *st) {
(void) path, (void) st;
return -1;
}
__attribute__((weak)) int mkdir(const char *path, int mode) {
(void) path, (void) mode;
return -1;
}
__attribute__((weak)) void _init(void) {
}
+662
View File
@@ -0,0 +1,662 @@
// https://www.nxp.com/docs/en/reference-manual/IMXRT1180RM.pdf
// https://www.nxp.com/docs/en/data-sheet/IMXRT1180EC.pdf
// https://www.mouser.com/catalog/specsheets/NXP_0-28-2026_FRDM-IMXRT1186.pdf
#pragma once
#include "MIMXRT1186_cm33.h"
#include "fsl_clock.h"
#include "fsl_ele_base_api.h"
#include "fsl_trdc.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define PIN(bank, num) ((((bank) - '0') << 8) | (num))
#define PINNO(pin) ((pin) & 255)
#define PINBANK(pin) ((pin) >> 8)
#define HAL_DEBUG_UART_CLK_ROOT kCLOCK_Root_Lpuart0102
#define HAL_DEBUG_UART_CLK 24000000UL
#define HAL_DEBUG_UART_TX_PIN PIN('1', 8) // GPIO_AON_08, ball A7
#define HAL_DEBUG_UART_RX_PIN PIN('1', 9) // GPIO_AON_09, ball B12
#define SYS_FREQUENCY 240000000UL
void hal_init(void);
size_t hal_ram_free(void);
size_t hal_ram_used(void);
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now);
uint64_t hal_get_tick(void);
static inline void spin(volatile uint32_t count) {
while (count--) (void) 0;
}
enum { HAL_GPIO_MODE_INPUT, HAL_GPIO_MODE_OUTPUT };
enum { HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_OTYPE_OPEN_DRAIN };
enum { HAL_GPIO_SPEED_LOW, HAL_GPIO_SPEED_HIGH };
enum { HAL_GPIO_PULL_NONE, HAL_GPIO_PULL_UP, HAL_GPIO_PULL_DOWN };
static void hal_flexspi2_clock_config(uint8_t src, uint32_t divider)
__attribute__((section(".iram")));
static void hal_flexspi2_clock_config(uint8_t src, uint32_t divider) {
const clock_root_t root = kCLOCK_Root_Flexspi2;
const clock_lpcg_t lpcg = kCLOCK_Flexspi2;
FLEXSPI_Type *base = FLEXSPI2;
uint32_t status, last_status, retry;
if ((CCM->CLOCK_ROOT[root].CONTROL & CCM_CLOCK_ROOT_CONTROL_MUX_MASK) ==
CCM_CLOCK_ROOT_CONTROL_MUX(src) &&
(CCM->CLOCK_ROOT[root].CONTROL & CCM_CLOCK_ROOT_CONTROL_DIV_MASK) ==
CCM_CLOCK_ROOT_CONTROL_DIV(divider - 1U)) {
return;
}
// BOARD_DeinitFlash(): flush/disable the CM33 cache, then stop FlexSPI2.
if ((XCACHE_PC->CCR & XCACHE_CCR_ENCACHE_MASK) != 0U) {
XCACHE_PC->CCR |=
XCACHE_CCR_PUSHW0_MASK | XCACHE_CCR_PUSHW1_MASK | XCACHE_CCR_GO_MASK;
while ((XCACHE_PC->CCR & XCACHE_CCR_GO_MASK) != 0U) (void) 0;
XCACHE_PC->CCR &=
~(XCACHE_CCR_PUSHW0_MASK | XCACHE_CCR_PUSHW1_MASK |
XCACHE_CCR_ENCACHE_MASK);
__ISB();
__DSB();
}
base->MCR0 &= ~FLEXSPI_MCR0_MDIS_MASK;
while ((base->STS0 & (FLEXSPI_STS0_ARBIDLE_MASK |
FLEXSPI_STS0_SEQIDLE_MASK)) !=
(FLEXSPI_STS0_ARBIDLE_MASK | FLEXSPI_STS0_SEQIDLE_MASK)) {
(void) 0;
}
base->MCR0 |= FLEXSPI_MCR0_MDIS_MASK;
CCM->LPCG[lpcg].DIRECT &= ~CCM_LPCG_DIRECT_ON_MASK;
__DSB();
__ISB();
while ((CCM->LPCG[lpcg].STATUS0 & CCM_LPCG_STATUS0_ON_MASK) != 0U)
(void) 0;
CCM->CLOCK_ROOT[root].CONTROL =
CCM_CLOCK_ROOT_CONTROL_MUX(src) |
CCM_CLOCK_ROOT_CONTROL_DIV(divider - 1U);
__DSB();
__ISB();
(void) CCM->CLOCK_ROOT[root].CONTROL;
CCM->LPCG[lpcg].DIRECT |= CCM_LPCG_DIRECT_ON_MASK;
__DSB();
__ISB();
while ((CCM->LPCG[lpcg].STATUS0 & CCM_LPCG_STATUS0_ON_MASK) == 0U)
(void) 0;
// BOARD_InitFlash(): restore the reference project's AHB buffers and DLL.
if ((base->AHBCR & FLEXSPI_AHBCR_PREFETCHEN_MASK) != 0U) {
base->AHBRXBUFCR0[0] =
FLEXSPI_AHBRXBUFCR0_PREFETCHEN_MASK |
FLEXSPI_AHBRXBUFCR0_PRIORITY(4U) |
FLEXSPI_AHBRXBUFCR0_MSTRID(0U) |
FLEXSPI_AHBRXBUFCR0_BUFSZ(512U / 8U); // NETC
base->AHBRXBUFCR0[1] =
FLEXSPI_AHBRXBUFCR0_PREFETCHEN_MASK |
FLEXSPI_AHBRXBUFCR0_PRIORITY(7U) |
FLEXSPI_AHBRXBUFCR0_MSTRID(8U) |
FLEXSPI_AHBRXBUFCR0_BUFSZ(512U / 8U); // CM33
base->AHBRXBUFCR0[2] =
FLEXSPI_AHBRXBUFCR0_PREFETCHEN_MASK |
FLEXSPI_AHBRXBUFCR0_PRIORITY(7U) |
FLEXSPI_AHBRXBUFCR0_MSTRID(9U) |
FLEXSPI_AHBRXBUFCR0_BUFSZ(512U / 8U); // CM7
for (uint32_t i = 3U; i < FLEXSPI_AHBRXBUFCR0_COUNT; i++) {
base->AHBRXBUFCR0[i] =
FLEXSPI_AHBRXBUFCR0_PREFETCHEN_MASK |
FLEXSPI_AHBRXBUFCR0_BUFSZ(0U);
}
}
base->DLLCR[0] = 1U;
base->MCR0 &= ~FLEXSPI_MCR0_MDIS_MASK;
base->MCR0 |= FLEXSPI_MCR0_SWRESET_MASK;
while ((base->MCR0 & FLEXSPI_MCR0_SWRESET_MASK) != 0U) (void) 0;
if ((base->DLLCR[0] & FLEXSPI_DLLCR_DLLEN_MASK) != 0U) {
last_status = base->STS2;
retry = 10U;
do {
status = base->STS2;
if ((status &
(FLEXSPI_STS2_AREFLOCK_MASK | FLEXSPI_STS2_ASLVLOCK_MASK)) ==
(FLEXSPI_STS2_AREFLOCK_MASK | FLEXSPI_STS2_ASLVLOCK_MASK)) {
retry = 100U;
break;
} else if (status == last_status) {
retry--;
} else {
retry = 10U;
last_status = status;
}
} while (retry > 0U);
while (retry-- > 0U) __NOP(); // ERR011377
}
if ((XCACHE_PC->CCR & XCACHE_CCR_ENCACHE_MASK) == 0U) {
XCACHE_PC->CCR = XCACHE_CCR_INVW1_MASK | XCACHE_CCR_INVW0_MASK;
XCACHE_PC->CCR |= XCACHE_CCR_GO_MASK;
while ((XCACHE_PC->CCR & XCACHE_CCR_GO_MASK) != 0U) (void) 0;
XCACHE_PC->CCR = XCACHE_CCR_ENCACHE_MASK;
__ISB();
__DSB();
}
}
static inline void hal_clock_root(clock_root_t root, uint8_t mux, uint8_t div) {
const clock_root_config_t config = {
.clockOff = false,
.mux = mux,
.div = div,
};
CLOCK_SetRootClock(root, &config);
}
static inline void hal_edgelock_clock_config(uint8_t mux, uint8_t div) {
if (CLOCK_GetRootClockDiv(kCLOCK_Root_Edgelock) != div ||
CLOCK_GetRootClockMux(kCLOCK_Root_Edgelock) != mux) {
status_t status;
do {
status = ELE_BaseAPI_ClockChangeStart(MU_RT_S3MUA);
} while (status != kStatus_Success);
hal_clock_root(kCLOCK_Root_Edgelock, mux, div);
uint8_t mhz =
(uint8_t) (CLOCK_GetRootClockFreq(kCLOCK_Root_Edgelock) / 1000000UL);
do {
status = ELE_BaseAPI_ClockChangeFinish(MU_RT_S3MUA, mhz, 0U);
} while (status != kStatus_Success);
}
}
static inline void hal_clock_init(void) {
const clock_sys_pll1_config_t sys_pll1 = {
.pllDiv2En = true,
.pllDiv5En = true,
.ss = NULL,
.ssEnable = false,
};
const clock_sys_pll2_config_t sys_pll2 = {
.mfd = 268435455U,
.ss = NULL,
.ssEnable = false,
};
CLOCK_OSC_EnableOscRc400M();
hal_clock_root(kCLOCK_Root_M33, kCLOCK_M33_ClockRoot_MuxOscRc400M, 2U);
CLOCK_OSC_Set1MHzOutputBehavior(kCLOCK_1MHzOutEnableFreeRunning1Mhz);
CLOCK_OSC_EnableOscRc24M(true);
ANADIG_OSC->OSC_24M_CTRL |=
ANADIG_OSC_OSC_24M_CTRL_OSC_EN_MASK |
ANADIG_OSC_OSC_24M_CTRL_LP_EN_MASK;
ANADIG_OSC->OSC_24M_CTRL &=
~(ANADIG_OSC_OSC_24M_CTRL_BYPASS_EN_MASK |
ANADIG_OSC_OSC_24M_CTRL_OSC_24M_GATE_MASK);
while ((ANADIG_OSC->OSC_24M_CTRL &
ANADIG_OSC_OSC_24M_CTRL_OSC_24M_STABLE_MASK) == 0U) {
(void) 0;
}
// The image is linked at 0x04000000 and executes through FlexSPI2.
hal_flexspi2_clock_config(0U, 1U); // OSC_RC_24M while PLLs change
CLOCK_InitSysPll1(&sys_pll1);
CLOCK_InitSysPll2(&sys_pll2);
CLOCK_InitSysPll3();
CLOCK_InitPfd(kCLOCK_PllSys3, kCLOCK_Pfd2, 22U);
CLOCK_InitPfd(kCLOCK_PllSys3, kCLOCK_Pfd3, 18U);
hal_clock_root(kCLOCK_Root_M33, kCLOCK_M33_ClockRoot_MuxSysPll3Out, 2U);
hal_edgelock_clock_config(kCLOCK_EDGELOCK_ClockRoot_MuxOscRc400M, 2U);
hal_clock_root(kCLOCK_Root_Bus_Aon,
kCLOCK_BUS_AON_ClockRoot_MuxSysPll2Out, 4U);
hal_clock_root(kCLOCK_Root_Bus_Wakeup,
kCLOCK_BUS_WAKEUP_ClockRoot_MuxSysPll2Out, 4U);
hal_clock_root(kCLOCK_Root_Wakeup_Axi,
kCLOCK_WAKEUP_AXI_ClockRoot_MuxSysPll3Out, 2U);
hal_clock_root(kCLOCK_Root_M33_Systick,
kCLOCK_M33_SYSTICK_ClockRoot_MuxOsc24MOut, 240U);
hal_flexspi2_clock_config(
kCLOCK_FLEXSPI2_ClockRoot_MuxSysPll3Pfd2, 4U);
hal_clock_root(kCLOCK_Root_Lpuart0102,
kCLOCK_LPUART0102_ClockRoot_MuxSysPll3Div2, 10U);
hal_clock_root(kCLOCK_Root_Enet,
kCLOCK_ENET_ClockRoot_MuxSysPll1Div2, 4U);
hal_clock_root(kCLOCK_Root_Tmr_1588,
kCLOCK_TMR_1588_ClockRoot_MuxSysPll3Out, 2U);
hal_clock_root(kCLOCK_Root_Netc,
kCLOCK_NETC_ClockRoot_MuxSysPll3Pfd3, 2U);
hal_clock_root(kCLOCK_Root_Mac0,
kCLOCK_MAC0_ClockRoot_MuxSysPll1Div2, 4U);
hal_clock_root(kCLOCK_Root_Mac1,
kCLOCK_MAC1_ClockRoot_MuxSysPll1Div2, 4U);
hal_clock_root(kCLOCK_Root_Mac2,
kCLOCK_MAC2_ClockRoot_MuxSysPll1Div2, 4U);
hal_clock_root(kCLOCK_Root_Mac3,
kCLOCK_MAC3_ClockRoot_MuxSysPll1Div2, 4U);
hal_clock_root(kCLOCK_Root_Mac4,
kCLOCK_MAC4_ClockRoot_MuxSysPll1Div2, 10U);
BLK_CTRL_WAKEUPMIX->NETC_PORT_MISC_CFG &=
~(BLK_CTRL_WAKEUPMIX_NETC_PORT_MISC_CFG_PORT0_RMII_REF_CLK_DIR_MASK |
BLK_CTRL_WAKEUPMIX_NETC_PORT_MISC_CFG_PORT1_RMII_REF_CLK_DIR_MASK |
BLK_CTRL_WAKEUPMIX_NETC_PORT_MISC_CFG_PORT2_RMII_REF_CLK_DIR_MASK |
BLK_CTRL_WAKEUPMIX_NETC_PORT_MISC_CFG_PORT3_RMII_REF_CLK_DIR_MASK |
BLK_CTRL_WAKEUPMIX_NETC_PORT_MISC_CFG_PORT4_RMII_REF_CLK_DIR_MASK);
BLK_CTRL_WAKEUPMIX->NETC_PORT_MISC_CFG |=
BLK_CTRL_WAKEUPMIX_NETC_PORT_MISC_CFG_TMR_EXT_CLK_SEL_MASK;
SystemCoreClock = CLOCK_GetRootClockFreq(kCLOCK_Root_M33);
}
static inline void hal_rtwdog_disable(RTWDOG_Type *wdog) {
if ((wdog->CS & RTWDOG_CS_CMD32EN_MASK) != 0U) {
wdog->CNT = 0xD928C520U;
} else {
wdog->CNT = 0xC520U;
wdog->CNT = 0xD928U;
}
wdog->TOVAL = 0xFFFFU;
wdog->CS = (wdog->CS & ~RTWDOG_CS_EN_MASK) | RTWDOG_CS_UPDATE_MASK;
}
static inline void hal_system_init(void) {
extern uint32_t _siram, _eiram, _siiram;
volatile uint32_t *dst = &_siram;
const uint32_t *src = &_siiram;
while (dst < &_eiram) *dst++ = *src++;
SCB->CPACR |= (3UL << 10U * 2U) | (3UL << 11U * 2U);
hal_rtwdog_disable(RTWDOG1);
hal_rtwdog_disable(RTWDOG2);
hal_rtwdog_disable(RTWDOG3);
hal_rtwdog_disable(RTWDOG4);
hal_rtwdog_disable(RTWDOG5);
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
if ((XCACHE_PC->CCR & XCACHE_CCR_ENCACHE_MASK) == 0U) {
XCACHE_PC->CCR = XCACHE_CCR_INVW1_MASK | XCACHE_CCR_INVW0_MASK;
XCACHE_PC->CCR |= XCACHE_CCR_GO_MASK;
while ((XCACHE_PC->CCR & XCACHE_CCR_GO_MASK) != 0U) (void) 0;
XCACHE_PC->CCR = XCACHE_CCR_ENCACHE_MASK;
}
SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk;
__DSB();
__ISB();
}
static inline RGPIO_Type *hal_gpio_bank(uint16_t pin) {
static RGPIO_Type *const g[] = RGPIO_BASE_PTRS;
return g[PINBANK(pin)];
}
static inline void hal_gpio_mux_config(uint16_t index, uint8_t af) {
IOMUXC->SW_MUX_CTL_PAD[index] =
IOMUXC_SW_MUX_CTL_PAD_MUX_MODE(af);
}
static inline void hal_periph_mux_config(uint16_t index, uint8_t input) {
IOMUXC->SELECT_INPUT[index] = input;
}
static inline void hal_gpio_aon_mux_config(uint16_t index, uint8_t af) {
IOMUXC_AON->SW_MUX_CTL_PAD[index] =
IOMUXC_AON_SW_MUX_CTL_PAD_MUX_MODE(af);
}
static inline void hal_gpio_pad_config(uint16_t index, uint8_t type,
uint8_t speed, uint8_t pull) {
if (index < kIOMUXC_SW_PAD_CTL_PAD_GPIO_AD_00 ||
index >= kIOMUXC_SW_PAD_CTL_PAD_GPIO_SD_B1_00) {
uint8_t pad_pull = 3U; // No pull
if (pull == HAL_GPIO_PULL_UP) pad_pull = 1U;
if (pull == HAL_GPIO_PULL_DOWN) pad_pull = 2U;
IOMUXC->SW_PAD_CTL_PAD[index] =
IOMUXC_SW_PAD_CTL_PAD_PDRV(speed == HAL_GPIO_SPEED_LOW) |
IOMUXC_SW_PAD_CTL_PAD_ODE(type == HAL_GPIO_OTYPE_OPEN_DRAIN) |
IOMUXC_SW_PAD_CTL_PAD_PULL(pad_pull);
} else {
bool dopull = pull != HAL_GPIO_PULL_NONE;
IOMUXC->SW_PAD_CTL_PAD[index] =
IOMUXC_SW_PAD_CTL_PAD_SRE(speed == HAL_GPIO_SPEED_LOW) |
IOMUXC_SW_PAD_CTL_PAD_DSE(speed == HAL_GPIO_SPEED_HIGH) |
IOMUXC_SW_PAD_CTL_PAD_ODE(type == HAL_GPIO_OTYPE_OPEN_DRAIN) |
IOMUXC_SW_PAD_CTL_PAD_PUE(dopull) |
IOMUXC_SW_PAD_CTL_PAD_PUS(pull == HAL_GPIO_PULL_UP);
}
}
static inline void hal_gpio_aon_pad_config(uint16_t index, uint8_t type,
uint8_t speed, uint8_t pull) {
bool dopull = pull != HAL_GPIO_PULL_NONE;
IOMUXC_AON->SW_PAD_CTL_PAD[index] =
IOMUXC_AON_SW_PAD_CTL_PAD_SRE(speed == HAL_GPIO_SPEED_LOW) |
IOMUXC_AON_SW_PAD_CTL_PAD_DSE(speed == HAL_GPIO_SPEED_HIGH) |
IOMUXC_AON_SW_PAD_CTL_PAD_ODE(type == HAL_GPIO_OTYPE_OPEN_DRAIN) |
IOMUXC_AON_SW_PAD_CTL_PAD_PUE(dopull) |
IOMUXC_AON_SW_PAD_CTL_PAD_PUS(pull == HAL_GPIO_PULL_UP);
}
static inline void hal_gpio_init(uint16_t pin, uint8_t mode, uint8_t type,
uint8_t speed, uint8_t pull) {
static const clock_ip_name_t clocks[] = GPIO_CLOCKS;
uint8_t bank = (uint8_t) PINBANK(pin);
uint8_t bit = (uint8_t) PINNO(pin);
uint32_t mask = 1UL << bit;
uint16_t index;
switch (bank) {
case 1:
index = kIOMUXC_AON_SW_MUX_CTL_PAD_GPIO_AON_00 + bit;
break;
case 2:
index = kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_B1_00 + bit;
break;
case 3:
index = kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_B1_32 + bit;
break;
case 4:
index = kIOMUXC_SW_MUX_CTL_PAD_GPIO_AD_00 + bit;
break;
case 5:
index = kIOMUXC_SW_MUX_CTL_PAD_GPIO_AD_32 + bit;
break;
case 6:
index = kIOMUXC_SW_MUX_CTL_PAD_GPIO_B1_00 + bit;
break;
default:
return;
}
RGPIO_Type *gpio = hal_gpio_bank(pin);
CLOCK_EnableClock(clocks[bank]);
CLOCK_EnableClock(bank == 1U ? kCLOCK_Iomuxc2 : kCLOCK_Iomuxc1);
gpio->ICR[bit] = RGPIO_ICR_ISF_MASK; // Disable and clear interrupts
gpio->PIDR &= ~mask; // Keep the digital input path enabled
if (mode == HAL_GPIO_MODE_INPUT) {
gpio->PDDR &= ~mask;
} else {
gpio->PCOR = mask; // Initial output level is low
gpio->PDDR |= mask;
}
if (bank == 1U) {
hal_gpio_aon_pad_config(index, type, speed, pull);
hal_gpio_aon_mux_config(index, 5U);
} else {
hal_gpio_pad_config(index, type, speed, pull);
hal_gpio_mux_config(index, 5U);
}
}
static inline void hal_gpio_input(uint16_t pin) {
hal_gpio_init(pin, HAL_GPIO_MODE_INPUT, HAL_GPIO_OTYPE_PUSH_PULL,
HAL_GPIO_SPEED_LOW, HAL_GPIO_PULL_NONE);
}
static inline void hal_gpio_output(uint16_t pin) {
hal_gpio_init(pin, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_OTYPE_PUSH_PULL,
HAL_GPIO_SPEED_LOW, HAL_GPIO_PULL_NONE);
}
static inline bool hal_gpio_read(uint16_t pin) {
RGPIO_Type *gpio = hal_gpio_bank(pin);
return (gpio->PDIR & (1UL << PINNO(pin))) != 0U;
}
static inline void hal_gpio_write(uint16_t pin, bool value) {
RGPIO_Type *gpio = hal_gpio_bank(pin);
uint32_t mask = 1UL << PINNO(pin);
if (value) {
gpio->PSOR = mask;
} else {
gpio->PCOR = mask;
}
}
static inline void hal_gpio_toggle(uint16_t pin) {
RGPIO_Type *gpio = hal_gpio_bank(pin);
gpio->PTOR = 1UL << PINNO(pin);
}
static inline void hal_uart_init(LPUART_Type *uart, unsigned long baud) {
if (uart != LPUART1 || baud == 0UL) return;
uint16_t tx = kIOMUXC_AON_SW_MUX_CTL_PAD_GPIO_AON_00 +
PINNO(HAL_DEBUG_UART_TX_PIN);
uint16_t rx = kIOMUXC_AON_SW_MUX_CTL_PAD_GPIO_AON_00 +
PINNO(HAL_DEBUG_UART_RX_PIN);
uint32_t sbr = HAL_DEBUG_UART_CLK / (16UL * baud);
if (sbr == 0U) sbr = 1U;
hal_clock_root(HAL_DEBUG_UART_CLK_ROOT,
kCLOCK_LPUART0102_ClockRoot_MuxSysPll3Div2, 10U);
CLOCK_EnableClock(kCLOCK_Iomuxc2);
CLOCK_EnableClock(kCLOCK_Lpuart1);
hal_gpio_aon_pad_config(tx, HAL_GPIO_OTYPE_PUSH_PULL,
HAL_GPIO_SPEED_HIGH, HAL_GPIO_PULL_NONE);
hal_gpio_aon_mux_config(tx, 0U);
hal_gpio_aon_pad_config(rx, HAL_GPIO_OTYPE_PUSH_PULL,
HAL_GPIO_SPEED_HIGH, HAL_GPIO_PULL_NONE);
hal_gpio_aon_mux_config(rx, 0U);
uart->GLOBAL |= LPUART_GLOBAL_RST_MASK;
uart->GLOBAL &= ~LPUART_GLOBAL_RST_MASK;
uart->FIFO = 0U; // Disable TX/RX FIFOs and FIFO interrupts
uart->WATER = 0U;
uart->BAUD = LPUART_BAUD_OSR(16U - 1U) | LPUART_BAUD_SBR(sbr);
uart->CTRL = LPUART_CTRL_TE_MASK | LPUART_CTRL_RE_MASK;
}
static inline void hal_uart_write_byte(LPUART_Type *uart, uint8_t byte) {
uart->DATA = byte;
while ((uart->STAT & LPUART_STAT_TDRE_MASK) == 0U) spin(1);
}
static inline void hal_uart_write_buf(LPUART_Type *uart, const char *buf,
size_t len) {
while (len-- > 0U) hal_uart_write_byte(uart, (uint8_t) *buf++);
}
static inline int hal_uart_read_ready(LPUART_Type *uart) {
return (int) (uart->STAT & LPUART_STAT_RDRF_MASK);
}
static inline uint8_t hal_uart_read_byte(LPUART_Type *uart) {
return (uint8_t) (uart->DATA & 0xFFU);
}
#define HAL_NETC_PHY0_ADDR 5U
#define HAL_NETC_PHY0_RESET PIN('1', 15)
static inline void hal_netc_trdc_region(uint8_t mrc, uint8_t domain,
uint32_t start, uint32_t end) {
const trdc_mrc_region_descriptor_config_t region = {
.memoryAccessControlSelect = 7U,
.startAddr = start,
.valid = true,
.nseEnable = false,
.endAddr = end,
.mrcIdx = mrc,
.domainIdx = domain,
.regionIdx = 0U,
};
TRDC_MrcSetRegionDescriptorConfig(TRDC2, &region);
}
// Open the security gates needed by the CPU and NETC
static inline void hal_netc_trdc_init(void) {
const trdc_processor_domain_assignment_t cm33 = {
.domainId = 2U,
.domainIdSelect = kTRDC_DidInput,
.pidDomainHitConfig = kTRDC_pidDomainHitNone0,
.pidMask = 0U,
.secureAttr = kTRDC_ForceSecure,
.pid = 0U,
.lock = false,
};
const trdc_processor_domain_assignment_t netc = {
.domainId = 10U,
.domainIdSelect = kTRDC_DidInput,
.pidDomainHitConfig = kTRDC_pidDomainHitNone0,
.pidMask = 0U,
.secureAttr = kTRDC_ForceSecure,
.pid = 0U,
.lock = false,
};
const trdc_memory_access_control_config_t netc_access = {
.secureUsrW = 1U,
.secureUsrR = 1U,
.securePrivW = 1U,
.securePrivR = 1U,
};
const trdc_memory_access_control_config_t cpu_access = {
.securePrivW = 1U,
.securePrivR = 1U,
};
ELE_BaseAPI_ReleaseRDC(MU_RT_S3MUA, 0x74U, 1U); // TRDC AON
ELE_BaseAPI_ReleaseRDC(MU_RT_S3MUA, 0x82U, 1U); // TRDC MEGA
ELE_BaseAPI_ReleaseRDC(MU_RT_S3MUA, 0x78U, 1U); // TRDC WAKEUP
TRDC_SetProcessorDomainAssignment(
TRDC1, (uint8_t) kTRDC1_MasterCM33, 0U, &cm33);
TRDC_SetProcessorDomainAssignment(
TRDC2, (uint8_t) kTRDC2_MasterNETC, 0U, &netc);
TRDC_MrcSetMemoryAccessConfig(TRDC2, &netc_access, 3U, 7U);
TRDC_MrcSetMemoryAccessConfig(TRDC2, &netc_access, 4U, 7U);
TRDC_MrcSetMemoryAccessConfig(TRDC2, &cpu_access, 6U, 7U);
hal_netc_trdc_region(3U, 10U, 0x20480000UL, 0x204FFFFFUL);
hal_netc_trdc_region(4U, 10U, 0x20500000UL, 0x2053FFFFUL);
hal_netc_trdc_region(6U, 2U, 0x60000000UL, 0x60FFFFFFUL);
}
static inline void hal_netc_pins_init(void) {
static const struct {
uint16_t index;
uint8_t af;
} muxes[] = {
{kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_B2_00, 4U}, // RX_CLK
{kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_B2_01, 4U}, // RX_DATA_2
{kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_B2_02, 4U}, // RX_DATA_3
{kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_B2_03, 4U}, // TX_DATA_2
{kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_B2_04, 4U}, // TX_DATA_3
{kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_B2_05, 3U}, // TX_DATA_0
{kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_B2_06, 3U}, // TX_DATA_1
{kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_B2_07, 3U}, // TX_EN
{kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_B2_08, 3U}, // TX_CLK
{kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_B2_09, 3U}, // RX_DATA_0
{kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_B2_10, 3U}, // RX_DATA_1
{kIOMUXC_SW_MUX_CTL_PAD_GPIO_EMC_B2_11, 3U}, // RX_EN
};
static const struct {
uint16_t index;
uint8_t input;
} inputs[] = {
{kIOMUXC_NETC_PINMUX_IPP_IND_ETH0_RX_CLK_SELECT_INPUT, 0U},
{kIOMUXC_NETC_PINMUX_IPP_IND_ETH0_RX_DV_SELECT_INPUT, 1U},
{kIOMUXC_NETC_PINMUX_IPP_IND_ETH0_RXD_SELECT_INPUT_0, 1U},
{kIOMUXC_NETC_PINMUX_IPP_IND_ETH0_RXD_SELECT_INPUT_1, 1U},
{kIOMUXC_NETC_PINMUX_IPP_IND_ETH0_RXD_SELECT_INPUT_2, 0U},
{kIOMUXC_NETC_PINMUX_IPP_IND_ETH0_RXD_SELECT_INPUT_3, 0U},
{kIOMUXC_NETC_PINMUX_IPP_IND_ETH0_TX_CLK_SELECT_INPUT, 1U},
};
CLOCK_EnableClock(kCLOCK_Iomuxc1);
CLOCK_EnableClock(kCLOCK_Iomuxc2);
hal_gpio_output(HAL_NETC_PHY0_RESET);
hal_gpio_write(HAL_NETC_PHY0_RESET, true);
hal_gpio_mux_config(kIOMUXC_SW_MUX_CTL_PAD_GPIO_AD_30, 7U); // EMDC
hal_gpio_pad_config(kIOMUXC_SW_PAD_CTL_PAD_GPIO_AD_30,
HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH,
HAL_GPIO_PULL_DOWN);
hal_gpio_mux_config(kIOMUXC_SW_MUX_CTL_PAD_GPIO_AD_31, 7U); // EMDIO
hal_gpio_pad_config(kIOMUXC_SW_PAD_CTL_PAD_GPIO_AD_31,
HAL_GPIO_OTYPE_OPEN_DRAIN, HAL_GPIO_SPEED_HIGH,
HAL_GPIO_PULL_DOWN);
hal_periph_mux_config(kIOMUXC_NETC_EMDIO_IN_SELECT_INPUT, 4U);
for (size_t i = 0; i < sizeof(muxes) / sizeof(muxes[0]); i++) {
hal_gpio_mux_config(muxes[i].index, muxes[i].af);
}
for (size_t i = 0; i < sizeof(inputs) / sizeof(inputs[0]); i++) {
hal_periph_mux_config(inputs[i].index, inputs[i].input);
}
}
static inline bool hal_netc_ierb_init(void) {
uint32_t timeout = 1000000UL;
BLK_CTRL_WAKEUPMIX->NETC_LINK_CFG[0] =
(BLK_CTRL_WAKEUPMIX->NETC_LINK_CFG[0] &
~BLK_CTRL_WAKEUPMIX_NETC_LINK_CFG_MII_PROT_MASK) |
BLK_CTRL_WAKEUPMIX_NETC_LINK_CFG_MII_PROT(2U);
if ((NETC_PRIV->NETCRR & NETC_PRIV_NETCRR_LOCK_MASK) != 0U) {
NETC_PRIV->NETCRR &= ~NETC_PRIV_NETCRR_LOCK_MASK;
while ((NETC_PRIV->NETCRR & NETC_PRIV_NETCRR_LOCK_MASK) != 0U &&
timeout > 0U) {
timeout--;
}
if ((NETC_PRIV->NETCRR & NETC_PRIV_NETCRR_LOCK_MASK) != 0U) return false;
}
// Attribute 1 permits MSI-X writes through the root-complex window.
NETC_IERB->ARRAY_NUM_RC[0].RCMSIAMQR =
(NETC_IERB->ARRAY_NUM_RC[0].RCMSIAMQR & ~(7UL << 27)) | (1UL << 27);
// RT1180's SoC-specific topology fixes link 0 to switch port 0, link 4 to
// ENETC0, and link 5 between switch port 4 and ENETC1.
NETC_IERB->L0BCR =
NETC_IERB_L0BCR_MDIO_PHYAD_PRTAD(HAL_NETC_PHY0_ADDR);
__DSB();
if ((NETC_PRIV->NETCSR & NETC_PRIV_NETCSR_ERROR_MASK) != 0U) return false;
NETC_PRIV->NETCRR |= NETC_PRIV_NETCRR_LOCK_MASK;
timeout = 1000000UL;
while ((NETC_PRIV->NETCSR & NETC_PRIV_NETCSR_STATE_MASK) != 0U &&
timeout > 0U) {
timeout--;
}
return (NETC_PRIV->NETCSR &
(NETC_PRIV_NETCSR_STATE_MASK | NETC_PRIV_NETCSR_ERROR_MASK)) == 0U;
}
static inline void hal_ethernet_init(void) {
hal_netc_trdc_init();
CLOCK_EnableClock(kCLOCK_Netc);
hal_netc_pins_init();
if (!hal_netc_ierb_init()) return;
hal_gpio_write(HAL_NETC_PHY0_RESET, false);
SDK_DelayAtLeastUs(10000U, SYS_FREQUENCY);
hal_gpio_write(HAL_NETC_PHY0_RESET, true);
SDK_DelayAtLeastUs(150000U, SYS_FREQUENCY);
NVIC_EnableIRQ(MSGINTR1_IRQn);
}
static inline void hal_rng_init(void) {
}
static inline uint32_t hal_rng_read(void) {
return 42;
}
@@ -0,0 +1,64 @@
ENTRY(Reset_Handler)
MEMORY {
boot_hdr (rx) : ORIGIN = 0x04000000, LENGTH = 0x0000b000
flash_irq (rx) : ORIGIN = 0x0400b000, LENGTH = 0x00000400
flash (rx) : ORIGIN = 0x0400b400, LENGTH = 0x00ff4c00
itcm (rwx) : ORIGIN = 0x0ffe0000, LENGTH = 128k
dtcm (rwx) : ORIGIN = 0x20000000, LENGTH = 128k
ocram1 (rw) : ORIGIN = 0x204c0000, LENGTH = 256k
}
__StackSize = 4k;
__StackTop = ORIGIN(dtcm) + LENGTH(dtcm);
__StackLimit = __StackTop - __StackSize;
SECTIONS {
.boot_hdr : {
FILL(0x00)
. = 0x0400; KEEP(*(.boot_hdr.conf))
. = 0x0800; KEEP(*(.boot_hdr.xmcd_data))
. = 0x1000; __boot_hdr_container__ = ABSOLUTE(.); KEEP(*(.boot_hdr.container))
. = 0xb000;
} > boot_hdr
.isr_vector ALIGN(1024) : { KEEP(*(.isr_vector)) } > flash_irq
.text : { *(.text .text.*) *(.rodata .rodata.*) *(.constdata .constdata.*) } > flash
.iram : { _siram = .; *(.iram .iram.*) . = ALIGN(4); _eiram = .; } > itcm AT > flash
_siiram = LOADADDR(.iram);
.data : {
__data_start__ = .;
*(.data SORT(.data.*)) *(.sdata SORT(.sdata.*))
. = ALIGN(4);
__data_end__ = .;
} > dtcm AT > flash
__data_flash_start = LOADADDR(.data);
.bss (NOLOAD) : {
__bss_start__ = .;
*(.bss SORT(.bss.*)) *(.sbss SORT(.sbss.*)) *(COMMON)
. = ALIGN(4);
__bss_end__ = .;
} > dtcm
_end = ALIGN(8);
/* Required by startup only; hal.c manages the dynamic heap from _end. */
__HeapBase = _end;
__HeapLimit = _end;
.eth_ram (NOLOAD) : {
. = ALIGN(64);
__eth_ram_start__ = .;
*(.eth_ram .eth_ram.*)
. = ALIGN(64);
__eth_ram_end__ = .;
} > ocram1
_image_size = LOADADDR(.data) + SIZEOF(.data) - ADDR(.isr_vector);
_image_loadaddr = ADDR(.isr_vector);
_image_offset = ADDR(.isr_vector) - __boot_hdr_container__;
}
@@ -0,0 +1,74 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
#include "hal.h"
#ifndef UART_DEBUG
#define UART_DEBUG LPUART1
#endif
#define LED1 PIN('2', 11) // User LED: GPIO_EMC_B1_11, RGPIO2.11
#define LED2 LED1
#define LED3 LED1
static void log_fn(char ch, void *param) {
hal_uart_write_buf(param, &ch, 1);
}
static void blink_task(void) {
static uint64_t blink_timer = 0;
if (hal_timer_expired(&blink_timer, 500, hal_get_tick())) {
hal_gpio_toggle(LED1);
}
}
uint64_t mg_millis(void) {
return hal_get_tick();
}
#if MG_ENABLE_CUSTOM_RANDOM
bool mg_random(void *buf, size_t len) {
for (size_t n = 0; n < len; n += sizeof(uint32_t)) {
uint32_t r = hal_rng_read();
memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r));
}
return true;
}
#endif
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), hal_get_tick());
} else {
mg_http_reply(c, 200, "", "Hi from Mongoose, tick %llu\n",
hal_get_tick());
}
}
}
int main(void) {
hal_clock_init();
hal_rng_init();
hal_gpio_output(LED1);
hal_gpio_output(LED2);
hal_gpio_output(LED3);
hal_uart_init(UART_DEBUG, 115200);
mg_log_set_fn(log_fn, UART_DEBUG);
hal_ethernet_init();
// Start a minimal web server
struct mg_mgr mgr;
mg_mgr_init(&mgr);
mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL);
for (;;) {
mg_mgr_poll(&mgr, 0);
blink_task();
}
return 0;
}
@@ -0,0 +1,21 @@
#define MG_ARCH MG_ARCH_ARMGCC
#define MG_ENABLE_TCPIP 1
#define MG_ENABLE_DRIVER_NETC 1
#define MG_OTA MG_OTA_RT1180
#define MG_IO_SIZE 256
#define MG_ENABLE_CUSTOM_MILLIS 1
#define MG_ETH_RAM __attribute__((section(".eth_ram")))
// For static IP configuration, define MG_TCPIP_{IP,MASK,GW}
// By default, those are set to zero, meaning that DHCP is used
//
// #define MG_TCPIP_IP MG_IPV4(192, 168, 0, 10)
// #define MG_TCPIP_GW MG_IPV4(192, 168, 0, 1)
// #define MG_TCPIP_MASK MG_IPV4(255, 255, 255, 0)
// Set custom MAC address. By default, it is randomly generated
// Using a build-time constant:
// #define MG_SET_MAC_ADDRESS(mac) do { mac[0] = 2; mac[1] = 3; mac[2] = 4; mac[3] = 5; mac[4] = 6; mac[5] = 7; } while (0)
//
@@ -0,0 +1 @@
See the example at https://github.com/mongoose-examples/frdm-mcxe247
@@ -0,0 +1,42 @@
# Environment setup: https://mongoose.ws/documentation/tutorials/tools/
CFLAGS = -W -Wall -Wextra -Werror -Wundef -Wshadow -Wdouble-promotion
CFLAGS += -Wformat-truncation -fno-common -Wno-conversion -Wno-sign-conversion
CFLAGS += -g3 -Os -ffunction-sections -fdata-sections
CFLAGS += -Wno-cast-function-type -Wno-old-style-declaration
CFLAGS += -Wno-unused-parameter
CFLAGS += -I. -Imongoose -Icmsis_core/CMSIS/Core/Include/
CFLAGS += -Icmsis_mcxe247/devices/MCXE247/ -Icmsis_mcxe247/devices/MCXE247/periph0/ -Icmsis_mcxe247/devices/MCXE247/drivers/
CFLAGS += -DCPU_MCXE247VLQ -DCPU_MCXE247VLQ_cm4
CFLAGS += -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard $(CFLAGS_EXTRA)
LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs=nano.specs -lc -lgcc -Wl,--gc-sections -Wl,-Map=$@.map
SOURCES = main.c hal.c mongoose/mongoose.c
SOURCES += cmsis_mcxe247/devices/MCXE247/gcc/startup_MCXE247.S
SOURCES += cmsis_mcxe247/devices/MCXE247/drivers/fsl_clock.c
CFLAGS += -D__ATOLLIC__ -D__STARTUP_CLEAR_BSS
all build: firmware.bin
mongoose/mongoose.c mongoose/mongoose.h:
mkdir -p mongoose
cp ../../../../mongoose.[ch] mongoose/
firmware.bin: firmware.elf
arm-none-eabi-objcopy -O binary $< $@
arm-none-eabi-size --format=berkeley $<
firmware.elf: cmsis_core cmsis_mcxe247 $(SOURCES) $(wildcard *.h) link.ld Makefile
arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@
cmsis_core:
git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@
cmsis_mcxe247:
curl -sL https://mcuxpresso.nxp.com/cmsis_pack/repo/NXP.MCXE247_DFP.26.06.00.pack -o NXP.MCXE247_DFP.26.06.00.pack
unzip -q NXP.MCXE247_DFP.26.06.00.pack -d $@
rm NXP.MCXE247_DFP.26.06.00.pack
clean:
rm -rf firmware.* cmsis_* mongoose/mongoose.*
+125
View File
@@ -0,0 +1,125 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "hal.h"
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now) {
uint64_t diff = now - *t; // Wrap-safe elapsed time since last expiry
if (period == 0) return false; // Avoid division by zero
if (diff < period) return false; // Period has not elapsed yet
*t += (diff / period) * period; // Preserve cadence, skip missed periods
return true;
}
static volatile uint64_t s_ticks; // Milliseconds since boot
void SysTick_Handler(void) { // SyStick IRQ handler, triggered every 1ms
s_ticks++;
}
uint64_t hal_get_tick(void) {
return s_ticks;
};
uint32_t SystemCoreClock = SYS_FREQUENCY;
void SystemInit(void) { // Called automatically by startup code
hal_system_init(); // Enable FPU
hal_clock_init();
SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms
}
void ExitRun0Mode(void) {
}
struct stat;
__attribute__((weak)) int _fstat(int fd, struct stat *st) {
(void) fd, (void) st;
return -1;
}
extern unsigned char _end[]; // End of data section, start of heap. See link.ld
static unsigned char *s_current_heap_end = _end;
size_t hal_ram_used(void) {
return (size_t) (s_current_heap_end - _end);
}
size_t hal_ram_free(void) {
unsigned char endofstack;
return (size_t) (&endofstack - s_current_heap_end);
}
void *_sbrk(int incr) {
unsigned char *prev_heap;
unsigned char *heap_end = (unsigned char *) ((size_t) &heap_end - 256);
prev_heap = s_current_heap_end;
// Check how much space we got from the heap end to the stack end
if (s_current_heap_end + incr > heap_end) return (void *) -1;
s_current_heap_end += incr;
return prev_heap;
}
__attribute__((weak)) int _open(const char *path) {
(void) path;
return -1;
}
__attribute__((weak)) int _close(int fd) {
(void) fd;
return -1;
}
__attribute__((weak)) int _isatty(int fd) {
(void) fd;
return 1;
}
__attribute__((weak)) int _lseek(int fd, int ptr, int dir) {
(void) fd, (void) ptr, (void) dir;
return 0;
}
__attribute__((weak)) void _exit(int status) {
(void) status;
for (;;) asm volatile("BKPT #0");
}
__attribute__((weak)) void _kill(int pid, int sig) {
(void) pid, (void) sig;
}
__attribute__((weak)) int _getpid(void) {
return -1;
}
__attribute__((weak)) int _write(int fd, char *ptr, int len) {
(void) fd, (void) ptr, (void) len;
return -1;
}
__attribute__((weak)) int _read(int fd, char *ptr, int len) {
(void) fd, (void) ptr, (void) len;
return -1;
}
__attribute__((weak)) int _link(const char *a, const char *b) {
(void) a, (void) b;
return -1;
}
__attribute__((weak)) int _unlink(const char *a) {
(void) a;
return -1;
}
__attribute__((weak)) int _stat(const char *path, struct stat *st) {
(void) path, (void) st;
return -1;
}
__attribute__((weak)) int mkdir(const char *path, int mode) {
(void) path, (void) mode;
return -1;
}
__attribute__((weak)) void _init(void) {
}
+289
View File
@@ -0,0 +1,289 @@
// Copyright (c) 2023 Cesanta Software Limited
// All rights reserved
// MCXE24x Series Reference Manual (MCXE24XRM)
// FRDM-MCXE247 Board User Manual: UM12286
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "MCXE247.h"
#include <fsl_clock.h>
#define BIT(x) (1UL << (x))
#define CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set))
#define PIN(bank, num) ((((bank) - 'A') << 8) | (num))
#define PINNO(pin) (pin & 255)
#define PINBANK(pin) (pin >> 8)
void hal_init(void);
size_t hal_ram_free(void);
size_t hal_ram_used(void);
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now);
uint64_t hal_get_tick(void);
static inline void spin(volatile uint32_t count) {
while (count--)
(void)0;
}
#define SYS_FREQUENCY 80000000UL
enum { HAL_GPIO_MODE_INPUT, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_MODE_AF };
enum { HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_OTYPE_OPEN_DRAIN };
enum { HAL_GPIO_SPEED_LOW, HAL_GPIO_SPEED_HIGH };
enum { HAL_GPIO_PULL_NONE, HAL_GPIO_PULL_DOWN, HAL_GPIO_PULL_UP };
static inline GPIO_Type *hal_gpio_bank(uint16_t pin) {
static GPIO_Type *const g[] = GPIO_BASE_PTRS;
return g[PINBANK(pin)];
}
static inline void hal_gpio_init(uint16_t pin, uint8_t mode, uint8_t type,
uint8_t speed, uint8_t pull, uint8_t af) {
static PORT_Type *const p[] = PORT_BASE_PTRS;
PORT_Type *port = p[PINBANK(pin)];
GPIO_Type *gpio = hal_gpio_bank(pin);
uint32_t mask = (uint32_t)BIT(PINNO(pin));
bool dopull = pull > 0;
if (gpio == GPIOA) {
CLOCK_EnableClock(kCLOCK_PortA);
} else if (gpio == GPIOB) {
CLOCK_EnableClock(kCLOCK_PortB);
} else if (gpio == GPIOC) {
CLOCK_EnableClock(kCLOCK_PortC);
} else if (gpio == GPIOD) {
CLOCK_EnableClock(kCLOCK_PortD);
} else if (gpio == GPIOE) {
CLOCK_EnableClock(kCLOCK_PortE);
}
if (dopull)
--pull;
port->PCR[PINNO(pin)] = PORT_PCR_MUX(af) | PORT_PCR_DSE(1) |
PORT_PCR_PE(dopull) | PORT_PCR_PS(pull);
if (mode == HAL_GPIO_MODE_INPUT) {
gpio->PDDR &= ~mask;
} else if (mode == HAL_GPIO_MODE_OUTPUT) {
gpio->PDDR |= mask;
}
(void) type, (void) speed;
}
static inline void hal_gpio_input(uint16_t pin) {
hal_gpio_init(pin, HAL_GPIO_MODE_INPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_LOW,
HAL_GPIO_PULL_NONE, 1);
}
static inline void hal_gpio_output(uint16_t pin) {
hal_gpio_init(pin, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_LOW,
HAL_GPIO_PULL_NONE, 1);
}
static inline bool hal_gpio_read(uint16_t pin) {
GPIO_Type *gpio = hal_gpio_bank(pin);
return gpio->PDIR & PINNO(pin);
}
static inline void hal_gpio_write(uint16_t pin, bool value) {
GPIO_Type *gpio = hal_gpio_bank(pin);
if (value) {
gpio->PDOR |= (1 << PINNO(pin));
} else {
gpio->PDOR &= ~(1 << PINNO(pin));
}
}
static inline void hal_gpio_toggle(uint16_t pin) {
GPIO_Type *gpio = hal_gpio_bank(pin);
uint32_t mask = (uint32_t)BIT(PINNO(pin));
gpio->PTOR = mask;
}
static inline void hal_uart_init(LPUART_Type *uart, unsigned long baud) {
uint8_t af = 3; // Alternate function
uint16_t pr = 0, pt = 0; // pins
uint32_t freq = 8000000; // 8MHz (System Oscillator clock)
uint32_t lpuart_clk;
if (uart == LPUART2)
lpuart_clk = kCLOCK_Lpuart2, pt = PIN('E', 12), pr = PIN('D', 17);
if (uart == LPUART1)
lpuart_clk = kCLOCK_Lpuart1, pt = PIN('C', 9), pr = PIN('C', 8);
CLOCK_SetIpSrc(lpuart_clk, kCLOCK_IpSrcSysOscAsync);
hal_gpio_init(pt, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_LOW,
HAL_GPIO_PULL_UP, af);
hal_gpio_init(pr, HAL_GPIO_MODE_INPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_LOW,
HAL_GPIO_PULL_UP, af);
CLOCK_EnableClock(lpuart_clk);
uart->GLOBAL |= LPUART_GLOBAL_RST_MASK; // reset, CTRL = 0, defaults
uart->GLOBAL &= ~LPUART_GLOBAL_RST_MASK;
// use an oversample ratio of 23
CLRSET(uart->BAUD,
LPUART_BAUD_OSR_MASK | LPUART_BAUD_SBR_MASK | LPUART_BAUD_SBNS_MASK,
LPUART_BAUD_OSR(23 - 1) | LPUART_BAUD_SBR(freq / (23 * baud)));
CLRSET(uart->CTRL,
LPUART_CTRL_PE_MASK | LPUART_CTRL_M_MASK | LPUART_CTRL_ILT_MASK |
LPUART_CTRL_IDLECFG_MASK,
LPUART_CTRL_IDLECFG(1) | LPUART_CTRL_ILT(1) |
LPUART_BAUD_SBNS(0)); // no parity, idle 2 chars after 1 stop bit
uart->CTRL |= LPUART_CTRL_TE_MASK | LPUART_CTRL_RE_MASK;
}
static inline void hal_uart_write_byte(LPUART_Type *uart, uint8_t byte) {
uart->DATA = byte;
while ((uart->STAT & LPUART_STAT_TDRE_MASK) == 0)
spin(1);
}
static inline void hal_uart_write_buf(LPUART_Type *uart, char *buf, size_t len) {
while (len-- > 0)
hal_uart_write_byte(uart, *(uint8_t *)buf++);
}
static inline void hal_ethernet_init(void) {
// enable peripheral clock
uint32_t reg = (*(volatile uint32_t *)(uint32_t)kCLOCK_Enet);
reg = (reg & ~(PCC_CLKCFG_PCS_MASK | PCC_CLKCFG_FRAC_MASK | PCC_CLKCFG_PCD_MASK)) | PCC_CLKCFG_PCS(6) |
PCC_CLKCFG_PCD(1) | PCC_CLKCFG_FRAC(1);
(*(volatile uint32_t *)(uint32_t)kCLOCK_Enet) = reg & ~PCC_CLKCFG_CGC_MASK;
(*(volatile uint32_t *)(uint32_t)kCLOCK_Enet) = reg;
CLOCK_EnableClock(kCLOCK_Enet);
// init pins
hal_gpio_init(PIN('C', 3), HAL_GPIO_MODE_OUTPUT, 0, 0, HAL_GPIO_PULL_NONE, 1); // PHY_RST
hal_gpio_write(PIN('C', 3), 1);
hal_gpio_init(PIN('B', 4), HAL_GPIO_MODE_AF, 0, 0, HAL_GPIO_PULL_NONE, 5); // MII_RMII_MDIO
hal_gpio_init(PIN('C', 0), HAL_GPIO_MODE_AF, 0, 0, HAL_GPIO_PULL_NONE, 4); // MII_RMII_RXD1
hal_gpio_init(PIN('C', 1), HAL_GPIO_MODE_AF, 0, 0, HAL_GPIO_PULL_NONE, 5); // MII_RMII_RXD0
hal_gpio_init(PIN('C', 17), HAL_GPIO_MODE_AF, 0, 0, HAL_GPIO_PULL_NONE, 5); // MII_RMII_RX_DV
hal_gpio_init(PIN('C', 2), HAL_GPIO_MODE_AF, 0, 0, HAL_GPIO_PULL_NONE, 5); // MII_RMII_TXD0
hal_gpio_init(PIN('D', 11), HAL_GPIO_MODE_AF, 0, 0, HAL_GPIO_PULL_NONE, 5); // MII_RMII_TX_CLK
hal_gpio_init(PIN('D', 12), HAL_GPIO_MODE_AF, 0, 0, HAL_GPIO_PULL_NONE, 5); // MII_RMII_TX_EN
hal_gpio_init(PIN('D', 7), HAL_GPIO_MODE_AF, 0, 0, HAL_GPIO_PULL_NONE, 5); // MII_RMII_TXD1
hal_gpio_init(PIN('E', 8), HAL_GPIO_MODE_AF, 0, 0, HAL_GPIO_PULL_NONE, 5); // MII_RMII_MDC
// reset PHY
hal_gpio_write(PIN('C', 3), 0);
spin(50000);
hal_gpio_write(PIN('C', 3), 1);
NVIC_EnableIRQ(ENET_Receive_IRQn);
}
static inline void hal_rtc_init(void) {
// Configure RTC clock to generate seeds
// Reconfigure it there as needed.
SIM->LPOCLKS = 0x13; // LPO32K_CLK, LPO32KCLKEN, LPO1KCLKEN
CLOCK_EnableClock(kCLOCK_Rtc0);
// If Time Invalid Flag set, reset the RTC
if (RTC->SR & RTC_SR_TIF_MASK) {
RTC->CR |= RTC_CR_SWR_MASK;
RTC->CR &= ~RTC_CR_SWR_MASK;
}
RTC->IER &= ~(RTC_IER_TSIC_MASK | RTC_IER_TSIE_MASK);
RTC->SR |= RTC_SR_TCE_MASK; // Enable timer
}
static inline void hal_clock_init(void) {
// init SysOsc
SCG->SOSCCSR = SCG_SOSCCSR_SOSCERR_MASK;
SCG->SOSCDIV = SCG_SOSCDIV_SOSCDIV1(1) | SCG_SOSCDIV_SOSCDIV2(1);
SCG->SOSCCFG = BIT(2) | SCG_SOSCCFG_RANGE(2); // work mode, range
SCG->SOSCCSR |= SCG_SOSCCSR_SOSCEN_MASK;
while ((SCG->SOSCCSR & SCG_SOSCCSR_SOSCVLD_MASK) == 0) spin(1);
// init FIRC
SCG->SIRCCSR = 0U;
SCG->SIRCDIV = SCG_SIRCDIV_SIRCDIV1(0) | SCG_SIRCDIV_SIRCDIV2(2);
SCG->SIRCCFG = SCG_SIRCCFG_RANGE(1);
SCG->SIRCCSR = SCG_SIRCCSR_SIRCEN_MASK | BIT(0);
while ((SCG->SIRCCSR & SCG_SIRCCSR_SIRCVLD_MASK) == 0) spin(1);
// Change to use SIRC as system clock source
SCG->RCCR = SCG_RCCR_DIVSLOW(3) | SCG_RCCR_SCS(2);
while (((SCG->CSR & SCG_CSR_SCS_MASK) >> SCG_CSR_SCS_SHIFT) != 2) spin(1);
SCG->FIRCDIV = SCG_FIRCDIV_FIRCDIV1(1) | SCG_FIRCDIV_FIRCDIV2(1);
SCG->FIRCCFG = SCG_FIRCCFG_RANGE(0);
SCG->FIRCCSR |= SCG_FIRCCSR_FIRCEN_MASK;
while ((SCG->FIRCCSR & SCG_FIRCCSR_FIRCVLD_MASK) == 0) spin(1);
SCG->RCCR = SCG_RCCR_DIVSLOW(3) | SCG_RCCR_SCS(3); // Change to FIRC
while (((SCG->CSR & SCG_CSR_SCS_MASK) >> SCG_CSR_SCS_SHIFT) != 3) spin(1);
// init SIRC
SCG->SIRCCSR = 0U;
SCG->SIRCDIV = SCG_SIRCDIV_SIRCDIV1(1) | SCG_SIRCDIV_SIRCDIV2(2);
SCG->SIRCCFG = SCG_SIRCCFG_RANGE(1);
SCG->SIRCCSR = SCG_SIRCCSR_SIRCEN_MASK | BIT(2);
while ((SCG->SIRCCSR & SCG_SIRCCSR_SIRCVLD_MASK) == 0) spin(1);
// init SysPLL
SCG->SPLLCSR = SCG_SPLLCSR_SPLLERR_MASK;
SCG->SPLLDIV = SCG_SPLLDIV_SPLLDIV1(2) | SCG_SPLLDIV_SPLLDIV2(0);
SCG->SPLLCFG = SCG_SPLLCFG_PREDIV(0) | SCG_SPLLCFG_MULT(24);
SCG->SPLLCSR = (uint32_t)SCG_SPLLCSR_SPLLEN_MASK ;
while ((SCG->SPLLCSR & SCG_SPLLCSR_SPLLVLD_MASK) == 0) spin(1);
// Set SCG to SPLL mode
SCG->RCCR = SCG_RCCR_DIVSLOW(4) | SCG_RCCR_DIVBUS(1) | SCG_RCCR_DIVCORE(1) | SCG_RCCR_SCS(6);
while (((SCG->CSR & SCG_CSR_SCS_MASK) >> SCG_CSR_SCS_SHIFT) != 6) spin(1);
// Set SCG CLKOUT selection.
SCG->CLKOUTCNFG = SCG_CLKOUTCNFG_CLKOUTSEL(3);
}
// TRNG is part of ELS, which requires flash partitioning to work
// Use our PRNG when the OS does not provide an alternative
#define MG_HAL_ENABLE_PRNG 1
static inline uint32_t hal_entropy_init(void) {
hal_rtc_init(); // Initialize RTC
spin(100000);
uint32_t seed = RTC->TPR; // get seed
spin(100000);
seed |= (RTC->TPR << 16);
return seed;
}
static inline bool hal_entropy_get(uint32_t *ent) {
*ent = RTC->TPR;
return true;
}
#if defined(__MCUXPRESSO)
#undef MG_HAL_DISABLE_SYSTEM_INIT
#define MG_HAL_DISABLE_SYSTEM_INIT 1
#undef MG_HAL_SBRK_NONE
#define MG_HAL_SBRK_NONE 1
#endif
static inline void hal_system_init(void) {
#if ((__FPU_PRESENT == 1) && (__FPU_USED == 1))
SCB->CPACR |= (3 << 20) | (3 << 22); // Enable FPU access
#endif
__DSB();
__ISB();
// Disable watchdog
if ((WDOG->CS & WDOG_CS_CMD32EN_MASK) != 0) {
WDOG->CNT = WDOG_UPDATE_KEY;
} else {
WDOG->CNT = WDOG_UPDATE_KEY & 0xFFFFU;
WDOG->CNT = (WDOG_UPDATE_KEY >> 16U) & 0xFFFFU;
}
WDOG->TOVAL = 0xFFFFU;
WDOG->CS = (uint32_t) ((WDOG->CS) & ~WDOG_CS_EN_MASK) | WDOG_CS_UPDATE_MASK;
LMEM->PCCCR |= LMEM_PCCCR_INVW1_MASK | LMEM_PCCCR_INVW0_MASK;
LMEM->PCCCR |= LMEM_PCCCR_GO_MASK;
// Wait until the command completes
while (LMEM->PCCCR & LMEM_PCCCR_GO_MASK) spin(1);
// Enable cache
LMEM->PCCCR |= LMEM_PCCCR_ENCACHE_MASK;
__ISB();
// Errata ERR050877 workaround. Enable FZ mode in the FPSCR
__set_FPSCR(__get_FPSCR() | FPSCR_FZ_MASK);
SYSMPU->CESR &= ~SYSMPU_CESR_VLD(1); // disable MPU
}
@@ -0,0 +1,35 @@
OUTPUT_FORMAT("elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(Reset_Handler)
MEMORY
{
flash(RX) : ORIGIN = 0x0, LENGTH = 0x180000
flash2(RX) : ORIGIN = 0x10000000, LENGTH = 0x80000
sram(RWX) : ORIGIN = 0x1ffe0000, LENGTH = 0x3f000
}
__StackTop = ORIGIN(sram) + LENGTH(sram);
SECTIONS
{
.vectors : { FILL(256) KEEP(*(.isr_vector)) } > flash
. = 0x400 ;
.FlashConfig : { KEEP(*(.FlashConfig)) } > flash
.text : { *(.text.* *.text.*) } > flash
.rodata : { *(.rodata*) } > flash
.data : {
__data_start__ = .;
*(.first_data)
*(.data SORT(.data.*))
__data_end__ = .;
} > sram AT > flash
__etext = LOADADDR(.data);
.bss : { __bss_start__ = .; *(.bss SORT(.bss.*) COMMON) __bss_end__ = .; } > sram
. = ALIGN(8);
_end = .;
}
+101
View File
@@ -0,0 +1,101 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
#include "hal.h"
#ifndef UART_DEBUG
#define UART_DEBUG LPUART2
#endif
#define LED1 PIN('C', 13) // Red
#define LED2 PIN('B', 11) // Green
#define LED3 PIN('C', 12) // Blue
static uint32_t s_rng_seed;
static void hal_rng_init(void) {
s_rng_seed = hal_entropy_init();
}
static uint32_t hal_rng_read(void) {
uint32_t seed = s_rng_seed, entropy;
if (hal_entropy_get(&entropy)) {
entropy *= 0x85ebca6b;
entropy ^= entropy << 16;
entropy ^= entropy >> 5;
seed ^= entropy;
seed *= 0x5bd1e995;
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
seed += entropy;
seed ^= seed >> 16;
}
s_rng_seed = seed;
return seed;
}
static void log_fn(char ch, void *param) {
hal_uart_write_buf(param, &ch, 1);
}
static void blink_task(void) {
static uint64_t blink_timer = 0;
if (hal_timer_expired(&blink_timer, 500, hal_get_tick())) {
hal_gpio_toggle(LED1);
}
}
uint64_t mg_millis(void) {
return hal_get_tick();
}
#if MG_ENABLE_CUSTOM_RANDOM
bool mg_random(void *buf, size_t len) {
for (size_t n = 0; n < len; n += sizeof(uint32_t)) {
uint32_t r = hal_rng_read();
memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r));
}
return true;
}
#endif
static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) { // New HTTP request received
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), hal_get_tick());
} else {
mg_http_reply(c, 200, "", "Hi from Mongoose, tick %llu\n",
hal_get_tick());
}
}
}
int main(void) {
hal_clock_init();
hal_rng_init();
hal_gpio_output(LED1);
hal_gpio_write(LED1, true);
hal_gpio_output(LED2);
hal_gpio_write(LED2, true);
hal_gpio_output(LED3);
hal_gpio_write(LED3, true);
hal_uart_init(UART_DEBUG, 115200);
mg_log_set_fn(log_fn, UART_DEBUG);
hal_ethernet_init();
// Start a minimal web server
struct mg_mgr mgr;
mg_mgr_init(&mgr);
mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL);
for (;;) {
mg_mgr_poll(&mgr, 0);
blink_task();
}
return 0;
}
@@ -0,0 +1,36 @@
#pragma once
// See https://mongoose.ws/documentation/#build-options
#define MG_ARCH MG_ARCH_ARMGCC
#define MG_ENABLE_TCPIP 1
#define MG_ENABLE_DRIVER_MCXE 1
#define MG_ENABLE_CUSTOM_MILLIS 1
#define MG_ENABLE_CUSTOM_RANDOM 1
#define MG_DRIVER_MDC_CR 15 // RMII MDC clock divider, from 0 to 7
#define MG_TCPIP_PHY_ADDR 0 // PHY address
// For static IP configuration, define MG_TCPIP_{IP,MASK,GW}
// By default, those are set to zero, meaning that DHCP is used
//
//#define MG_TCPIP_IP MG_IPV4(192, 168, 1, 10)
//#define MG_TCPIP_GW MG_IPV4(192, 168, 1, 1)
//#define MG_TCPIP_MASK MG_IPV4(255, 255, 255, 0)
// Set custom MAC address. By default, it is randomly generated
// Use the unique ID of the board
#define MGUID ((uint32_t*)0x40048054UL)
#define MG_SET_MAC_ADDRESS(mac) \
do { \
mac[0] = 2; \
mac[1] = MGUID[0] & 255; \
mac[2] = (MGUID[0] >> 10) & 255; \
mac[3] = (MGUID[0] >> 19) & 255; \
mac[4] = MGUID[1] & 255; \
mac[5] = MGUID[2] & 255; \
} while (0)
//
// Using custom function:
// extern void my_function(unsigned char *mac);
// #define MG_SET_MAC_ADDRESS(mac) my_function(mac)
@@ -1,29 +0,0 @@
BOARD = mcxn947
IDE = GCC+make
RTOS = baremetal
WIZARD_URL ?= http://mongoose.ws/wizard
all build example: firmware.bin
firmware.bin: wizard
make -C wizard CFLAGS_EXTRA=$(CFLAGS_EXTRA) && mv wizard/firmware.bin ./
wizard:
hash=$$(curl -H 'Cookie: mongoose_wizard_uid=F2PPlAgQCqSthV1g' -s -X POST -H "Content-Type: application/json" -d '{"build":{"board":"$(BOARD)","ide":"$(IDE)","rtos":"$(RTOS)"},"http":{"http": true}}' $(WIZARD_URL)/api/hash | jq -r '.hash') \
&& curl -H 'Cookie: mongoose_wizard_uid=F2PPlAgQCqSthV1g' -s $(WIZARD_URL)/api/zip -o wizard.zip
unzip wizard.zip
cd wizard/mongoose ; rm mongoose.[ch] ; ln -s ../../../../../mongoose.c ; ln -s ../../../../../mongoose.h
# Automated remote test. Requires env variable VCON_API_KEY set. See https://vcon.io/automated-firmware-tests/
DEVICE_URL ?= https://dash.vcon.io/api/v3/devices/??
update: firmware.bin
curl --fail-with-body -su :$(VCON_API_KEY) $(DEVICE_URL)/ota --data-binary @$<
test update: CFLAGS_EXTRA ="-DUART_DEBUG=USART?"
test: update
curl --fail-with-body -su :$(VCON_API_KEY) $(DEVICE_URL)/tx?t=15 | tee /tmp/output.txt
grep 'READY, IP:' /tmp/output.txt # Check for network init
clean:
rm -rf firmware.* wizard*
@@ -1 +0,0 @@
See [Wizard](https://mongoose.ws/wizard/#/output?board=mcxn947&ide=GCC+make&rtos=baremetal&file=README.md)
@@ -1,29 +0,0 @@
BOARD = mcxn947
IDE = GCC+make
RTOS = FreeRTOS
WIZARD_URL ?= http://mongoose.ws/wizard
all build example: firmware.bin
firmware.bin: wizard
make -C wizard CFLAGS_EXTRA=$(CFLAGS_EXTRA) && mv wizard/firmware.bin ./
wizard:
hash=$$(curl -H 'Cookie: mongoose_wizard_uid=F2PPlAgQCqSthV1g' -s -X POST -H "Content-Type: application/json" -d '{"build":{"board":"$(BOARD)","ide":"$(IDE)","rtos":"$(RTOS)"},"http":{"http": true}}' $(WIZARD_URL)/api/hash | jq -r '.hash') \
&& curl -H 'Cookie: mongoose_wizard_uid=F2PPlAgQCqSthV1g' -s $(WIZARD_URL)/api/zip -o wizard.zip
unzip wizard.zip
cd wizard/mongoose ; rm mongoose.[ch] ; ln -s ../../../../../mongoose.c ; ln -s ../../../../../mongoose.h
# Automated remote test. Requires env variable VCON_API_KEY set. See https://vcon.io/automated-firmware-tests/
DEVICE_URL ?= https://dash.vcon.io/api/v3/devices/??
update: firmware.bin
curl --fail-with-body -su :$(VCON_API_KEY) $(DEVICE_URL)/ota --data-binary @$<
test update: CFLAGS_EXTRA ="-DUART_DEBUG=USART?"
test: update
curl --fail-with-body -su :$(VCON_API_KEY) $(DEVICE_URL)/tx?t=15 | tee /tmp/output.txt
grep 'READY, IP:' /tmp/output.txt # Check for network init
clean:
rm -rf firmware.* wizard*
@@ -1 +0,0 @@
See [Wizard](https://mongoose.ws/wizard/#/output?board=mcxn947&ide=GCC+make&rtos=FreeRTOS&file=README.md)
@@ -1,27 +0,0 @@
BOARD = mcxn947
IDE = MCUXpresso
RTOS = baremetal
WIZARD_URL ?= http://mongoose.ws/wizard
TARGET ?= Debug
DOCKER = docker run --rm -v $(CURDIR):/root -w /root
IMAGE ?= scaprile/xpresso
HEADLESS_BUILD = /usr/local/mcuxpressoide/ide/mcuxpressoide --launcher.suppressErrors -nosplash -application org.eclipse.cdt.managedbuilder.core.headlessbuild
all build example: firmware.axf
firmware.axf: wizard
mkdir -p workspace
PROJNAME=`xq -r .projectDescription.name wizard/.project` && \
($(DOCKER) $(IMAGE) $(HEADLESS_BUILD) -data workspace -import wizard -cleanBuild $$PROJNAME/$(TARGET) || true) && \
cp wizard/$(TARGET)/$$PROJNAME.axf firmware.axf
wizard:
hash=$$(curl -H 'Cookie: mongoose_wizard_uid=F2PPlAgQCqSthV1g' -s -X POST -H "Content-Type: application/json" -d '{"build":{"board":"$(BOARD)","ide":"$(IDE)","rtos":"$(RTOS)"},"http":{"http": true}}' $(WIZARD_URL)/api/hash | jq -r '.hash') \
&& curl -H 'Cookie: mongoose_wizard_uid=F2PPlAgQCqSthV1g' -s $(WIZARD_URL)/api/zip -o wizard.zip
unzip wizard.zip
cd wizard/mongoose ; rm mongoose.[ch] ; cp -rL ../../../../../mongoose.[ch] .
clean:
test -f firmware.axf && sudo rm -rf firmware.* wizard* workspace mcuxpresso .cache .eclipse .p2 || true
@@ -1 +0,0 @@
See [Wizard](https://mongoose.ws/wizard/#/output?board=f429&ide=CubeIDE&rtos=baremetal&file=README.md)
@@ -1,27 +0,0 @@
BOARD = mcxn947
IDE = MCUXpresso
RTOS = FreeRTOS
WIZARD_URL ?= http://mongoose.ws/wizard
TARGET ?= Debug
DOCKER = docker run --rm -v $(CURDIR):/root -w /root
IMAGE ?= scaprile/xpresso
HEADLESS_BUILD = /usr/local/mcuxpressoide/ide/mcuxpressoide --launcher.suppressErrors -nosplash -application org.eclipse.cdt.managedbuilder.core.headlessbuild
all build example: firmware.axf
firmware.axf: wizard
mkdir -p workspace
PROJNAME=`xq -r .projectDescription.name wizard/.project` && \
($(DOCKER) $(IMAGE) $(HEADLESS_BUILD) -data workspace -import wizard -cleanBuild $$PROJNAME/$(TARGET) || true) && \
cp wizard/$(TARGET)/$$PROJNAME.axf firmware.axf
wizard:
hash=$$(curl -H 'Cookie: mongoose_wizard_uid=F2PPlAgQCqSthV1g' -s -X POST -H "Content-Type: application/json" -d '{"build":{"board":"$(BOARD)","ide":"$(IDE)","rtos":"$(RTOS)"},"http":{"http": true}}' $(WIZARD_URL)/api/hash | jq -r '.hash') \
&& curl -H 'Cookie: mongoose_wizard_uid=F2PPlAgQCqSthV1g' -s $(WIZARD_URL)/api/zip -o wizard.zip
unzip wizard.zip
cd wizard/mongoose ; rm mongoose.[ch] ; cp -rL ../../../../../mongoose.[ch] .
clean:
test -f firmware.axf && sudo rm -rf firmware.* wizard* workspace mcuxpresso .cache .eclipse .p2 || true
@@ -1 +0,0 @@
See [Wizard](https://mongoose.ws/wizard/#/output?board=f429&ide=CubeIDE&rtos=baremetal&file=README.md)
@@ -0,0 +1 @@
See the example at https://github.com/mongoose-examples/frdm-mcxn947
@@ -0,0 +1,44 @@
# Environment setup: https://mongoose.ws/documentation/tutorials/tools/
CFLAGS = -W -Wall -Wextra -Werror -Wundef -Wshadow -Wdouble-promotion
CFLAGS += -Wformat-truncation -fno-common -Wno-conversion -Wno-sign-conversion
CFLAGS += -g3 -Os -ffunction-sections -fdata-sections
CFLAGS += -Wno-cast-function-type -Wno-old-style-declaration
CFLAGS += -Wno-unused-parameter
CFLAGS += -I. -Imongoose -Icmsis_core/CMSIS/Core/Include/
CFLAGS += -Icmsis_mcxn947/devices/MCXN947/ -Icmsis_mcxn947/devices/MCXN947/periph/ -Icmsis_mcxn947/devices/MCXN947/drivers/
CFLAGS += -DCPU_MCXN947VDF -DCPU_MCXN947VDF_cm33 -DCPU_MCXN947VDF_cm33_core0
CFLAGS += -mcpu=cortex-m33 -mthumb -mfloat-abi=hard -mfpu=fpv5-sp-d16 $(CFLAGS_EXTRA)
LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs=nano.specs -lc -lgcc -Wl,--gc-sections -Wl,-Map=$@.map
SOURCES = main.c hal.c mongoose/mongoose.c
SOURCES += cmsis_mcxn947/devices/MCXN947/gcc/startup_MCXN947_cm33_core0.S
SOURCES += cmsis_mcxn947/devices/MCXN947/drivers/fsl_clock.c
SOURCES += cmsis_mcxn947/devices/MCXN947/drivers/fsl_spc.c
SOURCES += cmsis_mcxn947/devices/MCXN947/drivers/fsl_common_arm.c
CFLAGS += -D__ATOLLIC__ -D__STARTUP_CLEAR_BSS
all build: firmware.bin
mongoose/mongoose.c mongoose/mongoose.h:
mkdir -p mongoose
cp ../../../../mongoose.[ch] mongoose/
firmware.bin: firmware.elf
arm-none-eabi-objcopy -O binary $< $@
arm-none-eabi-size --format=berkeley $<
firmware.elf: cmsis_core cmsis_mcxn947 $(SOURCES) $(wildcard *.h) link.ld Makefile
arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@
cmsis_core:
git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@
cmsis_mcxn947:
curl -sL https://mcuxpresso.nxp.com/cmsis_pack/repo/NXP.MCXN947_DFP.26.06.00.pack -o NXP.MCXN947_DFP.26.06.00.pack
unzip -q NXP.MCXN947_DFP.26.06.00.pack -d $@
rm NXP.MCXN947_DFP.26.06.00.pack
clean:
rm -rf firmware.* cmsis_* mongoose/mongoose.*
+125
View File
@@ -0,0 +1,125 @@
// Copyright (c) 2026 Cesanta Software Limited
// All rights reserved
#include "hal.h"
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now) {
uint64_t diff = now - *t; // Wrap-safe elapsed time since last expiry
if (period == 0) return false; // Avoid division by zero
if (diff < period) return false; // Period has not elapsed yet
*t += (diff / period) * period; // Preserve cadence, skip missed periods
return true;
}
static volatile uint64_t s_ticks; // Milliseconds since boot
void SysTick_Handler(void) { // SyStick IRQ handler, triggered every 1ms
s_ticks++;
}
uint64_t hal_get_tick(void) {
return s_ticks;
};
uint32_t SystemCoreClock = SYS_FREQUENCY;
void SystemInit(void) { // Called automatically by startup code
hal_system_init(); // Enable FPU
hal_clock_init();
SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms
}
void ExitRun0Mode(void) {
}
struct stat;
__attribute__((weak)) int _fstat(int fd, struct stat *st) {
(void) fd, (void) st;
return -1;
}
extern unsigned char _end[]; // End of data section, start of heap. See link.ld
static unsigned char *s_current_heap_end = _end;
size_t hal_ram_used(void) {
return (size_t) (s_current_heap_end - _end);
}
size_t hal_ram_free(void) {
unsigned char endofstack;
return (size_t) (&endofstack - s_current_heap_end);
}
void *_sbrk(int incr) {
unsigned char *prev_heap;
unsigned char *heap_end = (unsigned char *) ((size_t) &heap_end - 256);
prev_heap = s_current_heap_end;
// Check how much space we got from the heap end to the stack end
if (s_current_heap_end + incr > heap_end) return (void *) -1;
s_current_heap_end += incr;
return prev_heap;
}
__attribute__((weak)) int _open(const char *path) {
(void) path;
return -1;
}
__attribute__((weak)) int _close(int fd) {
(void) fd;
return -1;
}
__attribute__((weak)) int _isatty(int fd) {
(void) fd;
return 1;
}
__attribute__((weak)) int _lseek(int fd, int ptr, int dir) {
(void) fd, (void) ptr, (void) dir;
return 0;
}
__attribute__((weak)) void _exit(int status) {
(void) status;
for (;;) asm volatile("BKPT #0");
}
__attribute__((weak)) void _kill(int pid, int sig) {
(void) pid, (void) sig;
}
__attribute__((weak)) int _getpid(void) {
return -1;
}
__attribute__((weak)) int _write(int fd, char *ptr, int len) {
(void) fd, (void) ptr, (void) len;
return -1;
}
__attribute__((weak)) int _read(int fd, char *ptr, int len) {
(void) fd, (void) ptr, (void) len;
return -1;
}
__attribute__((weak)) int _link(const char *a, const char *b) {
(void) a, (void) b;
return -1;
}
__attribute__((weak)) int _unlink(const char *a) {
(void) a;
return -1;
}
__attribute__((weak)) int _stat(const char *path, struct stat *st) {
(void) path, (void) st;
return -1;
}
__attribute__((weak)) int mkdir(const char *path, int mode) {
(void) path, (void) mode;
return -1;
}
__attribute__((weak)) void _init(void) {
}
+279
View File
@@ -0,0 +1,279 @@
// Copyright (c) 2023 Cesanta Software Limited
// All rights reserved
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "MCXN947_cm33_core0.h"
#define BIT(x) (1UL << (x))
#define CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set))
#define PIN(bank, num) ((bank << 8) | (num))
#define PINNO(pin) (pin & 255)
#define PINBANK(pin) (pin >> 8)
void hal_init(void);
size_t hal_ram_free(void);
size_t hal_ram_used(void);
bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now);
uint64_t hal_get_tick(void);
static inline void spin(volatile uint32_t count) {
while (count--)
(void)0;
}
#define SYS_FREQUENCY 150000000UL
enum { HAL_GPIO_MODE_INPUT, HAL_GPIO_MODE_OUTPUT };
enum { HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_OTYPE_OPEN_DRAIN };
enum { HAL_GPIO_SPEED_LOW, HAL_GPIO_SPEED_HIGH };
enum { HAL_GPIO_PULL_NONE, HAL_GPIO_PULL_DOWN, HAL_GPIO_PULL_UP };
static inline GPIO_Type *hal_gpio_bank(uint16_t pin) {
static GPIO_Type *const g[] = GPIO_BASE_PTRS;
return g[PINBANK(pin)];
}
static inline void hal_gpio_init(uint16_t pin, uint8_t mode, uint8_t type,
uint8_t speed, uint8_t pull, uint8_t af) {
static PORT_Type *const p[] = PORT_BASE_PTRS;
PORT_Type *port = p[PINBANK(pin)];
GPIO_Type *gpio = hal_gpio_bank(pin);
uint32_t mask = (uint32_t)BIT(PINNO(pin));
bool dopull = pull > 0;
if (dopull)
--pull;
if (gpio != GPIO5) {
SYSCON->AHBCLKCTRL0 |=
(1 << (SYSCON_AHBCLKCTRL0_GPIO0_SHIFT + PINBANK(pin))) |
(1 << (SYSCON_AHBCLKCTRL0_PORT0_SHIFT + PINBANK(pin)));
};
port->PCR[PINNO(pin)] = PORT_PCR_IBE(1) | PORT_PCR_MUX(af) | PORT_PCR_DSE(1) |
PORT_PCR_ODE(type) |
PORT_PCR_SRE(speed != HAL_GPIO_SPEED_HIGH) |
PORT_PCR_PE(dopull) | PORT_PCR_PS(pull);
gpio->ICR[PINNO(pin)] = GPIO_ICR_ISF_MASK;
if (mode == HAL_GPIO_MODE_INPUT) {
gpio->PDDR &= ~mask;
} else {
gpio->PDDR |= mask;
}
}
static inline void hal_gpio_input(uint16_t pin) {
hal_gpio_init(pin, HAL_GPIO_MODE_INPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_LOW,
HAL_GPIO_PULL_NONE, 0);
}
static inline void hal_gpio_output(uint16_t pin) {
hal_gpio_init(pin, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_LOW,
HAL_GPIO_PULL_NONE, 0);
}
static inline bool hal_gpio_read(uint16_t pin) {
GPIO_Type *gpio = hal_gpio_bank(pin);
return gpio->PDR[PINNO(pin)];
}
static inline void hal_gpio_write(uint16_t pin, bool value) {
GPIO_Type *gpio = hal_gpio_bank(pin);
if (value) {
gpio->PDR[PINNO(pin)] = 1;
} else {
gpio->PDR[PINNO(pin)] = 0;
}
}
static inline void hal_gpio_toggle(uint16_t pin) {
GPIO_Type *gpio = hal_gpio_bank(pin);
uint32_t mask = (uint32_t)BIT(PINNO(pin));
gpio->PTOR = mask;
}
// MCU-Link UART (P1_9/8; FC4_P1/0)
// Arduino J1_2/4 UART (P4_3/2; FC2_P3/2)
// 33.3.23 LP_FLEXCOMM clocking
// 66.2.4 LP_FLEXCOMM init
// 66.5 LPUART
static inline void hal_uart_init(LPUART_Type *uart, unsigned long baud) {
static LP_FLEXCOMM_Type *const f[] = LP_FLEXCOMM_BASE_PTRS;
uint8_t af = 2, fc = 0; // Alternate function, FlexComm instance
uint16_t pr = 0, pt = 0; // pins
uint32_t freq = 12000000; // fro_12_m
if (uart == LPUART2)
fc = 2, pt = PIN(4, 3), pr = PIN(4, 2);
if (uart == LPUART4)
fc = 4, pt = PIN(1, 9), pr = PIN(1, 8);
SYSCON->AHBCLKCTRL1 |= (1 << (SYSCON_AHBCLKCTRL1_FC0_SHIFT + fc));
SYSCON->PRESETCTRL1 |= (1 << (SYSCON_PRESETCTRL1_FC0_RST_SHIFT + fc));
SYSCON->PRESETCTRL1 &= ~(1 << (SYSCON_PRESETCTRL1_FC0_RST_SHIFT + fc));
SYSCON->FCCLKSEL[fc] = SYSCON_FCCLKSEL_SEL(2); // clock from FRO_12M / 1
SYSCON->FLEXCOMMCLKDIV[fc] = SYSCON_FLEXCOMMXCLKDIV_FLEXCOMMCLKDIV_DIV(0);
LP_FLEXCOMM_Type *flexcomm = f[fc];
flexcomm->PSELID = LP_FLEXCOMM_PSELID_PERSEL(1); // configure as UART
hal_gpio_init(pt, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_LOW,
HAL_GPIO_PULL_UP, af);
hal_gpio_init(pr, HAL_GPIO_MODE_INPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_LOW,
HAL_GPIO_PULL_UP, af);
uart->GLOBAL |= LPUART_GLOBAL_RST_MASK; // reset, CTRL = 0, defaults
uart->GLOBAL &= ~LPUART_GLOBAL_RST_MASK;
// use a weird oversample ratio of 26x to fit specs, standard 16x won't do
CLRSET(uart->BAUD,
LPUART_BAUD_OSR_MASK | LPUART_BAUD_SBR_MASK | LPUART_BAUD_SBNS_MASK,
LPUART_BAUD_OSR(26 - 1) | LPUART_BAUD_SBR(freq / (26 * baud)));
CLRSET(uart->CTRL,
LPUART_CTRL_PE_MASK | LPUART_CTRL_M_MASK | LPUART_CTRL_ILT_MASK |
LPUART_CTRL_IDLECFG_MASK,
LPUART_CTRL_IDLECFG(1) | LPUART_CTRL_ILT(1) |
LPUART_BAUD_SBNS(0)); // no parity, idle 2 chars after 1 stop bit
uart->CTRL |= LPUART_CTRL_TE_MASK | LPUART_CTRL_RE_MASK;
}
static inline void hal_uart_write_byte(LPUART_Type *uart, uint8_t byte) {
uart->DATA = byte;
while ((uart->STAT & LPUART_STAT_TDRE_MASK) == 0)
spin(1);
}
static inline void hal_uart_write_buf(LPUART_Type *uart, char *buf, size_t len) {
while (len-- > 0)
hal_uart_write_byte(uart, *(uint8_t *)buf++);
}
// TRNG is part of ELS and there is no info on it
// Use our PRNG when the OS does not provide an alternative
#define MG_HAL_ENABLE_PRNG 1
static inline uint32_t hal_entropy_init(void) {
// Use RTC as entropy source
// Configured at clock_init(), reconfigure it there as needed.
uint32_t seed;
// generate a random seed
seed = RTC0->RTC_TEST2;
for (uint32_t i = 0; i < 100000; i++) spin(1);
seed |= RTC0->RTC_TEST2 << 16;
return seed;
}
static inline bool hal_entropy_get(uint32_t *ent) {
if (SYSCON->AHBCLKCTRL1 & BIT(6)) { // 17.4.1.14
// if the RTC is still active, use the subsecond counter as seed
*ent = RTC0->RTC_TEST2;
return true;
}
return false;
}
// - PHY and MAC clocked via a 50MHz oscillator, P1_4 (ENET0_TXCLK)
// - 33.3.30 ENET clocking
// - SMI clocked from AHB module clock (CSR)
// - PHY RST connected to P5_8
// - PHY RXD0,1,DV = 1 on RST enable autonegotiation, no hw pull-ups
static inline void hal_ethernet_init(void) {
// '0' in clk_rmii, set for RMII mode
SYSCON->ENETRMIICLKSEL = SYSCON_ENETRMIICLKSEL_SEL(0);
SYSCON->ENETRMIICLKDIV = SYSCON_ENETRMIICLKDIV_DIV(0);
SYSCON->AHBCLKCTRL2 |= SYSCON_AHBCLKCTRL2_ENET_MASK; // enable bus clk
SYSCON0->PRESETCTRL2 = SYSCON_PRESETCTRL2_ENET_RST_MASK; // reset MAC
SYSCON0->PRESETCTRL2 &= ~SYSCON_PRESETCTRL2_ENET_RST_MASK; // then set RMII
SYSCON0->ENET_PHY_INTF_SEL |= SYSCON_ENET_PHY_INTF_SEL_PHY_SEL_MASK;
hal_gpio_init(PIN(5, 8), HAL_GPIO_MODE_OUTPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_LOW,
HAL_GPIO_PULL_NONE, 0); // set P5_8 as GPIO (PHY \RST)
hal_gpio_write(PIN(5, 8), 0); // reset PHY
hal_gpio_init(PIN(1, 4), HAL_GPIO_MODE_INPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH,
HAL_GPIO_PULL_NONE, 9); // set P1_4 as ENET0_TXCLK
hal_gpio_init(PIN(1, 5), HAL_GPIO_MODE_OUTPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH,
HAL_GPIO_PULL_NONE, 9); // set P1_5 as ENET0_TXEN
hal_gpio_init(PIN(1, 6), HAL_GPIO_MODE_OUTPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH,
HAL_GPIO_PULL_NONE, 9); // set P1_6 as ENET0_TXD0
hal_gpio_init(PIN(1, 7), HAL_GPIO_MODE_OUTPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH,
HAL_GPIO_PULL_NONE, 9); // set P1_7 as ENET0_TXD1
hal_gpio_init(PIN(1, 13), HAL_GPIO_MODE_INPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH,
HAL_GPIO_PULL_UP, 9); // set P1_13 as ENET0_RXDV
hal_gpio_init(PIN(1, 14), HAL_GPIO_MODE_INPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH,
HAL_GPIO_PULL_UP, 9); // set P1_14 as ENET0_RXD0
hal_gpio_init(PIN(1, 15), HAL_GPIO_MODE_INPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH,
HAL_GPIO_PULL_UP, 9); // set P1_15 as ENET0_RXD1
hal_gpio_init(PIN(1, 20), HAL_GPIO_MODE_OUTPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH,
HAL_GPIO_PULL_NONE, 9); // set P1_20 as ENET0_MDC
hal_gpio_init(PIN(1, 21), HAL_GPIO_MODE_OUTPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH,
HAL_GPIO_PULL_NONE, 9); // set P1_21 as ENET0_MDIO
spin(10000); // keep PHY RST low for a while
hal_gpio_write(PIN(5, 8), 1); // deassert RST
NVIC_EnableIRQ(ETHERNET_IRQn); // Setup Ethernet IRQ handler
}
#include "fsl_clock.h"
#include "fsl_spc.h"
// 33.2 Figure 127 SCG main clock
static inline void hal_clock_init(void) {
SYSCON->AHBCLKCTRL2 |= SYSCON_AHBCLKCTRL2_SCG_MASK; // enable SCG clk
CLRSET(SCG0->RCCR, SCG_CSR_SCS_MASK, SCG_CSR_SCS(2)); // clock main_clock
spc_active_mode_dcdc_option_t dcdc = {
.DCDCVoltage = kSPC_DCDC_OverdriveVoltage,
.DCDCDriveStrength = kSPC_DCDC_NormalDriveStrength};
SPC_SetActiveModeDCDCRegulatorConfig(SPC0, &dcdc); // Set DCDC to 1.2 V
spc_active_mode_core_ldo_option_t ldo = {
.CoreLDOVoltage = kSPC_CoreLDO_OverDriveVoltage,
.CoreLDODriveStrength = kSPC_CoreLDO_NormalDriveStrength};
SPC_SetActiveModeCoreLDORegulatorConfig(SPC0, &ldo); // Set LDO_CORE to 1.2 V
CLRSET(FMU0->FCTRL, FMU_FCTRL_RWSC_MASK, FMU_FCTRL_RWSC(3)); // Set Flash WS
spc_sram_voltage_config_t sram = {.operateVoltage = kSPC_sramOperateAt1P2V,
.requestVoltageUpdate = true};
SPC_SetSRAMOperateVoltage(SPC0, &sram); // Set SRAM timing for 1.2V
CLOCK_SetupFROHFClocking(48000000U); // Enable FRO HF
const pll_setup_t pll0 = {.pllctrl = SCG_APLLCTRL_SOURCE(1U) |
SCG_APLLCTRL_SELI(27U) |
SCG_APLLCTRL_SELP(13U),
.pllndiv = SCG_APLLNDIV_NDIV(8U),
.pllpdiv = SCG_APLLPDIV_PDIV(1U),
.pllmdiv = SCG_APLLMDIV_MDIV(50U),
.pllRate = 150000000U};
CLOCK_SetPLL0Freq(&pll0); // Setup PLL0 (APLL),
CLOCK_SetPll0MonitorMode(0); // disable monitor mode
CLRSET(SCG0->RCCR, SCG_CSR_SCS_MASK, SCG_CSR_SCS(5)); // clock main_clock
SYSCON->AHBCLKDIV = SYSCON_AHBCLKDIV_DIV(0); // /1
// We configure the RTC to get rng seeds, reconfigure it here as needed
CLOCK_SetupClk16KClocking(kCLOCK_Clk16KToVbat | kCLOCK_Clk16KToMain);
CLOCK_EnableClock(kCLOCK_Rtc0);
}
#if defined(__MCUXPRESSO)
#undef MG_HAL_DISABLE_SYSTEM_INIT
#define MG_HAL_DISABLE_SYSTEM_INIT 1
#undef MG_HAL_SBRK_NONE
#define MG_HAL_SBRK_NONE 1
#endif
static inline void hal_system_init(void) {
// Init IRAM (MCUXPresso startup handles this, CMSIS startup does not)
extern long _siram, _eiram, _siiram;
for (long *dst = &_siram, *src = &_siiram; dst < &_eiram;) *dst++ = *src++;
SCB->CPACR |=
#if 0
(3UL << 0 * 2) | (3UL << 1 * 2) | // Enable PowerQuad (CPO/CP1)
#endif
(3UL << 10 * 2) | (3UL << 11 * 2); // Enable FPU
__DSB();
__ISB();
SYSCON->LPCAC_CTRL &= ~SYSCON_LPCAC_CTRL_DIS_LPCAC_MASK; // enable LPCAC
// Read TRM 36.1 and decide whether you really want to enable aGDET and dGDET
#if 1
// Disable aGDET and dGDET
CLRSET(ITRC0->OUT_SEL[4][0], 0xC0000U, 0x2 << 18);
CLRSET(ITRC0->OUT_SEL[4][1], 0xC0000U, 0x2 << 18);
SPC0->ACTIVE_CFG |= SPC_ACTIVE_CFG_GLITCH_DETECT_DISABLE_MASK;
SPC0->GLITCH_DETECT_SC &= ~SPC_GLITCH_DETECT_SC_LOCK_MASK;
SPC0->GLITCH_DETECT_SC = 0x3C;
SPC0->GLITCH_DETECT_SC |= SPC_GLITCH_DETECT_SC_LOCK_MASK;
CLRSET(ITRC0->OUT_SEL[4][0], 0x3, 0x2);
CLRSET(ITRC0->OUT_SEL[4][1], 0x3, 0x2);
GDET0->GDET_ENABLE1 = 0;
GDET1->GDET_ENABLE1 = 0;
#endif
}
@@ -0,0 +1,51 @@
OUTPUT_FORMAT("elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(Reset_Handler)
MEMORY
{
flash(RX) : ORIGIN = 0x0, LENGTH = 0x100000
flash2(RX) : ORIGIN = 0x100000, LENGTH = 0x100000
sram(!RX) : ORIGIN = 0x20000000, LENGTH = 0x60000
sramx(WX) : ORIGIN = 0x04000000, LENGTH = 0x18000
}
_estack = ORIGIN(sram) + LENGTH(sram);
__StackTop = _estack;
__StackLimit = __StackTop - 0x2000;
SECTIONS
{
.vectors : { FILL(256) KEEP(*(.isr_vector)) } > flash
.text : { *(.text.* *.text.*) } > flash
.rodata : { *(.rodata*) } > flash
.data : {
_sdata = .;
*(.first_data)
*(.data SORT(.data.*))
_edata = .;
} > sram AT > flash
_sidata = LOADADDR(.data);
.bss : { _sbss = .; *(.bss SORT(.bss.*) COMMON) _ebss = .; } > sram
. = ALIGN(8);
_end = .;
.iram : {
_siram = .;
*(.iram)
_eiram = .;
} > sramx AT > flash
_siiram = LOADADDR(.iram);
/* Symbols expected by the CMSIS pack startup file. */
__etext = _sidata;
__data_start__ = _sdata;
__data_end__ = _edata;
__bss_start__ = _sbss;
__bss_end__ = _ebss;
__HeapBase = _end;
__HeapLimit = _end;
}
Loaded 100 of 2141 files, more files were not shown because too many files have changed in this diff. Show more