diff options
| author | hathach <[email protected]> | 2026-06-04 15:31:45 +0700 |
|---|---|---|
| committer | hathach <[email protected]> | 2026-06-04 15:31:45 +0700 |
| commit | 0393a5eaa1fc9ef33cbfd847c924cb83d82696f3 (patch) | |
| tree | b96374c05142757820304e2d89d6f260b24f1354 | |
| parent | 232315d6909d9ca7948c830a5e43ad35189da57f (diff) | |
| parent | ac32feafeb2fb0f6cc5c3010d32db319ff15ca64 (diff) | |
Merge branch 'master' into dwc2_postfix
| -rw-r--r-- | .github/labeler.yml | 77 | ||||
| -rw-r--r-- | .github/workflows/labeler.yml | 159 | ||||
| -rw-r--r-- | .github/workflows/sponsor-triage.yml | 165 | ||||
| -rw-r--r-- | hw/bsp/family_support.cmake | 36 | ||||
| -rw-r--r-- | hw/bsp/stm32f7/boards/stm32f769disco/board.cmake | 1 | ||||
| -rw-r--r-- | src/class/mtp/mtp_device.c | 11 | ||||
| -rw-r--r-- | src/portable/synopsys/dwc2/dcd_dwc2.c | 60 | ||||
| -rw-r--r-- | test/hil/tinyusb.json | 42 |
8 files changed, 465 insertions, 86 deletions
diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 000000000..6c7aa7e7d --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,77 @@ +# Path-based auto-labeling for USB IP / port drivers. +# Maps changed dcd/hcd files under src/portable/ to their "Port <ip>" label. +# Consumed by actions/labeler (see .github/workflows/labeler.yml -> label-port job). + +"Port DWC2": + - changed-files: + - any-glob-to-any-file: 'src/portable/synopsys/dwc2/**' + +"Port EHCI": + - changed-files: + - any-glob-to-any-file: 'src/portable/ehci/**' + +"Port OHCI": + - changed-files: + - any-glob-to-any-file: 'src/portable/ohci/**' + +"Port FSDev": + - changed-files: + - any-glob-to-any-file: 'src/portable/st/stm32_fsdev/**' + +"Port ChipIdea": + - changed-files: + - any-glob-to-any-file: 'src/portable/chipidea/**' + +"Port NXP IP3511": + - changed-files: + - any-glob-to-any-file: 'src/portable/nxp/lpc_ip3511/**' + +"Port NXP IP3516": + - changed-files: + - any-glob-to-any-file: 'src/portable/nxp/lpc_ip3516/**' + +"Port MUSB": + - changed-files: + - any-glob-to-any-file: + - 'src/portable/mentor/musb/**' + - 'src/portable/sunxi/**' + +"Port RUSB2": + - changed-files: + - any-glob-to-any-file: 'src/portable/renesas/rusb2/**' + +"Port WCH USBFS": + - changed-files: + - any-glob-to-any-file: 'src/portable/wch/*usbfs*' + +"Port WCH USBHS": + - changed-files: + - any-glob-to-any-file: 'src/portable/wch/*usbhs*' + +"Port MAX3421": + - changed-files: + - any-glob-to-any-file: 'src/portable/analog/max3421/**' + +"Port SAMD": + - changed-files: + - any-glob-to-any-file: 'src/portable/microchip/samd/**' + +"Port SAMG": + - changed-files: + - any-glob-to-any-file: 'src/portable/microchip/samg/**' + +"Port nRF": + - changed-files: + - any-glob-to-any-file: 'src/portable/nordic/nrf5x/**' + +"Port Nuvoton": + - changed-files: + - any-glob-to-any-file: 'src/portable/nuvoton/**' + +"Port RP2": + - changed-files: + - any-glob-to-any-file: 'src/portable/raspberrypi/**' + +"Port MSP430": + - changed-files: + - any-glob-to-any-file: 'src/portable/ti/msp430x5xx/**' diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index c3cc59d0d..fe09413f1 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -4,23 +4,29 @@ on: issues: types: [opened] pull_request_target: - types: [opened] + types: [opened, synchronize, reopened] + discussion: + types: [created] jobs: label-priority: + # Author-based priority labels: only on issue/PR/discussion creation, not on PR updates. + if: github.event_name != 'pull_request_target' || github.event.action == 'opened' runs-on: ubuntu-latest permissions: issues: write pull-requests: write + discussions: write steps: - - name: Label New Issue or PR - uses: actions/github-script@v7 + - name: Label New Issue, PR or Discussion + uses: actions/github-script@v8 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - let label = ''; + let labels = []; let username = ''; let issueOrPrNumber = 0; + let discussionNodeId = ''; if (context.eventName === 'issues') { username = context.payload.issue.user.login; @@ -28,25 +34,85 @@ jobs: } else if (context.eventName === 'pull_request_target') { username = context.payload.pull_request.user.login; issueOrPrNumber = context.payload.pull_request.number; + } else if (context.eventName === 'discussion') { + username = context.payload.discussion.user.login; + discussionNodeId = context.payload.discussion.node_id; } - // Check if an Adafruit member - try { - const adafruitResponse = await github.rest.orgs.checkMembershipForUser({ - org: 'adafruit', - username: username - }); + // Maintainer is an Adafruit member; skip the Adafruit perks for their own + // issues/PRs and treat them as a plain contributor (Prio only). + const isOwner = username.toLowerCase() === 'hathach'; - if (adafruitResponse.status === 204) { - console.log('Adafruit Member'); - label = 'Prio Urgent'; + // Check if an Adafruit member: Adafruit + Sponsor + top priority + if (!isOwner) { + try { + const adafruitResponse = await github.rest.orgs.checkMembershipForUser({ + org: 'adafruit', + username: username + }); + + if (adafruitResponse.status === 204) { + console.log('Adafruit Member'); + labels = ['Adafruit 🌸', 'Sponsor 💖', 'Prio Top 🚨']; + } + } catch (error) { + console.log('Not an Adafruit member'); } - } catch (error) { - console.log('Not an Adafruit member'); } - // Check if a contributor - if (label == '') { + // Check if a public GitHub Sponsor of the repo owner. + // Word ($32) tier and up get triage priority; DWORD/QWORD ($128+) go to the top. + // Private sponsorships are not visible to GITHUB_TOKEN, so only public sponsors are detected. + if (labels.length === 0) { + try { + const result = await github.graphql(` + query($sponsorable: String!, $sponsor: String!) { + user(login: $sponsorable) { + isSponsoredBy(accountLogin: $sponsor) + sponsorshipsAsMaintainer(includePrivate: false, first: 100) { + nodes { + sponsorEntity { + ... on User { login } + ... on Organization { login } + } + tier { monthlyPriceInDollars } + } + } + } + }`, { sponsorable: context.repo.owner, sponsor: username }); + + const owner = result.user; + if (owner && owner.isSponsoredBy) { + let monthly = 0; + const nodes = (owner.sponsorshipsAsMaintainer && owner.sponsorshipsAsMaintainer.nodes) || []; + for (const node of nodes) { + const login = node.sponsorEntity && node.sponsorEntity.login; + if (login && login.toLowerCase() === username.toLowerCase()) { + monthly = (node.tier && node.tier.monthlyPriceInDollars) || 0; + break; + } + } + + if (monthly >= 128) { + console.log('Sponsor (DWORD/QWORD tier)'); + labels = ['Sponsor 💖', 'Prio Top 🚨']; + } else if (monthly >= 32) { + console.log('Sponsor (Word tier)'); + labels = ['Sponsor 💖', 'Prio 📌']; + } else { + console.log('Sponsor (below Word tier or tier not visible)'); + labels = ['Sponsor 💖']; + } + } else { + console.log('Not a public sponsor'); + } + } catch (error) { + console.log('Sponsor lookup failed: ' + error.message); + } + } + + // Check if a contributor: prioritized in triage queue + if (labels.length === 0) { try { const collaboratorResponse = await github.rest.repos.checkCollaborator({ owner: context.repo.owner, @@ -56,18 +122,61 @@ jobs: if (collaboratorResponse.status === 204) { console.log('Contributor'); - label = 'Prio Higher'; + labels = ['Prio 📌']; } } catch (error) { console.log('Not a contributor'); } } - if (label !== '') { - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issueOrPrNumber, - labels: [label] - }); + if (labels.length !== 0) { + if (context.eventName === 'discussion') { + // Discussions are not covered by the REST issues API; resolve the label + // names to node IDs and attach them with the GraphQL labelable mutation. + const labelIds = []; + for (const name of labels) { + const res = await github.graphql(` + query($owner: String!, $repo: String!, $name: String!) { + repository(owner: $owner, name: $repo) { + label(name: $name) { id } + } + }`, { owner: context.repo.owner, repo: context.repo.repo, name: name }); + if (res.repository.label) { + labelIds.push(res.repository.label.id); + } + } + if (labelIds.length !== 0) { + await github.graphql(` + mutation($labelableId: ID!, $labelIds: [ID!]!) { + addLabelsToLabelable(input: { labelableId: $labelableId, labelIds: $labelIds }) { + clientMutationId + } + }`, { labelableId: discussionNodeId, labelIds: labelIds }); + } + } else { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueOrPrNumber, + labels: labels + }); + } } + + # Path-based Port labels: attach "Port <ip>" when a PR touches the matching + # dcd/hcd driver under src/portable/. Mapping lives in .github/labeler.yml. + label-port: + if: github.event_name == 'pull_request_target' + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + issues: write # allow auto-creating a Port label that doesn't exist yet + steps: + - uses: actions/labeler@v5 + with: + configuration-path: .github/labeler.yml + # sync-labels so a Port label is removed once a PR no longer touches + # that driver (job reruns on synchronize). Only labels listed in + # labeler.yml are managed, so author-based Prio/Sponsor labels are untouched. + sync-labels: true diff --git a/.github/workflows/sponsor-triage.yml b/.github/workflows/sponsor-triage.yml new file mode 100644 index 000000000..48b2ac7cc --- /dev/null +++ b/.github/workflows/sponsor-triage.yml @@ -0,0 +1,165 @@ +name: Sponsor Triage + +# Periodically add open issues and PRs opened by sponsors (public and private) to the private "Sponsor Triage" project board +# Requires a PAT in secret SPONSOR_TOKEN with scopes: +# - project (write project items / fields) +# - read:org (search org issues, check Adafruit membership) +# - read:user / sponsors (read own sponsorships incl. private) + +on: + schedule: + - cron: '0 */6 * * *' # every 6 hours + workflow_dispatch: + +concurrency: + group: sponsor-triage + cancel-in-progress: false + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - name: Sync sponsor issues to project board + uses: actions/github-script@v8 + with: + github-token: ${{ secrets.SPONSOR_TOKEN }} + script: | + const PROJECT_OWNER = 'hathach'; + const PROJECT_NUMBER = 3; + // Where to look for sponsor-authored open issues. + const SEARCH_SCOPES = ['org:adafruit', 'user:hathach']; + + const tierFromMonthly = (m) => { + if (m >= 512) return 'QWORD'; + if (m >= 128) return 'DWORD'; + if (m >= 32) return 'Word'; + if (m >= 8) return 'Byte'; + if (m >= 2) return 'Bit'; + return null; + }; + + // --- 1. Resolve project id + field/option ids (by name, never hardcoded) --- + const proj = await github.graphql(` + query($owner: String!, $number: Int!) { + user(login: $owner) { + projectV2(number: $number) { + id + fields(first: 50) { + nodes { + ... on ProjectV2SingleSelectField { + id name options { id name } + } + } + } + } + } + }`, { owner: PROJECT_OWNER, number: PROJECT_NUMBER }); + + const project = proj.user.projectV2; + const fieldByName = {}; + for (const f of project.fields.nodes) { + if (f && f.name) { + fieldByName[f.name] = { id: f.id, options: {} }; + for (const o of (f.options || [])) fieldByName[f.name].options[o.name] = o.id; + } + } + const tierField = fieldByName['Tier']; + const visField = fieldByName['Visibility']; + + // --- 2. Build sponsor map: loginLower -> { tier, visibility } --- + const sponsors = new Map(); + + // 2a. GitHub Sponsors of the owner, including private ones. + let after = null; + for (let page = 0; page < 20; page++) { + const res = await github.graphql(` + query($owner: String!, $after: String) { + user(login: $owner) { + sponsorshipsAsMaintainer(includePrivate: true, first: 100, after: $after) { + pageInfo { hasNextPage endCursor } + nodes { + privacyLevel + tier { monthlyPriceInDollars } + sponsorEntity { + ... on User { login } + ... on Organization { login } + } + } + } + } + }`, { owner: PROJECT_OWNER, after }); + const conn = res.user.sponsorshipsAsMaintainer; + for (const n of conn.nodes) { + const login = n.sponsorEntity && n.sponsorEntity.login; + if (!login) continue; + const tier = tierFromMonthly((n.tier && n.tier.monthlyPriceInDollars) || 0); + const visibility = n.privacyLevel === 'PRIVATE' ? 'Private' : 'Public'; + sponsors.set(login.toLowerCase(), { login, tier, visibility }); + } + if (!conn.pageInfo.hasNextPage) break; + after = conn.pageInfo.endCursor; + } + + core.info(`Resolved ${sponsors.size} sponsor account(s).`); + if (sponsors.size === 0) return; + + // 2b. Adafruit org members get the Adafruit tier regardless of $ amount. + for (const s of sponsors.values()) { + try { + const m = await github.rest.orgs.checkMembershipForUser({ org: 'adafruit', username: s.login }); + if (m.status === 204) s.tier = 'Adafruit'; + } catch (e) { /* not an Adafruit member */ } + } + + // --- 3. Find each sponsor's open issues and PRs in the search scopes --- + // (search type ISSUE returns both issues and pull requests) + const found = new Map(); // contentId -> { tier, visibility } + for (const s of sponsors.values()) { + for (const scope of SEARCH_SCOPES) { + const q = `${scope} is:open author:${s.login}`; + try { + const res = await github.graphql(` + query($q: String!) { + search(query: $q, type: ISSUE, first: 100) { + nodes { ... on Issue { id } ... on PullRequest { id } } + } + }`, { q }); + for (const node of res.search.nodes) { + if (node && node.id && !found.has(node.id)) { + found.set(node.id, { tier: s.tier, visibility: s.visibility }); + } + } + } catch (e) { + core.warning(`Search failed for one scope: ${e.message}`); + } + } + } + core.info(`Found ${found.size} open sponsor issue(s) across scopes.`); + + // --- 4. Add to board + set Tier / Visibility (idempotent) --- + const setField = async (itemId, field, optionName) => { + if (!field || !optionName) return; + const optId = field.options[optionName]; + if (!optId) return; + await github.graphql(` + mutation($p: ID!, $i: ID!, $f: ID!, $o: String!) { + updateProjectV2ItemFieldValue(input: { + projectId: $p, itemId: $i, fieldId: $f, value: { singleSelectOptionId: $o } + }) { projectV2Item { id } } + }`, { p: project.id, i: itemId, f: field.id, o: optId }); + }; + + let added = 0; + for (const [contentId, meta] of found) { + const res = await github.graphql(` + mutation($p: ID!, $c: ID!) { + addProjectV2ItemById(input: { projectId: $p, contentId: $c }) { + item { id } + } + }`, { p: project.id, c: contentId }); + const itemId = res.addProjectV2ItemById.item.id; + await setField(itemId, tierField, meta.tier); + await setField(itemId, visField, meta.visibility); + added++; + } + core.info(`Synced ${added} item(s) to the board.`); diff --git a/hw/bsp/family_support.cmake b/hw/bsp/family_support.cmake index 2468ac43c..07d693d77 100644 --- a/hw/bsp/family_support.cmake +++ b/hw/bsp/family_support.cmake @@ -244,7 +244,7 @@ function(family_add_bloaty TARGET) COMMAND ${BLOATY_EXE} ${OPTION_LIST} $<TARGET_FILE:${TARGET}> VERBATIM) - set_property(TARGET ${TARGET}-bloaty PROPERTY FOLDER ${TARGET}-group) + #set_property(TARGET ${TARGET}-bloaty PROPERTY FOLDER ${TARGET}-group) # post build # add_custom_command(TARGET ${TARGET} POST_BUILD # COMMAND ${BLOATY_EXE} --csv ${OPTION_LIST} $<TARGET_FILE:${TARGET}> > ${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_bloaty.csv @@ -265,7 +265,7 @@ function(family_add_linkermap TARGET) VERBATIM ) - set_property(TARGET ${TARGET}-linkermap PROPERTY FOLDER ${TARGET}-group) + #set_property(TARGET ${TARGET}-linkermap PROPERTY FOLDER ${TARGET}-group) # post build add_custom_command(TARGET ${TARGET} POST_BUILD @@ -347,7 +347,7 @@ echo \"$MEMBROWSE_CMD\"") COMMAND ${CMAKE_COMMAND} -E env MEMBROWSE_UPLOAD=0 bash -lc "${MEMBROWSE_PREPARE_CMD}; eval \"$MEMBROWSE_CMD\"" VERBATIM ) - set_property(TARGET ${TARGET}-membrowse PROPERTY FOLDER ${TARGET}-group) + #set_property(TARGET ${TARGET}-membrowse PROPERTY FOLDER ${TARGET}-group) add_custom_target(${TARGET}-membrowse-upload COMMAND ${CMAKE_COMMAND} -E env MEMBROWSE_UPLOAD=1 bash -lc "${MEMBROWSE_PREPARE_CMD}; eval \"$MEMBROWSE_CMD\"" @@ -359,7 +359,7 @@ echo \"$MEMBROWSE_CMD\"") endif () add_dependencies(examples-membrowse-upload ${TARGET}-membrowse-upload) - set_property(TARGET ${TARGET}-membrowse-upload PROPERTY FOLDER ${TARGET}-group) + #set_property(TARGET ${TARGET}-membrowse-upload PROPERTY FOLDER ${TARGET}-group) endif () endfunction() @@ -648,7 +648,7 @@ exit" VERBATIM ) - set_property(TARGET ${NAME_TARGET}-jlink PROPERTY FOLDER ${TARGET}-group) +# set_property(TARGET ${NAME_TARGET}-jlink PROPERTY FOLDER ${NAME_TARGET}-group) endfunction() @@ -663,7 +663,7 @@ function(family_flash_stlink TARGET) COMMAND ${STM32_PROGRAMMER_CLI} --connect port=swd --write $<TARGET_FILE:${TARGET}> --go ) - set_property(TARGET ${TARGET}-stlink PROPERTY FOLDER ${TARGET}-group) + #set_property(TARGET ${TARGET}-stlink PROPERTY FOLDER ${TARGET}-group) endfunction() @@ -678,7 +678,7 @@ function(family_flash_stflash TARGET) COMMAND ${ST_FLASH} write $<TARGET_FILE_DIR:${TARGET}>/${TARGET}.bin 0x8000000 ) - set_property(TARGET ${TARGET}-stflash PROPERTY FOLDER ${TARGET}-group) + #set_property(TARGET ${TARGET}-stflash PROPERTY FOLDER ${TARGET}-group) endfunction() @@ -706,7 +706,7 @@ function(family_flash_openocd TARGET) VERBATIM ) - set_property(TARGET ${TARGET}-openocd PROPERTY FOLDER ${TARGET}-group) + #set_property(TARGET ${TARGET}-openocd PROPERTY FOLDER ${TARGET}-group) endfunction() @@ -769,7 +769,7 @@ function(family_flash_wlink_rs TARGET) COMMAND ${WLINK_RS} flash $<TARGET_FILE:${TARGET}> ) - set_property(TARGET ${TARGET}-wlink-rs PROPERTY FOLDER ${TARGET}-group) + #set_property(TARGET ${TARGET}-wlink-rs PROPERTY FOLDER ${TARGET}-group) endfunction() @@ -784,7 +784,7 @@ function(family_flash_pyocd TARGET) COMMAND ${PYOCD} flash -t ${PYOCD_TARGET} $<TARGET_FILE:${TARGET}> ) - set_property(TARGET ${TARGET}-pyocd PROPERTY FOLDER ${TARGET}-group) + #set_property(TARGET ${TARGET}-pyocd PROPERTY FOLDER ${TARGET}-group) endfunction() @@ -794,7 +794,7 @@ function(family_flash_uf2 TARGET FAMILY_ID) DEPENDS ${TARGET} COMMAND python ${UF2CONV_PY} -f ${FAMILY_ID} --deploy $<TARGET_FILE_DIR:${TARGET}>/${TARGET}.uf2 ) - set_property(TARGET ${TARGET}-uf2 PROPERTY FOLDER ${TARGET}-group) + #set_property(TARGET ${TARGET}-uf2 PROPERTY FOLDER ${TARGET}-group) endfunction() @@ -810,7 +810,7 @@ function(family_flash_teensy TARGET) COMMAND ${TEENSY_CLI} --mcu=${TEENSY_MCU} -w -s $<TARGET_FILE_DIR:${TARGET}>/${TARGET}.hex ) - set_property(TARGET ${TARGET}-teensy PROPERTY FOLDER ${TARGET}-group) + #set_property(TARGET ${TARGET}-teensy PROPERTY FOLDER ${TARGET}-group) endfunction() @@ -830,7 +830,7 @@ function(family_flash_nxplink TARGET) COMMAND ${LINKSERVER_PATH} flash ${NXPLINK_DEVICE} load $<TARGET_FILE:${TARGET}> ) - set_property(TARGET ${TARGET}-nxplink PROPERTY FOLDER ${TARGET}-group) + #set_property(TARGET ${TARGET}-nxplink PROPERTY FOLDER ${TARGET}-group) endfunction() @@ -845,7 +845,7 @@ function(family_flash_dfu_util TARGET OPTION) VERBATIM ) - set_property(TARGET ${TARGET}-dfu-util PROPERTY FOLDER ${TARGET}-group) + #set_property(TARGET ${TARGET}-dfu-util PROPERTY FOLDER ${TARGET}-group) endfunction() function(family_flash_msp430flasher TARGET) @@ -862,7 +862,7 @@ function(family_flash_msp430flasher TARGET) ${MSP430FLASHER} -w $<TARGET_FILE_DIR:${TARGET}>/${TARGET}.hex -z [VCC] ) - set_property(TARGET ${TARGET}-msp430flasher PROPERTY FOLDER ${TARGET}-group) + #set_property(TARGET ${TARGET}-msp430flasher PROPERTY FOLDER ${TARGET}-group) endfunction() function(family_flash_rfp TARGET) @@ -880,7 +880,7 @@ function(family_flash_rfp TARGET) VERBATIM ) - set_property(TARGET ${TARGET}-rfp PROPERTY FOLDER ${TARGET}-group) + #set_property(TARGET ${TARGET}-rfp PROPERTY FOLDER ${TARGET}-group) endfunction() @@ -897,7 +897,7 @@ function(family_flash_uniflash TARGET) VERBATIM ) - set_property(TARGET ${TARGET}-uniflash PROPERTY FOLDER ${TARGET}-group) + #set_property(TARGET ${TARGET}-uniflash PROPERTY FOLDER ${TARGET}-group) endfunction() # Add flash ft9xx target need to remove kernal's ftdi_sio and bind D2XX drivers @@ -912,7 +912,7 @@ function(family_flash_ft9xx TARGET) COMMAND ${FT9XXPROG} -f $<TARGET_FILE_DIR:${TARGET}>/${TARGET}.bin ) - set_property(TARGET ${TARGET}-ft9xx PROPERTY FOLDER ${TARGET}-group) + #set_property(TARGET ${TARGET}-ft9xx PROPERTY FOLDER ${TARGET}-group) endfunction() #---------------------------------- diff --git a/hw/bsp/stm32f7/boards/stm32f769disco/board.cmake b/hw/bsp/stm32f7/boards/stm32f769disco/board.cmake index 2335b869e..dbdd07e4d 100644 --- a/hw/bsp/stm32f7/boards/stm32f769disco/board.cmake +++ b/hw/bsp/stm32f7/boards/stm32f769disco/board.cmake @@ -1,5 +1,6 @@ set(MCU_VARIANT stm32f769xx) set(JLINK_DEVICE stm32f769ni) +#set(JLINK_OPTION "-USB 000778170924") set(LD_FILE_GNU ${CMAKE_CURRENT_LIST_DIR}/STM32F769ZITx_FLASH.ld) diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index fd06b4601..1f76dfcc7 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -443,15 +443,12 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t // Check completion for IN and OUT separately bool is_complete; - - if (is_data_in) - { + if (is_data_in) { // IN completion: short packet, ZLP, or reaching total_len is_complete = (xferred_bytes == 0 || xferred_bytes < threshold || p_mtp->xferred_len >= p_mtp->total_len); - } - else - { - // OUT completion: reaching total_len or ZLP + } else { + // OUT completion: reaching total_len or ZLP only. A short packet does NOT end the phase + // (an early short packet before total_len is the cancel case, not normal completion). is_complete = (p_mtp->xferred_len >= p_mtp->total_len) || ((xferred_bytes == 0 && p_mtp->xferred_len > 0)); } diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c index e6d7dc08e..c7899a354 100644 --- a/src/portable/synopsys/dwc2/dcd_dwc2.c +++ b/src/portable/synopsys/dwc2/dcd_dwc2.c @@ -793,15 +793,15 @@ static void handle_bus_reset(uint8_t rhport) { xfer_status[0][TUSB_DIR_OUT].max_size = CFG_TUD_ENDPOINT0_SIZE; xfer_status[0][TUSB_DIR_IN].max_size = CFG_TUD_ENDPOINT0_SIZE; - uint32_t oepmsk = 0; + uint32_t gintmsk = GINTMSK_OTGINT | GINTMSK_IEPINT | GINTMSK_IISOIXFRM; if(dma_device_enabled(dwc2)) { - oepmsk = GINTMSK_OEPINT; + gintmsk |= GINTMSK_OEPINT; dma_setup_prepare(rhport); } else { dwc2->epout[0].doeptsiz |= (3 << DOEPTSIZ_STUPCNT_Pos); } - dwc2->gintmsk |= GINTMSK_OTGINT | oepmsk | GINTMSK_IEPINT | GINTMSK_IISOIXFRM; + dwc2->gintmsk |= gintmsk; } static void handle_enum_done(uint8_t rhport) { @@ -885,45 +885,47 @@ static void handle_rxflvl_irq(uint8_t rhport) { dwc2_regs_t* dwc2 = DWC2_REG(rhport); const volatile uint32_t* rx_fifo = dwc2->fifo[0]; + // DWC2 v3.10a (e.g. STM32L476) emits an extra EP0 RX_COMPLETE that is NOT a real OUT data transfer completion, in two + // situations - each flagged by a DOEPINT bit set on that word: + // - DOEPINT.STPKTRX (Setup Packet Received): pushed between SETUP_RX and SETUP_DONE of every control transfer. + // - DOEPINT.STSPHSRX (Status Phase Received for control write): pushed after the OUT data stage when the host + // starts the IN status phase. + // Both are dropped in the RX_COMPLETE case below, clearing the flag (W1C) so a latched STSPHSRX + // does not block the core from emitting the next SETUP_DONE. usbd still processes the real OUT data + // and queues the IN status ZLP itself - the core does not auto-complete the control-write status. + const bool quirk_v310a = (dwc2->gsnpsid == DWC2_CORE_REV_3_10a); + // Pop control word off FIFO const dwc2_grxstsp_t grxstsp = {.value = dwc2->grxstsp}; + const uint8_t packet_status = grxstsp.packet_status; const uint8_t epnum = grxstsp.ep_ch_num; dwc2_dep_t* epout = &dwc2->epout[epnum]; - switch (grxstsp.packet_status) { + switch (packet_status) { case GRXSTS_PKTSTS_GLOBAL_OUT_NAK: // Global OUT NAK: do nothing break; case GRXSTS_PKTSTS_SETUP_RX: { // Setup packet received - uint32_t* setup = (uint32_t*)(uintptr_t) _dcd_usbbuf.setup_buffer; + uint32_t * setup = (uint32_t*)(uintptr_t) _dcd_usbbuf.setup_buffer; // We can receive up to three setup packets in succession, but only the last one is valid. setup[0] = (*rx_fifo); setup[1] = (*rx_fifo); - - dwc2_dep_t* epin0 = &dwc2->epin[0]; - if (edpt_is_enabled(epin0)) { - edpt_disable(rhport, 0x80, false); - } - - // (GenID < 3.00a) Must wait SETUP_DONE before next OUT transfer, otherwise OUT data may be corrupted. - // (GenID >= 3.00a) On the other hand STUPCNT is auto reloaded and SETUP_DONE is only triggered once after bus reset. - if (dwc2->gsnpsid >= DWC2_CORE_REV_3_00a) { - dcd_event_setup_received(rhport, _dcd_usbbuf.setup_buffer, true); - } break; } - case GRXSTS_PKTSTS_SETUP_DONE: - // Setup packet done: + case GRXSTS_PKTSTS_SETUP_DONE: { + // Pop this word causes the Setup interrupt epout->doeptsiz |= (3 << DOEPTSIZ_STUPCNT_Pos); - - if (dwc2->gsnpsid < DWC2_CORE_REV_3_00a) { - dcd_event_setup_received(rhport, _dcd_usbbuf.setup_buffer, true); + epout->doepint = DOEPINT_SETUP | DOEPINT_STPKTRX; // Clear SETUP interrupt, required for core to re-write this control word + if (edpt_is_enabled(&dwc2->epin[0])) { + edpt_disable(rhport, 0x80, false); } + dcd_event_setup_received(rhport, _dcd_usbbuf.setup_buffer, true); break; + } case GRXSTS_PKTSTS_RX_DATA: { // Out packet received @@ -952,7 +954,19 @@ static void handle_rxflvl_irq(uint8_t rhport) { } case GRXSTS_PKTSTS_RX_COMPLETE: { - // Out packet done + // Pop this word causes the xfer complete interrupt + const uint32_t doepint = epout->doepint; + epout->doepint = DOEPINT_XFRC; + + // v3.10a quirk (see top of function): the extra RX_COMPLETE flagged with Setup Packet Received (STPKTRX) or + // Status Phase Received for control write (STSPHSRX) is not a real OUT completion. Drop it + if (quirk_v310a) { + if (doepint & (DOEPINT_STPKTRX | DOEPINT_STSPHSRX)) { + epout->doepint = DOEPINT_STPKTRX | DOEPINT_STSPHSRX; + break; + } + } + xfer_ctl_t* xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT); if (epnum == 0 && _dcd_data.ep0_pending[TUSB_DIR_OUT] > 0) { // EP0 can only handle one packet, schedule another packet to be received. @@ -1096,6 +1110,8 @@ static void handle_ep_irq(uint8_t rhport, uint8_t dir) { #if CFG_TUD_DWC2_SLAVE_ENABLE if (dir == TUSB_DIR_IN) { handle_epin_slave(rhport, epnum, intr.diepint_bm); + } else { + // epout is handled in handle_rxflvl_irq } #endif } diff --git a/test/hil/tinyusb.json b/test/hil/tinyusb.json index dc28df7b9..467b7378a 100644 --- a/test/hil/tinyusb.json +++ b/test/hil/tinyusb.json @@ -344,6 +344,20 @@ "comment": "2x16 access scheme with 1KB USB SRAM" }, { + "name": "stm32f407disco", + "uid": "30001A000647313332353735", + "tests": { + "device": true, + "host": false, + "dual": false + }, + "flasher": { + "name": "jlink", + "uid": "000773661813", + "args": "-device stm32f407vg" + } + }, + { "name": "stm32f723disco", "uid": "460029001951373031313335", "build": { @@ -413,6 +427,20 @@ "args": "-f interface/stlink.cfg -f target/stm32g0x.cfg" }, "comment": "32-bit scheme, 2KB USB SRAM" + }, + { + "name": "stm32l476disco", + "uid": "3C0050001150334258343920", + "tests": { + "device": true, + "host": false, + "dual": false + }, + "flasher": { + "name": "jlink", + "uid": "777632258", + "args": "-device STM32L476VG" + } } ], "boards-skip": [ @@ -449,20 +477,6 @@ "uid": "EBCA8F0670AF", "args": "" } - }, - { - "name": "stm32f407disco", - "uid": "30001A000647313332353735", - "tests": { - "device": true, - "host": false, - "dual": false - }, - "flasher": { - "name": "jlink", - "uid": "000773661813", - "args": "-device stm32f407vg" - } } ] } |
