git commit -m "first commit for v2"
This commit is contained in:
118
Devices/Libraries/Systems/can-utils/.clang-format
Executable file
118
Devices/Libraries/Systems/can-utils/.clang-format
Executable file
@@ -0,0 +1,118 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# clang-format configuration file. Intended for clang-format >= 11.
|
||||
#
|
||||
# For more information, see:
|
||||
#
|
||||
# Documentation/process/clang-format.rst
|
||||
# https://clang.llvm.org/docs/ClangFormat.html
|
||||
# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
|
||||
#
|
||||
---
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: false
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BraceWrapping:
|
||||
AfterClass: false
|
||||
AfterControlStatement: false
|
||||
AfterEnum: false
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Custom
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakBeforeTernaryOperators: false
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: BeforeComma
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: false
|
||||
ColumnLimit: 200
|
||||
CommentPragmas: "^ IWYU pragma:"
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerIndentWidth: 8
|
||||
ContinuationIndentWidth: 8
|
||||
Cpp11BracedListStyle: false
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: false
|
||||
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: ".*"
|
||||
Priority: 1
|
||||
IncludeIsMainRegex: "(Test)?$"
|
||||
IndentCaseLabels: false
|
||||
IndentGotoLabels: false
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 8
|
||||
IndentWrappedFunctionNames: false
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MacroBlockBegin: ""
|
||||
MacroBlockEnd: ""
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Auto
|
||||
ObjCBlockIndentWidth: 8
|
||||
ObjCSpaceAfterProperty: true
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
|
||||
# Taken from git's rules
|
||||
PenaltyBreakAssignment: 10
|
||||
PenaltyBreakBeforeFirstCallParameter: 30
|
||||
PenaltyBreakComment: 10
|
||||
PenaltyBreakFirstLessLess: 0
|
||||
PenaltyBreakString: 10
|
||||
PenaltyExcessCharacter: 100
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
|
||||
PointerAlignment: Right
|
||||
ReflowComments: false
|
||||
SortIncludes: false
|
||||
SortUsingDeclarations: false
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatementsExceptForEachMacros
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Cpp03
|
||||
TabWidth: 8
|
||||
UseTab: Always
|
||||
16
Devices/Libraries/Systems/can-utils/.editorconfig
Executable file
16
Devices/Libraries/Systems/can-utils/.editorconfig
Executable file
@@ -0,0 +1,16 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[{CMakeLists.txt,*.cmake}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
tab_width = 2
|
||||
|
||||
[{*.c,*.h}]
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
tab_width = 8
|
||||
70
Devices/Libraries/Systems/can-utils/.github/workflows/codeql-analysis.yml
vendored
Executable file
70
Devices/Libraries/Systems/can-utils/.github/workflows/codeql-analysis.yml
vendored
Executable file
@@ -0,0 +1,70 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '29 21 * * 1'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'cpp' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
104
Devices/Libraries/Systems/can-utils/.github/workflows/compile.yml
vendored
Executable file
104
Devices/Libraries/Systems/can-utils/.github/workflows/compile.yml
vendored
Executable file
@@ -0,0 +1,104 @@
|
||||
name: native and cross
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
release:
|
||||
- "ubuntu:20.04"
|
||||
- "ubuntu:22.04"
|
||||
- "ubuntu:24.04"
|
||||
- "ubuntu:rolling"
|
||||
- "debian:oldstable-slim"
|
||||
- "debian:stable-slim"
|
||||
- "debian:testing-slim"
|
||||
- "debian:unstable-slim"
|
||||
- "debian:experimental"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare ${{ matrix.release }} container
|
||||
env:
|
||||
release: ${{ matrix.release == 'debian:experimental' && '-t experimental' || '' }}
|
||||
run: |
|
||||
podman version
|
||||
podman run --name stable -di --userns=keep-id:uid=1000,gid=1000 -v "$PWD":/home -w /home ${{ matrix.release }} bash
|
||||
podman exec -i stable uname -a
|
||||
podman exec -i stable id
|
||||
podman exec -i -u root stable apt update
|
||||
podman exec -e DEBIAN_FRONTEND='noninteractive' -i -u root stable apt install -o APT::Install-Suggests=false -qy ${release} \
|
||||
clang \
|
||||
cmake \
|
||||
gcc \
|
||||
gcc-aarch64-linux-gnu \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
gcc-mips-linux-gnu \
|
||||
make
|
||||
|
||||
- name: Configure & Build with gcc
|
||||
env:
|
||||
cc: gcc
|
||||
run: |
|
||||
podman exec -i stable cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=${cc} -DENABLE_WERROR=ON -B build-${cc}
|
||||
podman exec -i stable cmake --build build-${cc}
|
||||
|
||||
- name: Configure & Build with clang
|
||||
env:
|
||||
cc: clang
|
||||
run: |
|
||||
podman exec -i stable cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=${cc} -DENABLE_WERROR=ON -B build-${cc}
|
||||
podman exec -i stable cmake --build build-${cc}
|
||||
|
||||
- name: Configure & Build with arm-linux-gnueabihf-gcc
|
||||
env:
|
||||
toolchain: arm-linux-gnueabihf-gcc
|
||||
run: |
|
||||
podman exec -i stable cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=cmake/${toolchain}.cmake -DENABLE_WERROR=ON -B build-${toolchain}
|
||||
podman exec -i stable cmake --build build-${toolchain}
|
||||
|
||||
- name: Configure & Build with arm-linux-gnueabihf-clang
|
||||
if:
|
||||
${{ matrix.release != 'ubuntu:20.04' && matrix.release != 'debian:oldstable-slim' }}
|
||||
env:
|
||||
toolchain: arm-linux-gnueabihf-clang
|
||||
run: |
|
||||
podman exec -i stable cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=cmake/${toolchain}.cmake -DENABLE_WERROR=ON -B build-${toolchain}
|
||||
podman exec -i stable cmake --build build-${toolchain}
|
||||
|
||||
- name: Configure & Build with aarch64-linux-gnu-gcc
|
||||
env:
|
||||
toolchain: aarch64-linux-gnu-gcc
|
||||
run: |
|
||||
podman exec -i stable cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=cmake/${toolchain}.cmake -DENABLE_WERROR=ON -B build-${toolchain}
|
||||
podman exec -i stable cmake --build build-${toolchain}
|
||||
|
||||
- name: Configure & Build with aarch64-linux-gnu-clang
|
||||
if:
|
||||
${{ matrix.release != 'ubuntu:20.04' && matrix.release != 'debian:oldstable-slim' }}
|
||||
env:
|
||||
toolchain: aarch64-linux-gnu-clang
|
||||
run: |
|
||||
podman exec -i stable cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=cmake/${toolchain}.cmake -DENABLE_WERROR=ON -B build-${toolchain}
|
||||
podman exec -i stable cmake --build build-${toolchain}
|
||||
|
||||
- name: Configure & Build with mips-linux-gnu-gcc
|
||||
env:
|
||||
toolchain: mips-linux-gnu-gcc
|
||||
run: |
|
||||
podman exec -i stable cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=cmake/${toolchain}.cmake -DENABLE_WERROR=ON -B build-${toolchain}
|
||||
podman exec -i stable cmake --build build-${toolchain}
|
||||
|
||||
- name: Show logs
|
||||
if: ${{ failure() }}
|
||||
run: |
|
||||
for log in build-*/CMakeFiles/{CMakeOutput.log,CMakeConfigureLog.yaml}; do \
|
||||
if [ -e ${log} ]; then \
|
||||
echo "---------------- ${log} ----------------"; \
|
||||
cat ${log}; \
|
||||
fi; \
|
||||
done
|
||||
15
Devices/Libraries/Systems/can-utils/.github/workflows/ndk.yml
vendored
Executable file
15
Devices/Libraries/Systems/can-utils/.github/workflows/ndk.yml
vendored
Executable file
@@ -0,0 +1,15 @@
|
||||
name: NDK
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: CMake and NDK
|
||||
run: |
|
||||
cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_WERROR=ON -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake -B build
|
||||
cmake --build build
|
||||
40
Devices/Libraries/Systems/can-utils/.gitignore
vendored
Executable file
40
Devices/Libraries/Systems/can-utils/.gitignore
vendored
Executable file
@@ -0,0 +1,40 @@
|
||||
*~
|
||||
*.o
|
||||
.ccls-cache
|
||||
compile_commands.json
|
||||
tags
|
||||
/build
|
||||
|
||||
/asc2log
|
||||
/bcmserver
|
||||
/can-calc-bit-timing
|
||||
/canbusload
|
||||
/candump
|
||||
/canfdtest
|
||||
/cangen
|
||||
/cangw
|
||||
/canlogserver
|
||||
/canplayer
|
||||
/cansend
|
||||
/cansequence
|
||||
/cansniffer
|
||||
/isobusfs-cli
|
||||
/isobusfs-srv
|
||||
/isotpdump
|
||||
/isotpperf
|
||||
/isotprecv
|
||||
/isotpsend
|
||||
/isotpserver
|
||||
/isotpsniffer
|
||||
/isotptun
|
||||
/j1939acd
|
||||
/j1939cat
|
||||
/j1939spy
|
||||
/j1939sr
|
||||
/log2asc
|
||||
/log2long
|
||||
/mcp251xfd-dump
|
||||
/slcan_attach
|
||||
/slcand
|
||||
/slcanpty
|
||||
/testj1939
|
||||
225
Devices/Libraries/Systems/can-utils/CMakeLists.txt
Executable file
225
Devices/Libraries/Systems/can-utils/CMakeLists.txt
Executable file
@@ -0,0 +1,225 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
project(can-utils LANGUAGES C)
|
||||
|
||||
message(STATUS "CMake version: ${CMAKE_VERSION}")
|
||||
|
||||
include (CheckFunctionExists)
|
||||
include (CheckSymbolExists)
|
||||
include (GNUInstallDirs)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Release)
|
||||
endif()
|
||||
|
||||
# Add an option to enable treating warnings as errors
|
||||
option(ENABLE_WERROR "Treat all compiler warnings as errors" OFF)
|
||||
|
||||
if(ENABLE_WERROR)
|
||||
add_compile_options(-Werror)
|
||||
endif()
|
||||
|
||||
add_definitions(-D_GNU_SOURCE)
|
||||
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wno-parentheses -Wsign-compare")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-strict-aliasing")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DSO_RXQ_OVFL=40")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DPF_CAN=29")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DAF_CAN=PF_CAN")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DN_SLCAN=17")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DSCM_TIMESTAMPING_OPT_STATS=54")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DCLOCK_TAI=11")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DSO_TXTIME=61")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DSCM_TXTIME=SO_TXTIME")
|
||||
|
||||
include_directories (.)
|
||||
include_directories (./include)
|
||||
|
||||
check_function_exists(fork HAVE_FORK)
|
||||
|
||||
set(PROGRAMS_CANLIB
|
||||
asc2log
|
||||
canbusload
|
||||
candump
|
||||
cangen
|
||||
canplayer
|
||||
cansend
|
||||
cansequence
|
||||
log2asc
|
||||
log2long
|
||||
slcanpty
|
||||
)
|
||||
|
||||
if(HAVE_FORK)
|
||||
list(APPEND PROGRAMS_CANLIB canlogserver)
|
||||
endif()
|
||||
|
||||
set(PROGRAMS_J1939
|
||||
j1939acd
|
||||
j1939cat
|
||||
j1939spy
|
||||
j1939sr
|
||||
testj1939
|
||||
)
|
||||
|
||||
set(PROGRAMS_J1939_TIMEDATE
|
||||
j1939-timedate-srv
|
||||
j1939-timedate-cli
|
||||
)
|
||||
|
||||
set(PROGRAMS_ISOBUSFS
|
||||
isobusfs-srv
|
||||
isobusfs-cli
|
||||
)
|
||||
|
||||
set(PROGRAMS
|
||||
${PROGRAMS_CANLIB}
|
||||
canfdtest
|
||||
cangw
|
||||
cansniffer
|
||||
isotpdump
|
||||
isotpperf
|
||||
isotprecv
|
||||
isotpsend
|
||||
isotpsniffer
|
||||
isotptun
|
||||
slcan_attach
|
||||
slcand
|
||||
)
|
||||
|
||||
if(HAVE_FORK)
|
||||
list(APPEND PROGRAMS bcmserver)
|
||||
list(APPEND PROGRAMS isotpserver)
|
||||
endif()
|
||||
|
||||
add_executable(can-calc-bit-timing
|
||||
calc-bit-timing/can-calc-bit-timing.c
|
||||
)
|
||||
|
||||
add_executable(mcp251xfd-dump
|
||||
mcp251xfd/mcp251xfd-dev-coredump.c
|
||||
mcp251xfd/mcp251xfd-dump.c
|
||||
mcp251xfd/mcp251xfd-main.c
|
||||
mcp251xfd/mcp251xfd-regmap.c
|
||||
)
|
||||
|
||||
if(NOT ANDROID)
|
||||
list(APPEND PROGRAMS ${PROGRAMS_J1939})
|
||||
|
||||
add_library(j1939 STATIC
|
||||
libj1939.c
|
||||
)
|
||||
|
||||
target_link_libraries(j1939
|
||||
PRIVATE can
|
||||
)
|
||||
|
||||
add_library(isobusfs SHARED
|
||||
isobusfs/isobusfs_cmn.c
|
||||
isobusfs/isobusfs_cmn_dh.c
|
||||
)
|
||||
|
||||
set(PUBLIC_HEADER_ISOBUSFS
|
||||
isobusfs/isobusfs_cmn.h
|
||||
isobusfs/isobusfs_cmn_cm.h
|
||||
)
|
||||
|
||||
set_target_properties(isobusfs PROPERTIES
|
||||
PUBLIC_HEADER "${PUBLIC_HEADER_ISOBUSFS}"
|
||||
)
|
||||
|
||||
install(TARGETS isobusfs
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
)
|
||||
|
||||
add_executable(isobusfs-cli
|
||||
isobusfs/isobusfs_cli.c
|
||||
isobusfs/isobusfs_cli_cm.c
|
||||
isobusfs/isobusfs_cli_dh.c
|
||||
isobusfs/isobusfs_cli_fa.c
|
||||
isobusfs/isobusfs_cli_selftests.c
|
||||
isobusfs/isobusfs_cli_int.c
|
||||
)
|
||||
|
||||
target_link_libraries(isobusfs-cli
|
||||
PRIVATE isobusfs can j1939
|
||||
)
|
||||
|
||||
add_executable(isobusfs-srv
|
||||
isobusfs/isobusfs_srv.c
|
||||
isobusfs/isobusfs_srv_cm.c
|
||||
isobusfs/isobusfs_srv_cm_fss.c
|
||||
isobusfs/isobusfs_srv_dh.c
|
||||
isobusfs/isobusfs_srv_fa.c
|
||||
isobusfs/isobusfs_srv_fh.c
|
||||
isobusfs/isobusfs_srv_vh.c
|
||||
)
|
||||
|
||||
target_link_libraries(isobusfs-srv
|
||||
PRIVATE isobusfs can j1939
|
||||
)
|
||||
|
||||
install(TARGETS
|
||||
isobusfs-cli
|
||||
isobusfs-srv
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
set(PUBLIC_HEADER_j1939_TIMEDATE
|
||||
j1939_timedate/j1939_timedate_cmn.h
|
||||
)
|
||||
|
||||
add_executable(j1939-timedate-cli
|
||||
j1939_timedate/j1939_timedate_cli.c
|
||||
)
|
||||
|
||||
target_link_libraries(j1939-timedate-cli
|
||||
PRIVATE can j1939
|
||||
)
|
||||
|
||||
add_executable(j1939-timedate-srv
|
||||
j1939_timedate/j1939_timedate_srv.c
|
||||
)
|
||||
|
||||
target_link_libraries(j1939-timedate-srv
|
||||
PRIVATE can j1939
|
||||
)
|
||||
|
||||
install(TARGETS
|
||||
j1939-timedate-cli
|
||||
j1939-timedate-srv
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
endif()
|
||||
|
||||
add_library(can STATIC
|
||||
lib.c
|
||||
canframelen.c
|
||||
)
|
||||
|
||||
foreach(name ${PROGRAMS})
|
||||
add_executable(${name} ${name}.c)
|
||||
|
||||
if("${name}" IN_LIST PROGRAMS_J1939)
|
||||
target_link_libraries(${name}
|
||||
PRIVATE j1939 can
|
||||
)
|
||||
elseif("${name}" IN_LIST PROGRAMS_CANLIB)
|
||||
target_link_libraries(${name}
|
||||
PRIVATE can
|
||||
)
|
||||
endif()
|
||||
|
||||
install(TARGETS ${name} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
endforeach()
|
||||
|
||||
install(TARGETS
|
||||
can-calc-bit-timing
|
||||
mcp251xfd-dump
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
|
||||
add_custom_target(uninstall
|
||||
"${CMAKE_COMMAND}" -P "${CMAKE_SOURCE_DIR}/cmake/make_uninstall.cmake"
|
||||
COMMENT "Add uninstall target"
|
||||
)
|
||||
27
Devices/Libraries/Systems/can-utils/LICENSES/BSD-3-Clause
Executable file
27
Devices/Libraries/Systems/can-utils/LICENSES/BSD-3-Clause
Executable file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) <year> <owner> . All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
340
Devices/Libraries/Systems/can-utils/LICENSES/GPL-2.0-only.txt
Executable file
340
Devices/Libraries/Systems/can-utils/LICENSES/GPL-2.0-only.txt
Executable file
@@ -0,0 +1,340 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) 19yy <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) 19yy name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
Public License instead of this License.
|
||||
12
Devices/Libraries/Systems/can-utils/LICENSES/Linux-syscall-note.txt
Executable file
12
Devices/Libraries/Systems/can-utils/LICENSES/Linux-syscall-note.txt
Executable file
@@ -0,0 +1,12 @@
|
||||
NOTE! This copyright does *not* cover user programs that use kernel
|
||||
services by normal system calls - this is merely considered normal use
|
||||
of the kernel, and does *not* fall under the heading of "derived work".
|
||||
Also note that the GPL below is copyrighted by the Free Software
|
||||
Foundation, but the instance of code that it refers to (the Linux
|
||||
kernel) is copyrighted by me and others who actually wrote it.
|
||||
|
||||
Also note that the only valid version of the GPL as far as the kernel
|
||||
is concerned is _this_ particular version of the license (ie v2, not
|
||||
v2.2 or v3.x or whatever), unless explicitly otherwise stated.
|
||||
|
||||
Linus Torvalds
|
||||
213
Devices/Libraries/Systems/can-utils/Makefile
Executable file
213
Devices/Libraries/Systems/can-utils/Makefile
Executable file
@@ -0,0 +1,213 @@
|
||||
#
|
||||
# Copyright (c) 2002-2005 Volkswagen Group Electronic Research
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions, the following disclaimer and
|
||||
# the referenced file 'COPYING'.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# Alternatively, provided that this notice is retained in full, this
|
||||
# software may be distributed under the terms of the GNU General
|
||||
# Public License ("GPL") version 2 as distributed in the 'COPYING'
|
||||
# file from the main directory of the linux kernel source.
|
||||
#
|
||||
# The provided data structures and external interfaces from this code
|
||||
# are not restricted to be used by modules with a GPL compatible license.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
# DAMAGE.
|
||||
#
|
||||
# Send feedback to <linux-can@vger.kernel.org>
|
||||
|
||||
DESTDIR ?=
|
||||
PREFIX ?= /usr/local
|
||||
|
||||
MAKEFLAGS := -k
|
||||
|
||||
CFLAGS := -O2 -Wall -Wno-parentheses -Wsign-compare
|
||||
|
||||
HAVE_FORK := $(shell ./check_cc.sh "$(CC)" fork_test.c)
|
||||
|
||||
CPPFLAGS += \
|
||||
-I. \
|
||||
-Iinclude \
|
||||
-DAF_CAN=PF_CAN \
|
||||
-DPF_CAN=29 \
|
||||
-DSO_RXQ_OVFL=40 \
|
||||
-DSCM_TIMESTAMPING_OPT_STATS=54 \
|
||||
-DCLOCK_TAI=11 \
|
||||
-DSO_TXTIME=61 \
|
||||
-DSCM_TXTIME=SO_TXTIME \
|
||||
-D_FILE_OFFSET_BITS=64 \
|
||||
-D_GNU_SOURCE
|
||||
|
||||
PROGRAMS_CANGW := \
|
||||
cangw
|
||||
|
||||
PROGRAMS_J1939_TIMEDATE := \
|
||||
j1939-timedate-srv \
|
||||
j1939-timedate-cli
|
||||
|
||||
PROGRAMS_ISOBUSFS := \
|
||||
isobusfs-srv \
|
||||
isobusfs-cli
|
||||
|
||||
PROGRAMS_ISOTP := \
|
||||
isotpdump \
|
||||
isotpperf \
|
||||
isotprecv \
|
||||
isotpsend \
|
||||
isotpsniffer \
|
||||
isotptun
|
||||
|
||||
ifeq ($(HAVE_FORK),1)
|
||||
PROGRAMS_ISOTP += \
|
||||
isotpserver
|
||||
endif
|
||||
|
||||
PROGRAMS_J1939 := \
|
||||
j1939acd \
|
||||
j1939cat \
|
||||
j1939spy \
|
||||
j1939sr \
|
||||
testj1939
|
||||
|
||||
PROGRAMS_SLCAN := \
|
||||
slcan_attach \
|
||||
slcand
|
||||
|
||||
PROGRAMS := \
|
||||
$(PROGRAMS_CANGW) \
|
||||
$(PROGRAMS_J1939_TIMEDATE) \
|
||||
$(PROGRAMS_ISOBUSFS) \
|
||||
$(PROGRAMS_ISOTP) \
|
||||
$(PROGRAMS_J1939) \
|
||||
$(PROGRAMS_SLCAN) \
|
||||
asc2log \
|
||||
can-calc-bit-timing \
|
||||
canbusload \
|
||||
candump \
|
||||
canfdtest \
|
||||
cangen \
|
||||
cansequence \
|
||||
canplayer \
|
||||
cansend \
|
||||
cansniffer \
|
||||
log2asc \
|
||||
log2long \
|
||||
mcp251xfd-dump \
|
||||
slcanpty
|
||||
|
||||
ifeq ($(HAVE_FORK),1)
|
||||
PROGRAMS += \
|
||||
canlogserver \
|
||||
bcmserver
|
||||
endif
|
||||
|
||||
all: $(PROGRAMS)
|
||||
|
||||
clean:
|
||||
rm -f $(PROGRAMS) *.o mcp251xfd/*.o isobusfs/*.o j1939_timedate/*.o
|
||||
|
||||
install:
|
||||
mkdir -p $(DESTDIR)$(PREFIX)/bin
|
||||
cp -f $(PROGRAMS) $(DESTDIR)$(PREFIX)/bin
|
||||
|
||||
distclean: clean
|
||||
rm -f $(PROGRAMS) $(LIBRARIES) *~
|
||||
|
||||
asc2log.o: lib.h
|
||||
candump.o: lib.h
|
||||
cangen.o: lib.h
|
||||
canlogserver.o: lib.h
|
||||
canplayer.o: lib.h
|
||||
cansend.o: lib.h
|
||||
log2asc.o: lib.h
|
||||
log2long.o: lib.h
|
||||
slcanpty.o: lib.h
|
||||
j1939acd.o: lib.h libj1939.h
|
||||
j1939cat.o: lib.h libj1939.h
|
||||
j1939spy.o: lib.h libj1939.h
|
||||
j1939sr.o: lib.h libj1939.h
|
||||
testj1939.o: lib.h libj1939.h
|
||||
isobusfs_srv.o: lib.h libj1939.h
|
||||
isobusfs_c.o: lib.h libj1939.h
|
||||
j1939_timedate_srv.o: lib.h libj1939.h
|
||||
j1939_timedate_cli.o: lib.h libj1939.h
|
||||
canframelen.o: canframelen.h
|
||||
|
||||
asc2log: asc2log.o lib.o
|
||||
canbusload: canbusload.o canframelen.o
|
||||
candump: candump.o lib.o
|
||||
cangen: cangen.o lib.o
|
||||
canlogserver: canlogserver.o lib.o
|
||||
canplayer: canplayer.o lib.o
|
||||
cansend: cansend.o lib.o
|
||||
cansequence: cansequence.o lib.o
|
||||
log2asc: log2asc.o lib.o
|
||||
log2long: log2long.o lib.o
|
||||
slcanpty: slcanpty.o lib.o
|
||||
j1939acd: j1939acd.o lib.o libj1939.o
|
||||
j1939cat: j1939cat.o lib.o libj1939.o
|
||||
j1939spy: j1939spy.o lib.o libj1939.o
|
||||
j1939sr: j1939sr.o lib.o libj1939.o
|
||||
testj1939: testj1939.o lib.o libj1939.o
|
||||
|
||||
j1939-timedate-srv: lib.o \
|
||||
libj1939.o \
|
||||
j1939_timedate/j1939_timedate_srv.o
|
||||
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
|
||||
|
||||
j1939-timedate-cli: lib.o \
|
||||
libj1939.o \
|
||||
j1939_timedate/j1939_timedate_cli.o
|
||||
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
|
||||
|
||||
isobusfs-srv: lib.o \
|
||||
libj1939.o \
|
||||
isobusfs/isobusfs_cmn.o \
|
||||
isobusfs/isobusfs_srv.o \
|
||||
isobusfs/isobusfs_srv_cm.o \
|
||||
isobusfs/isobusfs_srv_cm_fss.o \
|
||||
isobusfs/isobusfs_srv_dh.o \
|
||||
isobusfs/isobusfs_srv_fa.o \
|
||||
isobusfs/isobusfs_srv_fh.o \
|
||||
isobusfs/isobusfs_srv_vh.o \
|
||||
isobusfs/isobusfs_cmn_dh.o
|
||||
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
|
||||
|
||||
isobusfs-cli: lib.o \
|
||||
libj1939.o \
|
||||
isobusfs/isobusfs_cmn.o \
|
||||
isobusfs/isobusfs_cli.o \
|
||||
isobusfs/isobusfs_cli_cm.o \
|
||||
isobusfs/isobusfs_cli_dh.o \
|
||||
isobusfs/isobusfs_cli_fa.o \
|
||||
isobusfs/isobusfs_cli_selftests.o \
|
||||
isobusfs/isobusfs_cli_int.o
|
||||
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
|
||||
|
||||
can-calc-bit-timing: calc-bit-timing/can-calc-bit-timing.o
|
||||
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
|
||||
|
||||
mcp251xfd-dump: mcp251xfd/mcp251xfd-dev-coredump.o mcp251xfd/mcp251xfd-dump.o mcp251xfd/mcp251xfd-main.o mcp251xfd/mcp251xfd-regmap.o
|
||||
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
|
||||
78
Devices/Libraries/Systems/can-utils/README.md
Executable file
78
Devices/Libraries/Systems/can-utils/README.md
Executable file
@@ -0,0 +1,78 @@
|
||||
<p align="center">
|
||||
<img src="https://github.com/linux-can/can-logos/raw/master/png/SocketCAN-logo-60dpi.png" alt="SocketCAN logo"/>
|
||||
</p>
|
||||
|
||||
### SocketCAN userspace utilities and tools
|
||||
|
||||
This repository contains some userspace utilities for Linux CAN
|
||||
subsystem (aka SocketCAN):
|
||||
|
||||
#### Basic tools to display, record, generate and replay CAN traffic
|
||||
|
||||
* candump : display, filter and log CAN data to files
|
||||
* canplayer : replay CAN logfiles
|
||||
* cansend : send a single frame
|
||||
* cangen : generate (random) CAN traffic
|
||||
* cansequence : send and check sequence of CAN frames with incrementing payload
|
||||
* cansniffer : display CAN data content differences
|
||||
|
||||
#### CAN access via IP sockets
|
||||
* canlogserver : log CAN frames and serves them
|
||||
* bcmserver : interactive BCM configuration (remote/local)
|
||||
* [socketcand](https://github.com/linux-can/socketcand) : use RAW/BCM/ISO-TP sockets via TCP/IP sockets
|
||||
* [cannelloni](https://github.com/mguentner/cannelloni) : UDP/SCTP based SocketCAN tunnel
|
||||
|
||||
#### CAN in-kernel gateway configuration
|
||||
* cangw : CAN gateway userspace tool for netlink configuration
|
||||
|
||||
#### CAN bus measurement and testing
|
||||
* canbusload : calculate and display the CAN busload
|
||||
* can-calc-bit-timing : userspace version of in-kernel bitrate calculation
|
||||
* canfdtest : Full-duplex test program (DUT and host part)
|
||||
|
||||
#### ISO-TP tools [ISO15765-2:2016 for Linux](https://github.com/hartkopp/can-isotp)
|
||||
* isotpsend : send a single ISO-TP PDU
|
||||
* isotprecv : receive ISO-TP PDU(s)
|
||||
* isotpsniffer : 'wiretap' ISO-TP PDU(s)
|
||||
* isotpdump : 'wiretap' and interpret CAN messages (CAN_RAW)
|
||||
* isotpserver : IP server for simple TCP/IP <-> ISO 15765-2 bridging (ASCII HEX)
|
||||
* isotpperf : ISO15765-2 protocol performance visualisation
|
||||
* isotptun : create a bi-directional IP tunnel on CAN via ISO-TP
|
||||
|
||||
#### J1939/ISOBus tools
|
||||
* j1939acd : address claim daemon
|
||||
* j1939cat : take a file and send and receive it over CAN
|
||||
* j1939spy : spy on J1939 messages using SOC_J1939
|
||||
* j1939sr : send/recv from stdin or to stdout
|
||||
* testj1939 : send/receive test packet
|
||||
|
||||
Follow the link to see examples on how this tools can be used:
|
||||
[Kickstart guide to can-j1939 on linux](https://github.com/linux-can/can-utils/blob/master/can-j1939-kickstart.md)
|
||||
|
||||
#### Log file converters
|
||||
* asc2log : convert ASC logfile to compact CAN frame logfile
|
||||
* log2asc : convert compact CAN frame logfile to ASC logfile
|
||||
* log2long : convert compact CAN frame representation into user readable
|
||||
|
||||
#### Serial Line Discipline configuration (for slcan driver)
|
||||
* slcan_attach : userspace tool for serial line CAN interface configuration
|
||||
* slcand : daemon for serial line CAN interface configuration
|
||||
* slcanpty : creates a pty for applications using the slcan ASCII protocol
|
||||
|
||||
#### CMake Project Generator
|
||||
* Place your build folder anywhere, passing CMake the path. Relative or absolute.
|
||||
* Some examples using a build folder under the source tree root:
|
||||
* Android : ``cmake -DCMAKE_TOOLCHAIN_FILE=~/Android/Sdk/ndk-bundle/build/cmake/android.toolchain.cmake -DANDROID_PLATFORM=android-21 -DANDROID_ABI=armeabi-v7a .. && make``
|
||||
* Android Studio : Copy repo under your project's ``app`` folder, add ``add_subdirectory(can-utils)`` to your ``CMakeLists.txt`` file after ``cmake_minimum_required()``. Generating project will build Debug/Release for all supported EABI types. ie. arm64-v8a, armeabi-v7a, x86, x86_64.
|
||||
* Raspberry Pi : ``cmake -DCMAKE_TOOLCHAIN_FILE=~/rpi/tools/build/cmake/rpi.toolchain.cmake .. && make``
|
||||
* Linux : ``cmake -GNinja .. && ninja``
|
||||
* Linux Eclipse Photon (Debug) : ``CC=clang cmake -G"Eclipse CDT4 - Unix Makefiles" ../can-utils/ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_ECLIPSE_VERSION=4.8.0``
|
||||
* To override the base installation directory use: ``CMAKE_INSTALL_PREFIX``
|
||||
ie. ``CC=clang cmake -DCMAKE_INSTALL_PREFIX=./out .. && make install``
|
||||
|
||||
### Additional Information:
|
||||
|
||||
* [SocketCAN Documentation (Linux Kernel)](https://www.kernel.org/doc/Documentation/networking/can.txt)
|
||||
* [Elinux.org CAN Bus Page](http://elinux.org/CAN_Bus)
|
||||
* [Debian Package Description](https://packages.debian.org/sid/can-utils)
|
||||
|
||||
554
Devices/Libraries/Systems/can-utils/asc2log.c
Executable file
554
Devices/Libraries/Systems/can-utils/asc2log.c
Executable file
@@ -0,0 +1,554 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
|
||||
/*
|
||||
* asc2log.c - convert ASC logfile to compact CAN frame logfile
|
||||
*
|
||||
* Copyright (c) 2002-2007 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* Send feedback to <linux-can@vger.kernel.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <libgen.h>
|
||||
#include <locale.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/error.h>
|
||||
#include <net/if.h>
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#define BUFLEN 400 /* CAN FD mode lines can be pretty long */
|
||||
|
||||
extern int optind, opterr, optopt;
|
||||
|
||||
static void print_usage(char *prg)
|
||||
{
|
||||
fprintf(stderr, "%s - convert ASC logfile to compact CAN frame logfile.\n", prg);
|
||||
fprintf(stderr, "Usage: %s\n", prg);
|
||||
fprintf(stderr, "Options:\n");
|
||||
fprintf(stderr, "\t-I <infile>\t(default stdin)\n");
|
||||
fprintf(stderr, "\t-O <outfile>\t(default stdout)\n");
|
||||
}
|
||||
|
||||
static void prframe(FILE *file, struct timeval *tv, int dev,
|
||||
struct canfd_frame *cf, char *extra_info)
|
||||
{
|
||||
static char abuf[BUFLEN];
|
||||
|
||||
fprintf(file, "(%llu.%06llu) ", (unsigned long long)tv->tv_sec, (unsigned long long)tv->tv_usec);
|
||||
|
||||
if (dev > 0)
|
||||
fprintf(file, "can%d ", dev-1);
|
||||
else
|
||||
fprintf(file, "canX ");
|
||||
|
||||
snprintf_canframe(abuf, sizeof(abuf), (cu_t *)cf, 0);
|
||||
fprintf(file, "%s%s", abuf, extra_info);
|
||||
}
|
||||
|
||||
static void get_can_id(struct canfd_frame *cf, char *idstring, int base)
|
||||
{
|
||||
if (idstring[strlen(idstring)-1] == 'x') {
|
||||
cf->can_id = CAN_EFF_FLAG;
|
||||
idstring[strlen(idstring)-1] = 0;
|
||||
} else
|
||||
cf->can_id = 0;
|
||||
|
||||
cf->can_id |= strtoul(idstring, NULL, base);
|
||||
}
|
||||
|
||||
static void calc_tv(struct timeval *tv, struct timeval *read_tv,
|
||||
struct timeval *date_tv, char timestamps, int dplace)
|
||||
{
|
||||
if (dplace == 4) /* shift values having only 4 decimal places */
|
||||
read_tv->tv_usec *= 100; /* and need for 6 */
|
||||
|
||||
if (dplace == 5) /* shift values having only 5 decimal places */
|
||||
read_tv->tv_usec *= 10; /* and need for 6 */
|
||||
|
||||
if (timestamps == 'a') { /* absolute */
|
||||
|
||||
tv->tv_sec = date_tv->tv_sec + read_tv->tv_sec;
|
||||
tv->tv_usec = date_tv->tv_usec + read_tv->tv_usec;
|
||||
|
||||
} else { /* relative */
|
||||
|
||||
if (((!tv->tv_sec) && (!tv->tv_usec)) &&
|
||||
(date_tv->tv_sec || date_tv->tv_usec)) {
|
||||
tv->tv_sec = date_tv->tv_sec; /* initial date/time */
|
||||
tv->tv_usec = date_tv->tv_usec;
|
||||
}
|
||||
|
||||
tv->tv_sec += read_tv->tv_sec;
|
||||
tv->tv_usec += read_tv->tv_usec;
|
||||
}
|
||||
|
||||
if (tv->tv_usec >= 1000000) {
|
||||
tv->tv_usec -= 1000000;
|
||||
tv->tv_sec++;
|
||||
}
|
||||
}
|
||||
|
||||
static void eval_can(char* buf, struct timeval *date_tvp, char timestamps,
|
||||
char base, int dplace, FILE *outfile)
|
||||
{
|
||||
int interface;
|
||||
static struct timeval tv; /* current frame timestamp */
|
||||
static struct timeval read_tv; /* frame timestamp from ASC file */
|
||||
struct canfd_frame cf = { 0 };
|
||||
struct can_frame *ccf = (struct can_frame *)&cf; /* for len8_dlc */
|
||||
char rtr;
|
||||
int dlc = 0;
|
||||
int len = 0;
|
||||
int data[8];
|
||||
char tmp1[BUFLEN];
|
||||
char dir[3]; /* 'Rx' or 'Tx' plus terminating zero */
|
||||
char *extra_info;
|
||||
int i, items;
|
||||
unsigned long long sec, usec;
|
||||
|
||||
/* check for ErrorFrames */
|
||||
if (sscanf(buf, "%llu.%llu %d %s",
|
||||
&sec, &usec,
|
||||
&interface, tmp1) == 4) {
|
||||
read_tv.tv_sec = sec;
|
||||
read_tv.tv_usec = usec;
|
||||
|
||||
if (!strncmp(tmp1, "ErrorFrame", strlen("ErrorFrame"))) {
|
||||
|
||||
/* do not know more than 'Error' */
|
||||
cf.can_id = (CAN_ERR_FLAG | CAN_ERR_BUSERROR);
|
||||
cf.len = CAN_ERR_DLC;
|
||||
|
||||
calc_tv(&tv, &read_tv, date_tvp, timestamps, dplace);
|
||||
prframe(outfile, &tv, interface, &cf, "\n");
|
||||
fflush(outfile);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* 0.002367 1 390x Rx d 8 17 00 14 00 C0 00 08 00 */
|
||||
|
||||
/* check for CAN frames with (hexa)decimal values */
|
||||
if (base == 'h')
|
||||
items = sscanf(buf, "%llu.%llu %d %s %2s %c %x %x %x %x %x %x %x %x %x",
|
||||
&sec, &usec, &interface,
|
||||
tmp1, dir, &rtr, &dlc,
|
||||
&data[0], &data[1], &data[2], &data[3],
|
||||
&data[4], &data[5], &data[6], &data[7]);
|
||||
else
|
||||
items = sscanf(buf, "%llu.%llu %d %s %2s %c %x %d %d %d %d %d %d %d %d",
|
||||
&sec, &usec, &interface,
|
||||
tmp1, dir, &rtr, &dlc,
|
||||
&data[0], &data[1], &data[2], &data[3],
|
||||
&data[4], &data[5], &data[6], &data[7]);
|
||||
|
||||
read_tv.tv_sec = sec;
|
||||
read_tv.tv_usec = usec;
|
||||
if (items < 7 ) /* make sure we've read the dlc */
|
||||
return;
|
||||
|
||||
/* dlc is one character hex value 0..F */
|
||||
if (dlc > CAN_MAX_RAW_DLC)
|
||||
return;
|
||||
|
||||
/* retrieve real data length */
|
||||
if (dlc > CAN_MAX_DLC)
|
||||
len = CAN_MAX_DLEN;
|
||||
else
|
||||
len = dlc;
|
||||
|
||||
if ((items == len + 7 ) || /* data frame */
|
||||
((items == 6) && (rtr == 'r')) || /* RTR without DLC */
|
||||
((items == 7) && (rtr == 'r'))) { /* RTR with DLC */
|
||||
|
||||
/* check for CAN ID with (hexa)decimal value */
|
||||
if (base == 'h')
|
||||
get_can_id(&cf, tmp1, 16);
|
||||
else
|
||||
get_can_id(&cf, tmp1, 10);
|
||||
|
||||
/* dlc > 8 => len == CAN_MAX_DLEN => fill len8_dlc value */
|
||||
if (dlc > CAN_MAX_DLC)
|
||||
ccf->len8_dlc = dlc;
|
||||
|
||||
if (strlen(dir) != 2) /* "Rx" or "Tx" */
|
||||
return;
|
||||
|
||||
/* check for signed integer overflow */
|
||||
if (dplace == 4 && read_tv.tv_usec >= INT_MAX / 100)
|
||||
return;
|
||||
|
||||
if (dplace == 5 && read_tv.tv_usec >= INT_MAX / 10)
|
||||
return;
|
||||
|
||||
if (dir[0] == 'R')
|
||||
extra_info = " R\n";
|
||||
else
|
||||
extra_info = " T\n";
|
||||
|
||||
cf.len = len;
|
||||
if (rtr == 'r')
|
||||
cf.can_id |= CAN_RTR_FLAG;
|
||||
else
|
||||
for (i = 0; i < len; i++)
|
||||
cf.data[i] = data[i] & 0xFFU;
|
||||
|
||||
calc_tv(&tv, &read_tv, date_tvp, timestamps, dplace);
|
||||
prframe(outfile, &tv, interface, &cf, extra_info);
|
||||
fflush(outfile);
|
||||
}
|
||||
}
|
||||
|
||||
static void eval_canfd(char* buf, struct timeval *date_tvp, char timestamps,
|
||||
int dplace, FILE *outfile)
|
||||
{
|
||||
int interface;
|
||||
static struct timeval tv; /* current frame timestamp */
|
||||
static struct timeval read_tv; /* frame timestamp from ASC file */
|
||||
struct canfd_frame cf = { 0 };
|
||||
unsigned char brs, esi, ctmp;
|
||||
unsigned int flags;
|
||||
int dlc, dlen = 0;
|
||||
char tmp1[BUFLEN];
|
||||
char dir[3]; /* 'Rx' or 'Tx' plus terminating zero */
|
||||
char *extra_info;
|
||||
char *ptr;
|
||||
int i;
|
||||
unsigned long long sec, usec;
|
||||
|
||||
/* The CANFD format is mainly in hex representation but <DataLength>
|
||||
and probably some content we skip anyway. Don't trust the docs! */
|
||||
|
||||
/* 21.671796 CANFD 1 Tx 11 msgCanFdFr1 1 0 a 16 \
|
||||
00 00 00 00 00 00 00 00 00 00 00 00 00 00 59 c0 \
|
||||
100000 214 223040 80000000 46500250 460a0250 20011736 20010205 */
|
||||
|
||||
/* check for valid line without symbolic name */
|
||||
if (sscanf(buf, "%llu.%llu %*s %d %2s %s %hhx %hhx %x %d ",
|
||||
&sec, &usec, &interface,
|
||||
dir, tmp1, &brs, &esi, &dlc, &dlen) != 9) {
|
||||
|
||||
/* check for valid line with a symbolic name */
|
||||
if (sscanf(buf, "%llu.%llu %*s %d %2s %s %*s %hhx %hhx %x %d ",
|
||||
&sec, &usec, &interface,
|
||||
dir, tmp1, &brs, &esi, &dlc, &dlen) != 9) {
|
||||
|
||||
/* no valid CANFD format pattern */
|
||||
return;
|
||||
}
|
||||
}
|
||||
read_tv.tv_sec = sec;
|
||||
read_tv.tv_usec = usec;
|
||||
|
||||
/* check for allowed (unsigned) value ranges */
|
||||
if ((dlen > CANFD_MAX_DLEN) || (dlc > CANFD_MAX_DLC) ||
|
||||
(brs > 1) || (esi > 1))
|
||||
return;
|
||||
|
||||
if (strlen(dir) != 2) /* "Rx" or "Tx" */
|
||||
return;
|
||||
|
||||
/* check for signed integer overflow */
|
||||
if (dplace == 4 && read_tv.tv_usec >= INT_MAX / 100)
|
||||
return;
|
||||
|
||||
/* check for signed integer overflow */
|
||||
if (dplace == 5 && read_tv.tv_usec >= INT_MAX / 10)
|
||||
return;
|
||||
|
||||
if (dir[0] == 'R')
|
||||
extra_info = " R\n";
|
||||
else
|
||||
extra_info = " T\n";
|
||||
|
||||
/* don't trust ASCII content - sanitize data length */
|
||||
if (dlen != can_fd_dlc2len(can_fd_len2dlc(dlen)))
|
||||
return;
|
||||
|
||||
get_can_id(&cf, tmp1, 16);
|
||||
|
||||
/* now search for the beginning of the data[] content */
|
||||
sprintf(tmp1, " %x %x %x %2d ", brs, esi, dlc, dlen);
|
||||
|
||||
/* search for the pattern generated by real data */
|
||||
ptr = strcasestr(buf, tmp1);
|
||||
if (ptr == NULL)
|
||||
return;
|
||||
|
||||
ptr += strlen(tmp1); /* start of ASCII hex frame data */
|
||||
|
||||
cf.len = dlen;
|
||||
|
||||
for (i = 0; i < dlen; i++) {
|
||||
ctmp = asc2nibble(ptr[0]);
|
||||
if (ctmp > 0x0F)
|
||||
return;
|
||||
|
||||
cf.data[i] = (ctmp << 4);
|
||||
|
||||
ctmp = asc2nibble(ptr[1]);
|
||||
if (ctmp > 0x0F)
|
||||
return;
|
||||
|
||||
cf.data[i] |= ctmp;
|
||||
|
||||
ptr += 3; /* start of next ASCII hex byte */
|
||||
}
|
||||
|
||||
/* skip MessageDuration and MessageLength to get Flags value */
|
||||
if (sscanf(ptr, " %*x %*x %x ", &flags) != 1)
|
||||
return;
|
||||
|
||||
/* relevant flags in Flags field */
|
||||
#define ASC_F_RTR 0x00000010
|
||||
#define ASC_F_FDF 0x00001000
|
||||
#define ASC_F_BRS 0x00002000
|
||||
#define ASC_F_ESI 0x00004000
|
||||
|
||||
if (flags & ASC_F_FDF) {
|
||||
cf.flags = CANFD_FDF;
|
||||
if (flags & ASC_F_BRS)
|
||||
cf.flags |= CANFD_BRS;
|
||||
if (flags & ASC_F_ESI)
|
||||
cf.flags |= CANFD_ESI;
|
||||
} else {
|
||||
/* yes. The 'CANFD' format supports classic CAN content! */
|
||||
if (flags & ASC_F_RTR) {
|
||||
cf.can_id |= CAN_RTR_FLAG;
|
||||
/* dlen is always 0 for classic CAN RTR frames
|
||||
but the DLC value is valid in RTR cases */
|
||||
cf.len = dlc;
|
||||
/* sanitize payload length value */
|
||||
if (dlc > CAN_MAX_DLEN)
|
||||
cf.len = CAN_MAX_DLEN;
|
||||
}
|
||||
/* check for extra DLC when having a Classic CAN with 8 bytes payload */
|
||||
if ((cf.len == CAN_MAX_DLEN) && (dlc > CAN_MAX_DLEN) && (dlc <= CAN_MAX_RAW_DLC)) {
|
||||
struct can_frame *ccf = (struct can_frame *)&cf;
|
||||
|
||||
ccf->len8_dlc = dlc;
|
||||
}
|
||||
}
|
||||
|
||||
calc_tv(&tv, &read_tv, date_tvp, timestamps, dplace);
|
||||
prframe(outfile, &tv, interface, &cf, extra_info);
|
||||
fflush(outfile);
|
||||
|
||||
/* No support for really strange CANFD ErrorFrames format m( */
|
||||
}
|
||||
|
||||
static int get_date(struct timeval *tv, char *date)
|
||||
{
|
||||
struct tm tms;
|
||||
unsigned int msecs = 0;
|
||||
|
||||
if ((strcasestr(date, " am ") != NULL) || (strcasestr(date, " pm ") != NULL)) {
|
||||
/* assume EN/US date due to existing am/pm field */
|
||||
|
||||
if (!setlocale(LC_TIME, "en_US")) {
|
||||
fprintf(stderr, "Setting locale to 'en_US' failed!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strptime(date, "%B %d %I:%M:%S %p %Y", &tms)) {
|
||||
/* The string might contain a milliseconds value which strptime()
|
||||
does not support. So we read the ms value into the year variable
|
||||
before parsing the real year value (hack) */
|
||||
if (!strptime(date, "%B %d %I:%M:%S.%Y %p %Y", &tms))
|
||||
return 1;
|
||||
sscanf(date, "%*s %*d %*d:%*d:%*d.%3u ", &msecs);
|
||||
}
|
||||
|
||||
} else {
|
||||
/* assume DE date due to non existing am/pm field */
|
||||
|
||||
if (!setlocale(LC_TIME, "de_DE")) {
|
||||
fprintf(stderr, "Setting locale to 'de_DE' failed!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strptime(date, "%B %d %H:%M:%S %Y", &tms)) {
|
||||
/* The string might contain a milliseconds value which strptime()
|
||||
does not support. So we read the ms value into the year variable
|
||||
before parsing the real year value (hack) */
|
||||
if (!strptime(date, "%B %d %H:%M:%S.%Y %Y", &tms))
|
||||
return 1;
|
||||
sscanf(date, "%*s %*d %*d:%*d:%*d.%3u ", &msecs);
|
||||
}
|
||||
}
|
||||
|
||||
//printf("h %d m %d s %d ms %03d d %d m %d y %d\n",
|
||||
//tms.tm_hour, tms.tm_min, tms.tm_sec, msecs,
|
||||
//tms.tm_mday, tms.tm_mon+1, tms.tm_year+1900);
|
||||
|
||||
tv->tv_sec = mktime(&tms);
|
||||
tv->tv_usec = msecs * 1000;
|
||||
|
||||
if (tv->tv_sec < 0)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
char buf[BUFLEN], tmp1[BUFLEN], tmp2[BUFLEN];
|
||||
|
||||
FILE *infile = stdin;
|
||||
FILE *outfile = stdout;
|
||||
static int verbose;
|
||||
static struct timeval date_tv; /* date of the ASC file */
|
||||
static int dplace; /* decimal place 4, 5 or 6 or uninitialized */
|
||||
static char base; /* 'd'ec or 'h'ex */
|
||||
static char timestamps; /* 'a'bsolute or 'r'elative */
|
||||
int opt;
|
||||
unsigned long long sec, usec;
|
||||
|
||||
while ((opt = getopt(argc, argv, "I:O:v?")) != -1) {
|
||||
switch (opt) {
|
||||
case 'I':
|
||||
infile = fopen(optarg, "r");
|
||||
if (!infile) {
|
||||
perror("infile");
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'O':
|
||||
outfile = fopen(optarg, "w");
|
||||
if (!outfile) {
|
||||
perror("outfile");
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
verbose = 1;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
print_usage(basename(argv[0]));
|
||||
return 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unknown option %c\n", opt);
|
||||
print_usage(basename(argv[0]));
|
||||
return 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
while (fgets(buf, BUFLEN-1, infile)) {
|
||||
|
||||
if (!dplace) { /* the representation of a valid CAN frame not known */
|
||||
|
||||
/* check for base and timestamp entries in the header */
|
||||
if ((!base) &&
|
||||
(sscanf(buf, "base %s timestamps %s", tmp1, tmp2) == 2)) {
|
||||
base = tmp1[0];
|
||||
timestamps = tmp2[0];
|
||||
if (verbose)
|
||||
printf("base %c timestamps %c\n", base, timestamps);
|
||||
if ((base != 'h') && (base != 'd')) {
|
||||
printf("invalid base %s (must be 'hex' or 'dez')!\n",
|
||||
tmp1);
|
||||
return 1;
|
||||
}
|
||||
if ((timestamps != 'a') && (timestamps != 'r')) {
|
||||
printf("invalid timestamps %s (must be 'absolute'"
|
||||
" or 'relative')!\n", tmp2);
|
||||
return 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* check for the original logging date in the header */
|
||||
if ((!date_tv.tv_sec) &&
|
||||
(!strncmp(buf, "date", 4))) {
|
||||
|
||||
if (get_date(&date_tv, &buf[9])) { /* skip 'date day ' */
|
||||
fprintf(stderr, "Not able to determine original log "
|
||||
"file date. Using current time.\n");
|
||||
/* use current date as default */
|
||||
gettimeofday(&date_tv, NULL);
|
||||
}
|
||||
if (verbose)
|
||||
printf("date %llu => %s", (unsigned long long)date_tv.tv_sec, ctime(&date_tv.tv_sec));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* check for decimal places length in valid CAN frames */
|
||||
if (sscanf(buf, "%llu.%s %s ", &sec, tmp2,
|
||||
tmp1) != 3)
|
||||
continue; /* dplace remains zero until first found CAN frame */
|
||||
|
||||
dplace = strlen(tmp2);
|
||||
if (verbose)
|
||||
printf("decimal place %d, e.g. '%s'\n", dplace,
|
||||
tmp2);
|
||||
if (dplace < 4 || dplace > 6) {
|
||||
printf("invalid dplace %d (must be 4, 5 or 6)!\n",
|
||||
dplace);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* the representation of a valid CAN frame is known here */
|
||||
/* so try to get CAN frames and ErrorFrames and convert them */
|
||||
|
||||
/* check classic CAN format or the CANFD tag which can take both types */
|
||||
if (sscanf(buf, "%llu.%llu %s ", &sec, &usec, tmp1) == 3){
|
||||
if (!strncmp(tmp1, "CANFD", 5))
|
||||
eval_canfd(buf, &date_tv, timestamps, dplace, outfile);
|
||||
else
|
||||
eval_can(buf, &date_tv, timestamps, base, dplace, outfile);
|
||||
}
|
||||
}
|
||||
fclose(outfile);
|
||||
fclose(infile);
|
||||
return 0;
|
||||
}
|
||||
369
Devices/Libraries/Systems/can-utils/bcmserver.c
Executable file
369
Devices/Libraries/Systems/can-utils/bcmserver.c
Executable file
@@ -0,0 +1,369 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
|
||||
/*
|
||||
* tst-bcm-server.c
|
||||
*
|
||||
* Test program that implements a socket server which understands ASCII
|
||||
* messages for simple broadcast manager frame send commands.
|
||||
*
|
||||
* < interface command ival_s ival_us can_id can_dlc [data]* >
|
||||
*
|
||||
* Only the items 'can_id' and 'data' are given in (ASCII) hexadecimal values.
|
||||
*
|
||||
* ## TX path:
|
||||
*
|
||||
* The commands are 'A'dd, 'U'pdate, 'D'elete and 'S'end.
|
||||
* e.g.
|
||||
*
|
||||
* Send the CAN frame 123#1122334455667788 every second on vcan1
|
||||
* < vcan1 A 1 0 123 8 11 22 33 44 55 66 77 88 >
|
||||
*
|
||||
* Send the CAN frame 123#1122334455667788 every 10 usecs on vcan1
|
||||
* < vcan1 A 0 10 123 8 11 22 33 44 55 66 77 88 >
|
||||
*
|
||||
* Send the CAN frame 123#42424242 every 20 msecs on vcan1
|
||||
* < vcan1 A 0 20000 123 4 42 42 42 42 >
|
||||
*
|
||||
* Update the CAN frame 123#42424242 with 123#112233 - no change of timers
|
||||
* < vcan1 U 0 0 123 3 11 22 33 >
|
||||
*
|
||||
* Delete the cyclic send job from above
|
||||
* < vcan1 D 0 0 123 0 >
|
||||
*
|
||||
* Send a single CAN frame without cyclic transmission
|
||||
* < can0 S 0 0 123 0 >
|
||||
*
|
||||
* When the socket is closed the cyclic transmissions are terminated.
|
||||
*
|
||||
* ## RX path:
|
||||
*
|
||||
* The commands are 'R'eceive setup, 'F'ilter ID Setup and 'X' for delete.
|
||||
* e.g.
|
||||
*
|
||||
* Receive CAN ID 0x123 from vcan1 and check for changes in the first byte
|
||||
* < vcan1 R 0 0 123 1 FF >
|
||||
*
|
||||
* Receive CAN ID 0x123 from vcan1 and check for changes in given mask
|
||||
* < vcan1 R 0 0 123 8 FF 00 F8 00 00 00 00 00 >
|
||||
*
|
||||
* As above but throttle receive update rate down to 1.5 seconds
|
||||
* < vcan1 R 1 500000 123 8 FF 00 F8 00 00 00 00 00 >
|
||||
*
|
||||
* Filter for CAN ID 0x123 from vcan1 without content filtering
|
||||
* < vcan1 F 0 0 123 0 >
|
||||
*
|
||||
* Delete receive filter ('R' or 'F') for CAN ID 0x123
|
||||
* < vcan1 X 0 0 123 0 >
|
||||
*
|
||||
* CAN messages received by the given filters are send in the format:
|
||||
* < interface can_id can_dlc [data]* >
|
||||
*
|
||||
* e.g. when receiving a CAN message from vcan1 with
|
||||
* can_id 0x123 , data length 4 and data 0x11, 0x22, 0x33 and 0x44
|
||||
*
|
||||
* < vcan1 123 4 11 22 33 44 >
|
||||
*
|
||||
* ##
|
||||
*
|
||||
* Authors:
|
||||
* Andre Naujoks (the socket server stuff)
|
||||
* Oliver Hartkopp (the rest)
|
||||
*
|
||||
* Copyright (c) 2002-2009 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* Send feedback to <linux-can@vger.kernel.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/bcm.h>
|
||||
|
||||
#define MAXLEN 100
|
||||
#define FORMATSZ 80
|
||||
#define PORT 28600
|
||||
|
||||
void childdied(int i)
|
||||
{
|
||||
wait(NULL);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
|
||||
int sl, sa, sc;
|
||||
int i;
|
||||
int idx = 0;
|
||||
struct sockaddr_in saddr, clientaddr;
|
||||
struct sockaddr_can caddr;
|
||||
socklen_t caddrlen = sizeof(caddr);
|
||||
struct ifreq ifr;
|
||||
fd_set readfds;
|
||||
socklen_t sin_size = sizeof(clientaddr);
|
||||
struct sigaction signalaction;
|
||||
sigset_t sigset;
|
||||
|
||||
char buf[MAXLEN];
|
||||
char format[FORMATSZ];
|
||||
char rxmsg[50];
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wpragmas"
|
||||
#pragma GCC diagnostic ignored "-Wgnu-variable-sized-type-not-at-end"
|
||||
struct {
|
||||
struct bcm_msg_head msg_head;
|
||||
struct can_frame frame;
|
||||
} msg;
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
if (snprintf(format, FORMATSZ, "< %%%ds %%c %%lu %%lu %%x %%hhu "
|
||||
"%%hhx %%hhx %%hhx %%hhx %%hhx %%hhx "
|
||||
"%%hhx %%hhx >", IFNAMSIZ-1) >= FORMATSZ-1)
|
||||
exit(1);
|
||||
|
||||
sigemptyset(&sigset);
|
||||
signalaction.sa_handler = &childdied;
|
||||
signalaction.sa_mask = sigset;
|
||||
signalaction.sa_flags = 0;
|
||||
sigaction(SIGCHLD, &signalaction, NULL); /* signal for dying child */
|
||||
|
||||
if((sl = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
|
||||
perror("inetsocket");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
saddr.sin_family = AF_INET;
|
||||
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
saddr.sin_port = htons(PORT);
|
||||
|
||||
while(bind(sl,(struct sockaddr*)&saddr, sizeof(saddr)) < 0) {
|
||||
struct timespec f = {
|
||||
.tv_nsec = 100 * 1000 * 1000,
|
||||
};
|
||||
|
||||
printf(".");fflush(NULL);
|
||||
nanosleep(&f, NULL);
|
||||
}
|
||||
|
||||
if (listen(sl,3) != 0) {
|
||||
perror("listen");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
while (1) {
|
||||
sa = accept(sl,(struct sockaddr *)&clientaddr, &sin_size);
|
||||
if (sa > 0 ){
|
||||
if (!fork())
|
||||
break;
|
||||
close(sa);
|
||||
}
|
||||
else {
|
||||
if (errno != EINTR) {
|
||||
/*
|
||||
* If the cause for the error was NOT the
|
||||
* signal from a dying child => give an error
|
||||
*/
|
||||
perror("accept");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* open BCM socket */
|
||||
|
||||
if ((sc = socket(PF_CAN, SOCK_DGRAM, CAN_BCM)) < 0) {
|
||||
perror("bcmsocket");
|
||||
return 1;
|
||||
}
|
||||
|
||||
memset(&caddr, 0, sizeof(caddr));
|
||||
caddr.can_family = PF_CAN;
|
||||
/* can_ifindex is set to 0 (any device) => need for sendto() */
|
||||
|
||||
if (connect(sc, (struct sockaddr *)&caddr, sizeof(caddr)) < 0) {
|
||||
perror("connect");
|
||||
return 1;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(sc, &readfds);
|
||||
FD_SET(sa, &readfds);
|
||||
|
||||
select((sc > sa)?sc+1:sa+1, &readfds, NULL, NULL, NULL);
|
||||
|
||||
if (FD_ISSET(sc, &readfds)) {
|
||||
|
||||
recvfrom(sc, &msg, sizeof(msg), 0,
|
||||
(struct sockaddr*)&caddr, &caddrlen);
|
||||
|
||||
ifr.ifr_ifindex = caddr.can_ifindex;
|
||||
ioctl(sc, SIOCGIFNAME, &ifr);
|
||||
|
||||
sprintf(rxmsg, "< %s %03X %d ", ifr.ifr_name,
|
||||
msg.msg_head.can_id, msg.frame.can_dlc);
|
||||
|
||||
for ( i = 0; i < msg.frame.can_dlc; i++)
|
||||
sprintf(rxmsg + strlen(rxmsg), "%02X ",
|
||||
msg.frame.data[i]);
|
||||
|
||||
/* delimiter '\0' for Adobe(TM) Flash(TM) XML sockets */
|
||||
strcat(rxmsg, ">\0");
|
||||
|
||||
send(sa, rxmsg, strlen(rxmsg) + 1, 0);
|
||||
}
|
||||
|
||||
|
||||
if (FD_ISSET(sa, &readfds)) {
|
||||
|
||||
char cmd;
|
||||
int items;
|
||||
|
||||
if (read(sa, buf+idx, 1) < 1)
|
||||
exit(1);
|
||||
|
||||
if (!idx) {
|
||||
if (buf[0] == '<')
|
||||
idx = 1;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (idx > MAXLEN-2) {
|
||||
idx = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (buf[idx] != '>') {
|
||||
idx++;
|
||||
continue;
|
||||
}
|
||||
|
||||
buf[idx+1] = 0;
|
||||
idx = 0;
|
||||
|
||||
//printf("read '%s'\n", buf);
|
||||
|
||||
/* prepare bcm message settings */
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.msg_head.nframes = 1;
|
||||
|
||||
items = sscanf(buf, format,
|
||||
ifr.ifr_name,
|
||||
&cmd,
|
||||
&msg.msg_head.ival2.tv_sec,
|
||||
&msg.msg_head.ival2.tv_usec,
|
||||
&msg.msg_head.can_id,
|
||||
&msg.frame.can_dlc,
|
||||
&msg.frame.data[0],
|
||||
&msg.frame.data[1],
|
||||
&msg.frame.data[2],
|
||||
&msg.frame.data[3],
|
||||
&msg.frame.data[4],
|
||||
&msg.frame.data[5],
|
||||
&msg.frame.data[6],
|
||||
&msg.frame.data[7]);
|
||||
|
||||
if (items < 6)
|
||||
break;
|
||||
if (msg.frame.can_dlc > 8)
|
||||
break;
|
||||
if (items != 6 + msg.frame.can_dlc)
|
||||
break;
|
||||
|
||||
msg.frame.can_id = msg.msg_head.can_id;
|
||||
|
||||
switch (cmd) {
|
||||
case 'S':
|
||||
msg.msg_head.opcode = TX_SEND;
|
||||
break;
|
||||
case 'A':
|
||||
msg.msg_head.opcode = TX_SETUP;
|
||||
msg.msg_head.flags |= SETTIMER | STARTTIMER;
|
||||
break;
|
||||
case 'U':
|
||||
msg.msg_head.opcode = TX_SETUP;
|
||||
msg.msg_head.flags = 0;
|
||||
break;
|
||||
case 'D':
|
||||
msg.msg_head.opcode = TX_DELETE;
|
||||
break;
|
||||
|
||||
case 'R':
|
||||
msg.msg_head.opcode = RX_SETUP;
|
||||
msg.msg_head.flags = SETTIMER;
|
||||
break;
|
||||
case 'F':
|
||||
msg.msg_head.opcode = RX_SETUP;
|
||||
msg.msg_head.flags = RX_FILTER_ID | SETTIMER;
|
||||
break;
|
||||
case 'X':
|
||||
msg.msg_head.opcode = RX_DELETE;
|
||||
break;
|
||||
default:
|
||||
printf("unknown command '%c'.\n", cmd);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!ioctl(sc, SIOCGIFINDEX, &ifr)) {
|
||||
caddr.can_ifindex = ifr.ifr_ifindex;
|
||||
sendto(sc, &msg, sizeof(msg), 0,
|
||||
(struct sockaddr*)&caddr, sizeof(caddr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(sc);
|
||||
close(sa);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
|
||||
#include "compat.h"
|
||||
|
||||
/*
|
||||
* imported from v2.6.31-rc1~330^2~376
|
||||
*
|
||||
* 39549eef3587 can: CAN Network device driver and Netlink interface
|
||||
*
|
||||
* cherry-picked for easier integration:
|
||||
* 61463a30f652 can: make function can_get_bittiming static
|
||||
* aabdfd6adb80 can: replace the dev_dbg/info/err/... with the new netdev_xxx macros
|
||||
* 08da7da41ea4 can: provide a separate bittiming_const parameter to bittiming functions
|
||||
* b25a437206ed can: dev: remove unused variable from can_calc_bittiming() function
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2005 Marc Kleine-Budde, Pengutronix
|
||||
* Copyright (C) 2006 Andrey Volkov, Varma Electronics
|
||||
* Copyright (C) 2008-2009 Wolfgang Grandegger <wg@grandegger.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the version 2 of the GNU General Public License
|
||||
* as published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* Bit-timing calculation derived from:
|
||||
*
|
||||
* Code based on LinCAN sources and H8S2638 project
|
||||
* Copyright 2004-2006 Pavel Pisa - DCE FELK CVUT cz
|
||||
* Copyright 2005 Stanislav Marek
|
||||
* email: pisa@cmp.felk.cvut.cz
|
||||
*
|
||||
* Calculates proper bit-timing parameters for a specified bit-rate
|
||||
* and sample-point, which can then be used to set the bit-timing
|
||||
* registers of the CAN controller. You can find more information
|
||||
* in the header file linux/can/netlink.h.
|
||||
*/
|
||||
static int can_update_spt(const struct can_bittiming_const *btc,
|
||||
int sampl_pt, int tseg, int *tseg1, int *tseg2)
|
||||
{
|
||||
*tseg2 = tseg + 1 - (sampl_pt * (tseg + 1)) / 1000;
|
||||
if (*tseg2 < btc->tseg2_min)
|
||||
*tseg2 = btc->tseg2_min;
|
||||
if (*tseg2 > btc->tseg2_max)
|
||||
*tseg2 = btc->tseg2_max;
|
||||
*tseg1 = tseg - *tseg2;
|
||||
if (*tseg1 > btc->tseg1_max) {
|
||||
*tseg1 = btc->tseg1_max;
|
||||
*tseg2 = tseg - *tseg1;
|
||||
}
|
||||
return 1000 * (tseg + 1 - *tseg2) / (tseg + 1);
|
||||
}
|
||||
|
||||
static int can_calc_bittiming(struct net_device *dev, struct can_bittiming *bt,
|
||||
const struct can_bittiming_const *btc)
|
||||
{
|
||||
struct can_priv *priv = netdev_priv(dev);
|
||||
long best_error = 1000000000, error = 0;
|
||||
int best_tseg = 0, best_brp = 0, brp = 0;
|
||||
int tsegall, tseg = 0, tseg1 = 0, tseg2 = 0;
|
||||
int spt_error = 1000, spt = 0, sampl_pt;
|
||||
long rate;
|
||||
u64 v64;
|
||||
|
||||
/* Use CIA recommended sample points */
|
||||
if (bt->sample_point) {
|
||||
sampl_pt = bt->sample_point;
|
||||
} else {
|
||||
if (bt->bitrate > 800000)
|
||||
sampl_pt = 750;
|
||||
else if (bt->bitrate > 500000)
|
||||
sampl_pt = 800;
|
||||
else
|
||||
sampl_pt = 875;
|
||||
}
|
||||
|
||||
/* tseg even = round down, odd = round up */
|
||||
for (tseg = (btc->tseg1_max + btc->tseg2_max) * 2 + 1;
|
||||
tseg >= (btc->tseg1_min + btc->tseg2_min) * 2; tseg--) {
|
||||
tsegall = 1 + tseg / 2;
|
||||
/* Compute all possible tseg choices (tseg=tseg1+tseg2) */
|
||||
brp = priv->clock.freq / (tsegall * bt->bitrate) + tseg % 2;
|
||||
/* chose brp step which is possible in system */
|
||||
brp = (brp / btc->brp_inc) * btc->brp_inc;
|
||||
if ((brp < btc->brp_min) || (brp > btc->brp_max))
|
||||
continue;
|
||||
rate = priv->clock.freq / (brp * tsegall);
|
||||
error = bt->bitrate - rate;
|
||||
/* tseg brp biterror */
|
||||
if (error < 0)
|
||||
error = -error;
|
||||
if (error > best_error)
|
||||
continue;
|
||||
best_error = error;
|
||||
if (error == 0) {
|
||||
spt = can_update_spt(btc, sampl_pt, tseg / 2,
|
||||
&tseg1, &tseg2);
|
||||
error = sampl_pt - spt;
|
||||
if (error < 0)
|
||||
error = -error;
|
||||
if (error > spt_error)
|
||||
continue;
|
||||
spt_error = error;
|
||||
}
|
||||
best_tseg = tseg / 2;
|
||||
best_brp = brp;
|
||||
if (error == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (best_error) {
|
||||
/* Error in one-tenth of a percent */
|
||||
error = (best_error * 1000) / bt->bitrate;
|
||||
if (error > CAN_CALC_MAX_ERROR) {
|
||||
netdev_err(dev,
|
||||
"bitrate error %ld.%ld%% too high\n",
|
||||
error / 10, error % 10);
|
||||
return -EDOM;
|
||||
} else {
|
||||
netdev_warn(dev, "bitrate error %ld.%ld%%\n",
|
||||
error / 10, error % 10);
|
||||
}
|
||||
}
|
||||
|
||||
/* real sample point */
|
||||
bt->sample_point = can_update_spt(btc, sampl_pt, best_tseg,
|
||||
&tseg1, &tseg2);
|
||||
|
||||
v64 = (u64)best_brp * 1000000000UL;
|
||||
do_div(v64, priv->clock.freq);
|
||||
bt->tq = (u32)v64;
|
||||
bt->prop_seg = tseg1 / 2;
|
||||
bt->phase_seg1 = tseg1 - bt->prop_seg;
|
||||
bt->phase_seg2 = tseg2;
|
||||
bt->sjw = 1;
|
||||
bt->brp = best_brp;
|
||||
/* real bit-rate */
|
||||
bt->bitrate = priv->clock.freq / (bt->brp * (tseg1 + tseg2 + 1));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks the validity of the specified bit-timing parameters prop_seg,
|
||||
* phase_seg1, phase_seg2 and sjw and tries to determine the bitrate
|
||||
* prescaler value brp. You can find more information in the header
|
||||
* file linux/can/netlink.h.
|
||||
*/
|
||||
static int can_fixup_bittiming(struct net_device *dev, struct can_bittiming *bt,
|
||||
const struct can_bittiming_const *btc)
|
||||
{
|
||||
struct can_priv *priv = netdev_priv(dev);
|
||||
int tseg1, alltseg;
|
||||
u64 brp64;
|
||||
|
||||
tseg1 = bt->prop_seg + bt->phase_seg1;
|
||||
if (!bt->sjw)
|
||||
bt->sjw = 1;
|
||||
if (bt->sjw > btc->sjw_max ||
|
||||
tseg1 < btc->tseg1_min || tseg1 > btc->tseg1_max ||
|
||||
bt->phase_seg2 < btc->tseg2_min || bt->phase_seg2 > btc->tseg2_max)
|
||||
return -ERANGE;
|
||||
|
||||
brp64 = (u64)priv->clock.freq * (u64)bt->tq;
|
||||
if (btc->brp_inc > 1)
|
||||
do_div(brp64, btc->brp_inc);
|
||||
brp64 += 500000000UL - 1;
|
||||
do_div(brp64, 1000000000UL); /* the practicable BRP */
|
||||
if (btc->brp_inc > 1)
|
||||
brp64 *= btc->brp_inc;
|
||||
bt->brp = (u32)brp64;
|
||||
|
||||
if (bt->brp < btc->brp_min || bt->brp > btc->brp_max)
|
||||
return -EINVAL;
|
||||
|
||||
alltseg = bt->prop_seg + bt->phase_seg1 + bt->phase_seg2 + 1;
|
||||
bt->bitrate = priv->clock.freq / (bt->brp * alltseg);
|
||||
bt->sample_point = ((tseg1 + 1) * 1000) / alltseg;
|
||||
|
||||
return 0;
|
||||
}
|
||||
196
Devices/Libraries/Systems/can-utils/calc-bit-timing/can-calc-bit-timing-v3_18.c
Executable file
196
Devices/Libraries/Systems/can-utils/calc-bit-timing/can-calc-bit-timing-v3_18.c
Executable file
@@ -0,0 +1,196 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
|
||||
#include "compat.h"
|
||||
|
||||
/*
|
||||
* imported from v3.18-rc1~52^2~248^2~1
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2005 Marc Kleine-Budde, Pengutronix
|
||||
* Copyright (C) 2006 Andrey Volkov, Varma Electronics
|
||||
* Copyright (C) 2008-2009 Wolfgang Grandegger <wg@grandegger.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the version 2 of the GNU General Public License
|
||||
* as published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Bit-timing calculation derived from:
|
||||
*
|
||||
* Code based on LinCAN sources and H8S2638 project
|
||||
* Copyright 2004-2006 Pavel Pisa - DCE FELK CVUT cz
|
||||
* Copyright 2005 Stanislav Marek
|
||||
* email: pisa@cmp.felk.cvut.cz
|
||||
*
|
||||
* Calculates proper bit-timing parameters for a specified bit-rate
|
||||
* and sample-point, which can then be used to set the bit-timing
|
||||
* registers of the CAN controller. You can find more information
|
||||
* in the header file linux/can/netlink.h.
|
||||
*/
|
||||
static int can_update_spt(const struct can_bittiming_const *btc,
|
||||
int sampl_pt, int tseg, int *tseg1, int *tseg2)
|
||||
{
|
||||
*tseg2 = tseg + 1 - (sampl_pt * (tseg + 1)) / 1000;
|
||||
if (*tseg2 < btc->tseg2_min)
|
||||
*tseg2 = btc->tseg2_min;
|
||||
if (*tseg2 > btc->tseg2_max)
|
||||
*tseg2 = btc->tseg2_max;
|
||||
*tseg1 = tseg - *tseg2;
|
||||
if (*tseg1 > btc->tseg1_max) {
|
||||
*tseg1 = btc->tseg1_max;
|
||||
*tseg2 = tseg - *tseg1;
|
||||
}
|
||||
return 1000 * (tseg + 1 - *tseg2) / (tseg + 1);
|
||||
}
|
||||
|
||||
static int can_calc_bittiming(struct net_device *dev, struct can_bittiming *bt,
|
||||
const struct can_bittiming_const *btc)
|
||||
{
|
||||
struct can_priv *priv = netdev_priv(dev);
|
||||
long best_error = 1000000000, error = 0;
|
||||
int best_tseg = 0, best_brp = 0, brp = 0;
|
||||
int tsegall, tseg = 0, tseg1 = 0, tseg2 = 0;
|
||||
int spt_error = 1000, spt = 0, sampl_pt;
|
||||
long rate;
|
||||
u64 v64;
|
||||
|
||||
/* Use CIA recommended sample points */
|
||||
if (bt->sample_point) {
|
||||
sampl_pt = bt->sample_point;
|
||||
} else {
|
||||
if (bt->bitrate > 800000)
|
||||
sampl_pt = 750;
|
||||
else if (bt->bitrate > 500000)
|
||||
sampl_pt = 800;
|
||||
else
|
||||
sampl_pt = 875;
|
||||
}
|
||||
|
||||
/* tseg even = round down, odd = round up */
|
||||
for (tseg = (btc->tseg1_max + btc->tseg2_max) * 2 + 1;
|
||||
tseg >= (btc->tseg1_min + btc->tseg2_min) * 2; tseg--) {
|
||||
tsegall = 1 + tseg / 2;
|
||||
/* Compute all possible tseg choices (tseg=tseg1+tseg2) */
|
||||
brp = priv->clock.freq / (tsegall * bt->bitrate) + tseg % 2;
|
||||
/* chose brp step which is possible in system */
|
||||
brp = (brp / btc->brp_inc) * btc->brp_inc;
|
||||
if ((brp < btc->brp_min) || (brp > btc->brp_max))
|
||||
continue;
|
||||
rate = priv->clock.freq / (brp * tsegall);
|
||||
error = bt->bitrate - rate;
|
||||
/* tseg brp biterror */
|
||||
if (error < 0)
|
||||
error = -error;
|
||||
if (error > best_error)
|
||||
continue;
|
||||
best_error = error;
|
||||
if (error == 0) {
|
||||
spt = can_update_spt(btc, sampl_pt, tseg / 2,
|
||||
&tseg1, &tseg2);
|
||||
error = sampl_pt - spt;
|
||||
if (error < 0)
|
||||
error = -error;
|
||||
if (error > spt_error)
|
||||
continue;
|
||||
spt_error = error;
|
||||
}
|
||||
best_tseg = tseg / 2;
|
||||
best_brp = brp;
|
||||
if (error == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (best_error) {
|
||||
/* Error in one-tenth of a percent */
|
||||
error = (best_error * 1000) / bt->bitrate;
|
||||
if (error > CAN_CALC_MAX_ERROR) {
|
||||
netdev_err(dev,
|
||||
"bitrate error %ld.%ld%% too high\n",
|
||||
error / 10, error % 10);
|
||||
return -EDOM;
|
||||
} else {
|
||||
netdev_warn(dev, "bitrate error %ld.%ld%%\n",
|
||||
error / 10, error % 10);
|
||||
}
|
||||
}
|
||||
|
||||
/* real sample point */
|
||||
bt->sample_point = can_update_spt(btc, sampl_pt, best_tseg,
|
||||
&tseg1, &tseg2);
|
||||
|
||||
v64 = (u64)best_brp * 1000000000UL;
|
||||
do_div(v64, priv->clock.freq);
|
||||
bt->tq = (u32)v64;
|
||||
bt->prop_seg = tseg1 / 2;
|
||||
bt->phase_seg1 = tseg1 - bt->prop_seg;
|
||||
bt->phase_seg2 = tseg2;
|
||||
|
||||
/* check for sjw user settings */
|
||||
if (!bt->sjw || !btc->sjw_max)
|
||||
bt->sjw = 1;
|
||||
else {
|
||||
/* bt->sjw is at least 1 -> sanitize upper bound to sjw_max */
|
||||
if (bt->sjw > btc->sjw_max)
|
||||
bt->sjw = btc->sjw_max;
|
||||
/* bt->sjw must not be higher than tseg2 */
|
||||
if (tseg2 < bt->sjw)
|
||||
bt->sjw = tseg2;
|
||||
}
|
||||
|
||||
bt->brp = best_brp;
|
||||
/* real bit-rate */
|
||||
bt->bitrate = priv->clock.freq / (bt->brp * (tseg1 + tseg2 + 1));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks the validity of the specified bit-timing parameters prop_seg,
|
||||
* phase_seg1, phase_seg2 and sjw and tries to determine the bitrate
|
||||
* prescaler value brp. You can find more information in the header
|
||||
* file linux/can/netlink.h.
|
||||
*/
|
||||
static int can_fixup_bittiming(struct net_device *dev, struct can_bittiming *bt,
|
||||
const struct can_bittiming_const *btc)
|
||||
{
|
||||
struct can_priv *priv = netdev_priv(dev);
|
||||
int tseg1, alltseg;
|
||||
u64 brp64;
|
||||
|
||||
tseg1 = bt->prop_seg + bt->phase_seg1;
|
||||
if (!bt->sjw)
|
||||
bt->sjw = 1;
|
||||
if (bt->sjw > btc->sjw_max ||
|
||||
tseg1 < btc->tseg1_min || tseg1 > btc->tseg1_max ||
|
||||
bt->phase_seg2 < btc->tseg2_min || bt->phase_seg2 > btc->tseg2_max)
|
||||
return -ERANGE;
|
||||
|
||||
brp64 = (u64)priv->clock.freq * (u64)bt->tq;
|
||||
if (btc->brp_inc > 1)
|
||||
do_div(brp64, btc->brp_inc);
|
||||
brp64 += 500000000UL - 1;
|
||||
do_div(brp64, 1000000000UL); /* the practicable BRP */
|
||||
if (btc->brp_inc > 1)
|
||||
brp64 *= btc->brp_inc;
|
||||
bt->brp = (u32)brp64;
|
||||
|
||||
if (bt->brp < btc->brp_min || bt->brp > btc->brp_max)
|
||||
return -EINVAL;
|
||||
|
||||
alltseg = bt->prop_seg + bt->phase_seg1 + bt->phase_seg2 + 1;
|
||||
bt->bitrate = priv->clock.freq / (bt->brp * alltseg);
|
||||
bt->sample_point = ((tseg1 + 1) * 1000) / alltseg;
|
||||
|
||||
return 0;
|
||||
}
|
||||
225
Devices/Libraries/Systems/can-utils/calc-bit-timing/can-calc-bit-timing-v4_8.c
Executable file
225
Devices/Libraries/Systems/can-utils/calc-bit-timing/can-calc-bit-timing-v4_8.c
Executable file
@@ -0,0 +1,225 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
|
||||
#include "compat.h"
|
||||
|
||||
/*
|
||||
* imported from v4.8-rc1~140^2~304^2~11
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2005 Marc Kleine-Budde, Pengutronix
|
||||
* Copyright (C) 2006 Andrey Volkov, Varma Electronics
|
||||
* Copyright (C) 2008-2009 Wolfgang Grandegger <wg@grandegger.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the version 2 of the GNU General Public License
|
||||
* as published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Bit-timing calculation derived from:
|
||||
*
|
||||
* Code based on LinCAN sources and H8S2638 project
|
||||
* Copyright 2004-2006 Pavel Pisa - DCE FELK CVUT cz
|
||||
* Copyright 2005 Stanislav Marek
|
||||
* email: pisa@cmp.felk.cvut.cz
|
||||
*
|
||||
* Calculates proper bit-timing parameters for a specified bit-rate
|
||||
* and sample-point, which can then be used to set the bit-timing
|
||||
* registers of the CAN controller. You can find more information
|
||||
* in the header file linux/can/netlink.h.
|
||||
*/
|
||||
static int can_update_sample_point(const struct can_bittiming_const *btc,
|
||||
unsigned int sample_point_nominal, unsigned int tseg,
|
||||
unsigned int *tseg1_ptr, unsigned int *tseg2_ptr,
|
||||
unsigned int *sample_point_error_ptr)
|
||||
{
|
||||
unsigned int sample_point_error, best_sample_point_error = UINT_MAX;
|
||||
unsigned int sample_point, best_sample_point = 0;
|
||||
unsigned int tseg1, tseg2;
|
||||
int i;
|
||||
|
||||
for (i = 0; i <= 1; i++) {
|
||||
tseg2 = tseg + CAN_CALC_SYNC_SEG - (sample_point_nominal * (tseg + CAN_CALC_SYNC_SEG)) / 1000 - i;
|
||||
tseg2 = clamp(tseg2, btc->tseg2_min, btc->tseg2_max);
|
||||
tseg1 = tseg - tseg2;
|
||||
if (tseg1 > btc->tseg1_max) {
|
||||
tseg1 = btc->tseg1_max;
|
||||
tseg2 = tseg - tseg1;
|
||||
}
|
||||
|
||||
sample_point = 1000 * (tseg + CAN_CALC_SYNC_SEG - tseg2) / (tseg + CAN_CALC_SYNC_SEG);
|
||||
sample_point_error = abs(sample_point_nominal - sample_point);
|
||||
|
||||
if ((sample_point <= sample_point_nominal) && (sample_point_error < best_sample_point_error)) {
|
||||
best_sample_point = sample_point;
|
||||
best_sample_point_error = sample_point_error;
|
||||
*tseg1_ptr = tseg1;
|
||||
*tseg2_ptr = tseg2;
|
||||
}
|
||||
}
|
||||
|
||||
if (sample_point_error_ptr)
|
||||
*sample_point_error_ptr = best_sample_point_error;
|
||||
|
||||
return best_sample_point;
|
||||
}
|
||||
|
||||
static int can_calc_bittiming(struct net_device *dev, struct can_bittiming *bt,
|
||||
const struct can_bittiming_const *btc)
|
||||
{
|
||||
struct can_priv *priv = netdev_priv(dev);
|
||||
unsigned int bitrate; /* current bitrate */
|
||||
unsigned int bitrate_error; /* difference between current and nominal value */
|
||||
unsigned int best_bitrate_error = UINT_MAX;
|
||||
unsigned int sample_point_error; /* difference between current and nominal value */
|
||||
unsigned int best_sample_point_error = UINT_MAX;
|
||||
unsigned int sample_point_nominal; /* nominal sample point */
|
||||
unsigned int best_tseg = 0; /* current best value for tseg */
|
||||
unsigned int best_brp = 0; /* current best value for brp */
|
||||
unsigned int brp, tsegall, tseg, tseg1 = 0, tseg2 = 0;
|
||||
u64 v64;
|
||||
|
||||
/* Use CiA recommended sample points */
|
||||
if (bt->sample_point) {
|
||||
sample_point_nominal = bt->sample_point;
|
||||
} else {
|
||||
if (bt->bitrate > 800000)
|
||||
sample_point_nominal = 750;
|
||||
else if (bt->bitrate > 500000)
|
||||
sample_point_nominal = 800;
|
||||
else
|
||||
sample_point_nominal = 875;
|
||||
}
|
||||
|
||||
/* tseg even = round down, odd = round up */
|
||||
for (tseg = (btc->tseg1_max + btc->tseg2_max) * 2 + 1;
|
||||
tseg >= (btc->tseg1_min + btc->tseg2_min) * 2; tseg--) {
|
||||
tsegall = CAN_CALC_SYNC_SEG + tseg / 2;
|
||||
|
||||
/* Compute all possible tseg choices (tseg=tseg1+tseg2) */
|
||||
brp = priv->clock.freq / (tsegall * bt->bitrate) + tseg % 2;
|
||||
|
||||
/* choose brp step which is possible in system */
|
||||
brp = (brp / btc->brp_inc) * btc->brp_inc;
|
||||
if ((brp < btc->brp_min) || (brp > btc->brp_max))
|
||||
continue;
|
||||
|
||||
bitrate = priv->clock.freq / (brp * tsegall);
|
||||
bitrate_error = abs(bt->bitrate - bitrate);
|
||||
|
||||
/* tseg brp biterror */
|
||||
if (bitrate_error > best_bitrate_error)
|
||||
continue;
|
||||
|
||||
/* reset sample point error if we have a better bitrate */
|
||||
if (bitrate_error < best_bitrate_error)
|
||||
best_sample_point_error = UINT_MAX;
|
||||
|
||||
can_update_sample_point(btc, sample_point_nominal, tseg / 2, &tseg1, &tseg2, &sample_point_error);
|
||||
if (sample_point_error > best_sample_point_error)
|
||||
continue;
|
||||
|
||||
best_sample_point_error = sample_point_error;
|
||||
best_bitrate_error = bitrate_error;
|
||||
best_tseg = tseg / 2;
|
||||
best_brp = brp;
|
||||
|
||||
if (bitrate_error == 0 && sample_point_error == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (best_bitrate_error) {
|
||||
/* Error in one-tenth of a percent */
|
||||
v64 = (u64)best_bitrate_error * 1000;
|
||||
do_div(v64, bt->bitrate);
|
||||
bitrate_error = (u32)v64;
|
||||
if (bitrate_error > CAN_CALC_MAX_ERROR) {
|
||||
netdev_err(dev,
|
||||
"bitrate error %d.%d%% too high\n",
|
||||
bitrate_error / 10, bitrate_error % 10);
|
||||
return -EDOM;
|
||||
}
|
||||
netdev_warn(dev, "bitrate error %d.%d%%\n",
|
||||
bitrate_error / 10, bitrate_error % 10);
|
||||
}
|
||||
|
||||
/* real sample point */
|
||||
bt->sample_point = can_update_sample_point(btc, sample_point_nominal, best_tseg,
|
||||
&tseg1, &tseg2, NULL);
|
||||
|
||||
v64 = (u64)best_brp * 1000 * 1000 * 1000;
|
||||
do_div(v64, priv->clock.freq);
|
||||
bt->tq = (u32)v64;
|
||||
bt->prop_seg = tseg1 / 2;
|
||||
bt->phase_seg1 = tseg1 - bt->prop_seg;
|
||||
bt->phase_seg2 = tseg2;
|
||||
|
||||
/* check for sjw user settings */
|
||||
if (!bt->sjw || !btc->sjw_max) {
|
||||
bt->sjw = 1;
|
||||
} else {
|
||||
/* bt->sjw is at least 1 -> sanitize upper bound to sjw_max */
|
||||
if (bt->sjw > btc->sjw_max)
|
||||
bt->sjw = btc->sjw_max;
|
||||
/* bt->sjw must not be higher than tseg2 */
|
||||
if (tseg2 < bt->sjw)
|
||||
bt->sjw = tseg2;
|
||||
}
|
||||
|
||||
bt->brp = best_brp;
|
||||
|
||||
/* real bitrate */
|
||||
bt->bitrate = priv->clock.freq / (bt->brp * (CAN_CALC_SYNC_SEG + tseg1 + tseg2));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks the validity of the specified bit-timing parameters prop_seg,
|
||||
* phase_seg1, phase_seg2 and sjw and tries to determine the bitrate
|
||||
* prescaler value brp. You can find more information in the header
|
||||
* file linux/can/netlink.h.
|
||||
*/
|
||||
static int can_fixup_bittiming(struct net_device *dev, struct can_bittiming *bt,
|
||||
const struct can_bittiming_const *btc)
|
||||
{
|
||||
struct can_priv *priv = netdev_priv(dev);
|
||||
int tseg1, alltseg;
|
||||
u64 brp64;
|
||||
|
||||
tseg1 = bt->prop_seg + bt->phase_seg1;
|
||||
if (!bt->sjw)
|
||||
bt->sjw = 1;
|
||||
if (bt->sjw > btc->sjw_max ||
|
||||
tseg1 < btc->tseg1_min || tseg1 > btc->tseg1_max ||
|
||||
bt->phase_seg2 < btc->tseg2_min || bt->phase_seg2 > btc->tseg2_max)
|
||||
return -ERANGE;
|
||||
|
||||
brp64 = (u64)priv->clock.freq * (u64)bt->tq;
|
||||
if (btc->brp_inc > 1)
|
||||
do_div(brp64, btc->brp_inc);
|
||||
brp64 += 500000000UL - 1;
|
||||
do_div(brp64, 1000000000UL); /* the practicable BRP */
|
||||
if (btc->brp_inc > 1)
|
||||
brp64 *= btc->brp_inc;
|
||||
bt->brp = (u32)brp64;
|
||||
|
||||
if (bt->brp < btc->brp_min || bt->brp > btc->brp_max)
|
||||
return -EINVAL;
|
||||
|
||||
alltseg = bt->prop_seg + bt->phase_seg1 + bt->phase_seg2 + 1;
|
||||
bt->bitrate = priv->clock.freq / (bt->brp * alltseg);
|
||||
bt->sample_point = ((tseg1 + 1) * 1000) / alltseg;
|
||||
|
||||
return 0;
|
||||
}
|
||||
218
Devices/Libraries/Systems/can-utils/calc-bit-timing/can-calc-bit-timing-v5_16.c
Executable file
218
Devices/Libraries/Systems/can-utils/calc-bit-timing/can-calc-bit-timing-v5_16.c
Executable file
@@ -0,0 +1,218 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
|
||||
#include "compat.h"
|
||||
|
||||
/*
|
||||
* imported from v5.16-rc1~159^2~104^2~13
|
||||
*
|
||||
*/
|
||||
|
||||
/* Copyright (C) 2005 Marc Kleine-Budde, Pengutronix
|
||||
* Copyright (C) 2006 Andrey Volkov, Varma Electronics
|
||||
* Copyright (C) 2008-2009 Wolfgang Grandegger <wg@grandegger.com>
|
||||
*/
|
||||
|
||||
/* Bit-timing calculation derived from:
|
||||
*
|
||||
* Code based on LinCAN sources and H8S2638 project
|
||||
* Copyright 2004-2006 Pavel Pisa - DCE FELK CVUT cz
|
||||
* Copyright 2005 Stanislav Marek
|
||||
* email: pisa@cmp.felk.cvut.cz
|
||||
*
|
||||
* Calculates proper bit-timing parameters for a specified bit-rate
|
||||
* and sample-point, which can then be used to set the bit-timing
|
||||
* registers of the CAN controller. You can find more information
|
||||
* in the header file linux/can/netlink.h.
|
||||
*/
|
||||
static int
|
||||
can_update_sample_point(const struct can_bittiming_const *btc,
|
||||
unsigned int sample_point_nominal, unsigned int tseg,
|
||||
unsigned int *tseg1_ptr, unsigned int *tseg2_ptr,
|
||||
unsigned int *sample_point_error_ptr)
|
||||
{
|
||||
unsigned int sample_point_error, best_sample_point_error = UINT_MAX;
|
||||
unsigned int sample_point, best_sample_point = 0;
|
||||
unsigned int tseg1, tseg2;
|
||||
int i;
|
||||
|
||||
for (i = 0; i <= 1; i++) {
|
||||
tseg2 = tseg + CAN_SYNC_SEG -
|
||||
(sample_point_nominal * (tseg + CAN_SYNC_SEG)) /
|
||||
1000 - i;
|
||||
tseg2 = clamp(tseg2, btc->tseg2_min, btc->tseg2_max);
|
||||
tseg1 = tseg - tseg2;
|
||||
if (tseg1 > btc->tseg1_max) {
|
||||
tseg1 = btc->tseg1_max;
|
||||
tseg2 = tseg - tseg1;
|
||||
}
|
||||
|
||||
sample_point = 1000 * (tseg + CAN_SYNC_SEG - tseg2) /
|
||||
(tseg + CAN_SYNC_SEG);
|
||||
sample_point_error = abs(sample_point_nominal - sample_point);
|
||||
|
||||
if (sample_point <= sample_point_nominal &&
|
||||
sample_point_error < best_sample_point_error) {
|
||||
best_sample_point = sample_point;
|
||||
best_sample_point_error = sample_point_error;
|
||||
*tseg1_ptr = tseg1;
|
||||
*tseg2_ptr = tseg2;
|
||||
}
|
||||
}
|
||||
|
||||
if (sample_point_error_ptr)
|
||||
*sample_point_error_ptr = best_sample_point_error;
|
||||
|
||||
return best_sample_point;
|
||||
}
|
||||
|
||||
int can_calc_bittiming(struct net_device *dev, struct can_bittiming *bt,
|
||||
const struct can_bittiming_const *btc)
|
||||
{
|
||||
struct can_priv *priv = netdev_priv(dev);
|
||||
unsigned int bitrate; /* current bitrate */
|
||||
unsigned int bitrate_error; /* difference between current and nominal value */
|
||||
unsigned int best_bitrate_error = UINT_MAX;
|
||||
unsigned int sample_point_error; /* difference between current and nominal value */
|
||||
unsigned int best_sample_point_error = UINT_MAX;
|
||||
unsigned int sample_point_nominal; /* nominal sample point */
|
||||
unsigned int best_tseg = 0; /* current best value for tseg */
|
||||
unsigned int best_brp = 0; /* current best value for brp */
|
||||
unsigned int brp, tsegall, tseg, tseg1 = 0, tseg2 = 0;
|
||||
u64 v64;
|
||||
|
||||
/* Use CiA recommended sample points */
|
||||
if (bt->sample_point) {
|
||||
sample_point_nominal = bt->sample_point;
|
||||
} else {
|
||||
if (bt->bitrate > 800 * CAN_KBPS)
|
||||
sample_point_nominal = 750;
|
||||
else if (bt->bitrate > 500 * CAN_KBPS)
|
||||
sample_point_nominal = 800;
|
||||
else
|
||||
sample_point_nominal = 875;
|
||||
}
|
||||
|
||||
/* tseg even = round down, odd = round up */
|
||||
for (tseg = (btc->tseg1_max + btc->tseg2_max) * 2 + 1;
|
||||
tseg >= (btc->tseg1_min + btc->tseg2_min) * 2; tseg--) {
|
||||
tsegall = CAN_SYNC_SEG + tseg / 2;
|
||||
|
||||
/* Compute all possible tseg choices (tseg=tseg1+tseg2) */
|
||||
brp = priv->clock.freq / (tsegall * bt->bitrate) + tseg % 2;
|
||||
|
||||
/* choose brp step which is possible in system */
|
||||
brp = (brp / btc->brp_inc) * btc->brp_inc;
|
||||
if (brp < btc->brp_min || brp > btc->brp_max)
|
||||
continue;
|
||||
|
||||
bitrate = priv->clock.freq / (brp * tsegall);
|
||||
bitrate_error = abs(bt->bitrate - bitrate);
|
||||
|
||||
/* tseg brp biterror */
|
||||
if (bitrate_error > best_bitrate_error)
|
||||
continue;
|
||||
|
||||
/* reset sample point error if we have a better bitrate */
|
||||
if (bitrate_error < best_bitrate_error)
|
||||
best_sample_point_error = UINT_MAX;
|
||||
|
||||
can_update_sample_point(btc, sample_point_nominal, tseg / 2,
|
||||
&tseg1, &tseg2, &sample_point_error);
|
||||
if (sample_point_error > best_sample_point_error)
|
||||
continue;
|
||||
|
||||
best_sample_point_error = sample_point_error;
|
||||
best_bitrate_error = bitrate_error;
|
||||
best_tseg = tseg / 2;
|
||||
best_brp = brp;
|
||||
|
||||
if (bitrate_error == 0 && sample_point_error == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (best_bitrate_error) {
|
||||
/* Error in one-tenth of a percent */
|
||||
v64 = (u64)best_bitrate_error * 1000;
|
||||
do_div(v64, bt->bitrate);
|
||||
bitrate_error = (u32)v64;
|
||||
if (bitrate_error > CAN_CALC_MAX_ERROR) {
|
||||
netdev_err(dev,
|
||||
"bitrate error %d.%d%% too high\n",
|
||||
bitrate_error / 10, bitrate_error % 10);
|
||||
return -EDOM;
|
||||
}
|
||||
netdev_warn(dev, "bitrate error %d.%d%%\n",
|
||||
bitrate_error / 10, bitrate_error % 10);
|
||||
}
|
||||
|
||||
/* real sample point */
|
||||
bt->sample_point = can_update_sample_point(btc, sample_point_nominal,
|
||||
best_tseg, &tseg1, &tseg2,
|
||||
NULL);
|
||||
|
||||
v64 = (u64)best_brp * 1000 * 1000 * 1000;
|
||||
do_div(v64, priv->clock.freq);
|
||||
bt->tq = (u32)v64;
|
||||
bt->prop_seg = tseg1 / 2;
|
||||
bt->phase_seg1 = tseg1 - bt->prop_seg;
|
||||
bt->phase_seg2 = tseg2;
|
||||
|
||||
/* check for sjw user settings */
|
||||
if (!bt->sjw || !btc->sjw_max) {
|
||||
bt->sjw = 1;
|
||||
} else {
|
||||
/* bt->sjw is at least 1 -> sanitize upper bound to sjw_max */
|
||||
if (bt->sjw > btc->sjw_max)
|
||||
bt->sjw = btc->sjw_max;
|
||||
/* bt->sjw must not be higher than tseg2 */
|
||||
if (tseg2 < bt->sjw)
|
||||
bt->sjw = tseg2;
|
||||
}
|
||||
|
||||
bt->brp = best_brp;
|
||||
|
||||
/* real bitrate */
|
||||
bt->bitrate = priv->clock.freq /
|
||||
(bt->brp * (CAN_SYNC_SEG + tseg1 + tseg2));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Checks the validity of the specified bit-timing parameters prop_seg,
|
||||
* phase_seg1, phase_seg2 and sjw and tries to determine the bitrate
|
||||
* prescaler value brp. You can find more information in the header
|
||||
* file linux/can/netlink.h.
|
||||
*/
|
||||
static int can_fixup_bittiming(struct net_device *dev, struct can_bittiming *bt,
|
||||
const struct can_bittiming_const *btc)
|
||||
{
|
||||
struct can_priv *priv = netdev_priv(dev);
|
||||
unsigned int tseg1, alltseg;
|
||||
u64 brp64;
|
||||
|
||||
tseg1 = bt->prop_seg + bt->phase_seg1;
|
||||
if (!bt->sjw)
|
||||
bt->sjw = 1;
|
||||
if (bt->sjw > btc->sjw_max ||
|
||||
tseg1 < btc->tseg1_min || tseg1 > btc->tseg1_max ||
|
||||
bt->phase_seg2 < btc->tseg2_min || bt->phase_seg2 > btc->tseg2_max)
|
||||
return -ERANGE;
|
||||
|
||||
brp64 = (u64)priv->clock.freq * (u64)bt->tq;
|
||||
if (btc->brp_inc > 1)
|
||||
do_div(brp64, btc->brp_inc);
|
||||
brp64 += 500000000UL - 1;
|
||||
do_div(brp64, 1000000000UL); /* the practicable BRP */
|
||||
if (btc->brp_inc > 1)
|
||||
brp64 *= btc->brp_inc;
|
||||
bt->brp = (u32)brp64;
|
||||
|
||||
if (bt->brp < btc->brp_min || bt->brp > btc->brp_max)
|
||||
return -EINVAL;
|
||||
|
||||
alltseg = bt->prop_seg + bt->phase_seg1 + bt->phase_seg2 + 1;
|
||||
bt->bitrate = priv->clock.freq / (bt->brp * alltseg);
|
||||
bt->sample_point = ((tseg1 + 1) * 1000) / alltseg;
|
||||
|
||||
return 0;
|
||||
}
|
||||
218
Devices/Libraries/Systems/can-utils/calc-bit-timing/can-calc-bit-timing-v5_19.c
Executable file
218
Devices/Libraries/Systems/can-utils/calc-bit-timing/can-calc-bit-timing-v5_19.c
Executable file
@@ -0,0 +1,218 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
|
||||
#include "compat.h"
|
||||
|
||||
/*
|
||||
* imported from v5.19-rc1~159^2~286^2~15
|
||||
*
|
||||
*/
|
||||
|
||||
/* Copyright (C) 2005 Marc Kleine-Budde, Pengutronix
|
||||
* Copyright (C) 2006 Andrey Volkov, Varma Electronics
|
||||
* Copyright (C) 2008-2009 Wolfgang Grandegger <wg@grandegger.com>
|
||||
*/
|
||||
|
||||
/* Bit-timing calculation derived from:
|
||||
*
|
||||
* Code based on LinCAN sources and H8S2638 project
|
||||
* Copyright 2004-2006 Pavel Pisa - DCE FELK CVUT cz
|
||||
* Copyright 2005 Stanislav Marek
|
||||
* email: pisa@cmp.felk.cvut.cz
|
||||
*
|
||||
* Calculates proper bit-timing parameters for a specified bit-rate
|
||||
* and sample-point, which can then be used to set the bit-timing
|
||||
* registers of the CAN controller. You can find more information
|
||||
* in the header file linux/can/netlink.h.
|
||||
*/
|
||||
static int
|
||||
can_update_sample_point(const struct can_bittiming_const *btc,
|
||||
const unsigned int sample_point_nominal, const unsigned int tseg,
|
||||
unsigned int *tseg1_ptr, unsigned int *tseg2_ptr,
|
||||
unsigned int *sample_point_error_ptr)
|
||||
{
|
||||
unsigned int sample_point_error, best_sample_point_error = UINT_MAX;
|
||||
unsigned int sample_point, best_sample_point = 0;
|
||||
unsigned int tseg1, tseg2;
|
||||
int i;
|
||||
|
||||
for (i = 0; i <= 1; i++) {
|
||||
tseg2 = tseg + CAN_SYNC_SEG -
|
||||
(sample_point_nominal * (tseg + CAN_SYNC_SEG)) /
|
||||
1000 - i;
|
||||
tseg2 = clamp(tseg2, btc->tseg2_min, btc->tseg2_max);
|
||||
tseg1 = tseg - tseg2;
|
||||
if (tseg1 > btc->tseg1_max) {
|
||||
tseg1 = btc->tseg1_max;
|
||||
tseg2 = tseg - tseg1;
|
||||
}
|
||||
|
||||
sample_point = 1000 * (tseg + CAN_SYNC_SEG - tseg2) /
|
||||
(tseg + CAN_SYNC_SEG);
|
||||
sample_point_error = abs(sample_point_nominal - sample_point);
|
||||
|
||||
if (sample_point <= sample_point_nominal &&
|
||||
sample_point_error < best_sample_point_error) {
|
||||
best_sample_point = sample_point;
|
||||
best_sample_point_error = sample_point_error;
|
||||
*tseg1_ptr = tseg1;
|
||||
*tseg2_ptr = tseg2;
|
||||
}
|
||||
}
|
||||
|
||||
if (sample_point_error_ptr)
|
||||
*sample_point_error_ptr = best_sample_point_error;
|
||||
|
||||
return best_sample_point;
|
||||
}
|
||||
|
||||
int can_calc_bittiming(const struct net_device *dev, struct can_bittiming *bt,
|
||||
const struct can_bittiming_const *btc)
|
||||
{
|
||||
struct can_priv *priv = netdev_priv(dev);
|
||||
unsigned int bitrate; /* current bitrate */
|
||||
unsigned int bitrate_error; /* difference between current and nominal value */
|
||||
unsigned int best_bitrate_error = UINT_MAX;
|
||||
unsigned int sample_point_error; /* difference between current and nominal value */
|
||||
unsigned int best_sample_point_error = UINT_MAX;
|
||||
unsigned int sample_point_nominal; /* nominal sample point */
|
||||
unsigned int best_tseg = 0; /* current best value for tseg */
|
||||
unsigned int best_brp = 0; /* current best value for brp */
|
||||
unsigned int brp, tsegall, tseg, tseg1 = 0, tseg2 = 0;
|
||||
u64 v64;
|
||||
|
||||
/* Use CiA recommended sample points */
|
||||
if (bt->sample_point) {
|
||||
sample_point_nominal = bt->sample_point;
|
||||
} else {
|
||||
if (bt->bitrate > 800 * KILO /* BPS */)
|
||||
sample_point_nominal = 750;
|
||||
else if (bt->bitrate > 500 * KILO /* BPS */)
|
||||
sample_point_nominal = 800;
|
||||
else
|
||||
sample_point_nominal = 875;
|
||||
}
|
||||
|
||||
/* tseg even = round down, odd = round up */
|
||||
for (tseg = (btc->tseg1_max + btc->tseg2_max) * 2 + 1;
|
||||
tseg >= (btc->tseg1_min + btc->tseg2_min) * 2; tseg--) {
|
||||
tsegall = CAN_SYNC_SEG + tseg / 2;
|
||||
|
||||
/* Compute all possible tseg choices (tseg=tseg1+tseg2) */
|
||||
brp = priv->clock.freq / (tsegall * bt->bitrate) + tseg % 2;
|
||||
|
||||
/* choose brp step which is possible in system */
|
||||
brp = (brp / btc->brp_inc) * btc->brp_inc;
|
||||
if (brp < btc->brp_min || brp > btc->brp_max)
|
||||
continue;
|
||||
|
||||
bitrate = priv->clock.freq / (brp * tsegall);
|
||||
bitrate_error = abs(bt->bitrate - bitrate);
|
||||
|
||||
/* tseg brp biterror */
|
||||
if (bitrate_error > best_bitrate_error)
|
||||
continue;
|
||||
|
||||
/* reset sample point error if we have a better bitrate */
|
||||
if (bitrate_error < best_bitrate_error)
|
||||
best_sample_point_error = UINT_MAX;
|
||||
|
||||
can_update_sample_point(btc, sample_point_nominal, tseg / 2,
|
||||
&tseg1, &tseg2, &sample_point_error);
|
||||
if (sample_point_error >= best_sample_point_error)
|
||||
continue;
|
||||
|
||||
best_sample_point_error = sample_point_error;
|
||||
best_bitrate_error = bitrate_error;
|
||||
best_tseg = tseg / 2;
|
||||
best_brp = brp;
|
||||
|
||||
if (bitrate_error == 0 && sample_point_error == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (best_bitrate_error) {
|
||||
/* Error in one-tenth of a percent */
|
||||
v64 = (u64)best_bitrate_error * 1000;
|
||||
do_div(v64, bt->bitrate);
|
||||
bitrate_error = (u32)v64;
|
||||
if (bitrate_error > CAN_CALC_MAX_ERROR) {
|
||||
netdev_err(dev,
|
||||
"bitrate error %d.%d%% too high\n",
|
||||
bitrate_error / 10, bitrate_error % 10);
|
||||
return -EDOM;
|
||||
}
|
||||
netdev_warn(dev, "bitrate error %d.%d%%\n",
|
||||
bitrate_error / 10, bitrate_error % 10);
|
||||
}
|
||||
|
||||
/* real sample point */
|
||||
bt->sample_point = can_update_sample_point(btc, sample_point_nominal,
|
||||
best_tseg, &tseg1, &tseg2,
|
||||
NULL);
|
||||
|
||||
v64 = (u64)best_brp * 1000 * 1000 * 1000;
|
||||
do_div(v64, priv->clock.freq);
|
||||
bt->tq = (u32)v64;
|
||||
bt->prop_seg = tseg1 / 2;
|
||||
bt->phase_seg1 = tseg1 - bt->prop_seg;
|
||||
bt->phase_seg2 = tseg2;
|
||||
|
||||
/* check for sjw user settings */
|
||||
if (!bt->sjw || !btc->sjw_max) {
|
||||
bt->sjw = 1;
|
||||
} else {
|
||||
/* bt->sjw is at least 1 -> sanitize upper bound to sjw_max */
|
||||
if (bt->sjw > btc->sjw_max)
|
||||
bt->sjw = btc->sjw_max;
|
||||
/* bt->sjw must not be higher than tseg2 */
|
||||
if (tseg2 < bt->sjw)
|
||||
bt->sjw = tseg2;
|
||||
}
|
||||
|
||||
bt->brp = best_brp;
|
||||
|
||||
/* real bitrate */
|
||||
bt->bitrate = priv->clock.freq /
|
||||
(bt->brp * (CAN_SYNC_SEG + tseg1 + tseg2));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Checks the validity of the specified bit-timing parameters prop_seg,
|
||||
* phase_seg1, phase_seg2 and sjw and tries to determine the bitrate
|
||||
* prescaler value brp. You can find more information in the header
|
||||
* file linux/can/netlink.h.
|
||||
*/
|
||||
static int can_fixup_bittiming(const struct net_device *dev, struct can_bittiming *bt,
|
||||
const struct can_bittiming_const *btc)
|
||||
{
|
||||
const struct can_priv *priv = netdev_priv(dev);
|
||||
unsigned int tseg1, alltseg;
|
||||
u64 brp64;
|
||||
|
||||
tseg1 = bt->prop_seg + bt->phase_seg1;
|
||||
if (!bt->sjw)
|
||||
bt->sjw = 1;
|
||||
if (bt->sjw > btc->sjw_max ||
|
||||
tseg1 < btc->tseg1_min || tseg1 > btc->tseg1_max ||
|
||||
bt->phase_seg2 < btc->tseg2_min || bt->phase_seg2 > btc->tseg2_max)
|
||||
return -ERANGE;
|
||||
|
||||
brp64 = (u64)priv->clock.freq * (u64)bt->tq;
|
||||
if (btc->brp_inc > 1)
|
||||
do_div(brp64, btc->brp_inc);
|
||||
brp64 += 500000000UL - 1;
|
||||
do_div(brp64, 1000000000UL); /* the practicable BRP */
|
||||
if (btc->brp_inc > 1)
|
||||
brp64 *= btc->brp_inc;
|
||||
bt->brp = (u32)brp64;
|
||||
|
||||
if (bt->brp < btc->brp_min || bt->brp > btc->brp_max)
|
||||
return -EINVAL;
|
||||
|
||||
alltseg = bt->prop_seg + bt->phase_seg1 + bt->phase_seg2 + 1;
|
||||
bt->bitrate = priv->clock.freq / (bt->brp * alltseg);
|
||||
bt->sample_point = ((tseg1 + 1) * 1000) / alltseg;
|
||||
|
||||
return 0;
|
||||
}
|
||||
292
Devices/Libraries/Systems/can-utils/calc-bit-timing/can-calc-bit-timing-v6_3.c
Executable file
292
Devices/Libraries/Systems/can-utils/calc-bit-timing/can-calc-bit-timing-v6_3.c
Executable file
@@ -0,0 +1,292 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
|
||||
#include "compat.h"
|
||||
|
||||
/*
|
||||
* imported from v6.3-rc1~162^2~124^2^2~1
|
||||
*
|
||||
*/
|
||||
|
||||
void can_sjw_set_default(struct can_bittiming *bt)
|
||||
{
|
||||
if (bt->sjw)
|
||||
return;
|
||||
|
||||
/* If user space provides no sjw, use sane default of phase_seg2 / 2 */
|
||||
bt->sjw = max(1U, min(bt->phase_seg1, bt->phase_seg2 / 2));
|
||||
}
|
||||
|
||||
int can_sjw_check(const struct net_device *dev, const struct can_bittiming *bt,
|
||||
const struct can_bittiming_const *btc, struct netlink_ext_ack *extack)
|
||||
{
|
||||
if (bt->sjw > btc->sjw_max) {
|
||||
NL_SET_ERR_MSG_FMT(extack, "sjw: %u greater than max sjw: %u",
|
||||
bt->sjw, btc->sjw_max);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (bt->sjw > bt->phase_seg1) {
|
||||
NL_SET_ERR_MSG_FMT(extack,
|
||||
"sjw: %u greater than phase-seg1: %u",
|
||||
bt->sjw, bt->phase_seg1);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (bt->sjw > bt->phase_seg2) {
|
||||
NL_SET_ERR_MSG_FMT(extack,
|
||||
"sjw: %u greater than phase-seg2: %u",
|
||||
bt->sjw, bt->phase_seg2);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* can_bit_time() - Duration of one bit
|
||||
*
|
||||
* Please refer to ISO 11898-1:2015, section 11.3.1.1 "Bit time" for
|
||||
* additional information.
|
||||
*
|
||||
* Return: the number of time quanta in one bit.
|
||||
*/
|
||||
static inline unsigned int can_bit_time(const struct can_bittiming *bt)
|
||||
{
|
||||
return CAN_SYNC_SEG + bt->prop_seg + bt->phase_seg1 + bt->phase_seg2;
|
||||
}
|
||||
|
||||
/* Copyright (C) 2005 Marc Kleine-Budde, Pengutronix
|
||||
* Copyright (C) 2006 Andrey Volkov, Varma Electronics
|
||||
* Copyright (C) 2008-2009 Wolfgang Grandegger <wg@grandegger.com>
|
||||
*/
|
||||
|
||||
/* Bit-timing calculation derived from:
|
||||
*
|
||||
* Code based on LinCAN sources and H8S2638 project
|
||||
* Copyright 2004-2006 Pavel Pisa - DCE FELK CVUT cz
|
||||
* Copyright 2005 Stanislav Marek
|
||||
* email: pisa@cmp.felk.cvut.cz
|
||||
*
|
||||
* Calculates proper bit-timing parameters for a specified bit-rate
|
||||
* and sample-point, which can then be used to set the bit-timing
|
||||
* registers of the CAN controller. You can find more information
|
||||
* in the header file linux/can/netlink.h.
|
||||
*/
|
||||
static int
|
||||
can_update_sample_point(const struct can_bittiming_const *btc,
|
||||
const unsigned int sample_point_nominal, const unsigned int tseg,
|
||||
unsigned int *tseg1_ptr, unsigned int *tseg2_ptr,
|
||||
unsigned int *sample_point_error_ptr)
|
||||
{
|
||||
unsigned int sample_point_error, best_sample_point_error = UINT_MAX;
|
||||
unsigned int sample_point, best_sample_point = 0;
|
||||
unsigned int tseg1, tseg2;
|
||||
int i;
|
||||
|
||||
for (i = 0; i <= 1; i++) {
|
||||
tseg2 = tseg + CAN_SYNC_SEG -
|
||||
(sample_point_nominal * (tseg + CAN_SYNC_SEG)) /
|
||||
1000 - i;
|
||||
tseg2 = clamp(tseg2, btc->tseg2_min, btc->tseg2_max);
|
||||
tseg1 = tseg - tseg2;
|
||||
if (tseg1 > btc->tseg1_max) {
|
||||
tseg1 = btc->tseg1_max;
|
||||
tseg2 = tseg - tseg1;
|
||||
}
|
||||
|
||||
sample_point = 1000 * (tseg + CAN_SYNC_SEG - tseg2) /
|
||||
(tseg + CAN_SYNC_SEG);
|
||||
sample_point_error = abs(sample_point_nominal - sample_point);
|
||||
|
||||
if (sample_point <= sample_point_nominal &&
|
||||
sample_point_error < best_sample_point_error) {
|
||||
best_sample_point = sample_point;
|
||||
best_sample_point_error = sample_point_error;
|
||||
*tseg1_ptr = tseg1;
|
||||
*tseg2_ptr = tseg2;
|
||||
}
|
||||
}
|
||||
|
||||
if (sample_point_error_ptr)
|
||||
*sample_point_error_ptr = best_sample_point_error;
|
||||
|
||||
return best_sample_point;
|
||||
}
|
||||
|
||||
int can_calc_bittiming(const struct net_device *dev, struct can_bittiming *bt,
|
||||
const struct can_bittiming_const *btc, struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct can_priv *priv = netdev_priv(dev);
|
||||
unsigned int bitrate; /* current bitrate */
|
||||
unsigned int bitrate_error; /* difference between current and nominal value */
|
||||
unsigned int best_bitrate_error = UINT_MAX;
|
||||
unsigned int sample_point_error; /* difference between current and nominal value */
|
||||
unsigned int best_sample_point_error = UINT_MAX;
|
||||
unsigned int sample_point_nominal; /* nominal sample point */
|
||||
unsigned int best_tseg = 0; /* current best value for tseg */
|
||||
unsigned int best_brp = 0; /* current best value for brp */
|
||||
unsigned int brp, tsegall, tseg, tseg1 = 0, tseg2 = 0;
|
||||
u64 v64;
|
||||
int err;
|
||||
|
||||
/* Use CiA recommended sample points */
|
||||
if (bt->sample_point) {
|
||||
sample_point_nominal = bt->sample_point;
|
||||
} else {
|
||||
if (bt->bitrate > 800 * KILO /* BPS */)
|
||||
sample_point_nominal = 750;
|
||||
else if (bt->bitrate > 500 * KILO /* BPS */)
|
||||
sample_point_nominal = 800;
|
||||
else
|
||||
sample_point_nominal = 875;
|
||||
}
|
||||
|
||||
/* tseg even = round down, odd = round up */
|
||||
for (tseg = (btc->tseg1_max + btc->tseg2_max) * 2 + 1;
|
||||
tseg >= (btc->tseg1_min + btc->tseg2_min) * 2; tseg--) {
|
||||
tsegall = CAN_SYNC_SEG + tseg / 2;
|
||||
|
||||
/* Compute all possible tseg choices (tseg=tseg1+tseg2) */
|
||||
brp = priv->clock.freq / (tsegall * bt->bitrate) + tseg % 2;
|
||||
|
||||
/* choose brp step which is possible in system */
|
||||
brp = (brp / btc->brp_inc) * btc->brp_inc;
|
||||
if (brp < btc->brp_min || brp > btc->brp_max)
|
||||
continue;
|
||||
|
||||
bitrate = priv->clock.freq / (brp * tsegall);
|
||||
bitrate_error = abs(bt->bitrate - bitrate);
|
||||
|
||||
/* tseg brp biterror */
|
||||
if (bitrate_error > best_bitrate_error)
|
||||
continue;
|
||||
|
||||
/* reset sample point error if we have a better bitrate */
|
||||
if (bitrate_error < best_bitrate_error)
|
||||
best_sample_point_error = UINT_MAX;
|
||||
|
||||
can_update_sample_point(btc, sample_point_nominal, tseg / 2,
|
||||
&tseg1, &tseg2, &sample_point_error);
|
||||
if (sample_point_error >= best_sample_point_error)
|
||||
continue;
|
||||
|
||||
best_sample_point_error = sample_point_error;
|
||||
best_bitrate_error = bitrate_error;
|
||||
best_tseg = tseg / 2;
|
||||
best_brp = brp;
|
||||
|
||||
if (bitrate_error == 0 && sample_point_error == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (best_bitrate_error) {
|
||||
/* Error in one-tenth of a percent */
|
||||
v64 = (u64)best_bitrate_error * 1000;
|
||||
do_div(v64, bt->bitrate);
|
||||
bitrate_error = (u32)v64;
|
||||
if (bitrate_error > CAN_CALC_MAX_ERROR) {
|
||||
NL_SET_ERR_MSG_FMT(extack,
|
||||
"bitrate error: %u.%u%% too high",
|
||||
bitrate_error / 10, bitrate_error % 10);
|
||||
return -EINVAL;
|
||||
}
|
||||
NL_SET_ERR_MSG_FMT(extack,
|
||||
"bitrate error: %u.%u%%",
|
||||
bitrate_error / 10, bitrate_error % 10);
|
||||
}
|
||||
|
||||
/* real sample point */
|
||||
bt->sample_point = can_update_sample_point(btc, sample_point_nominal,
|
||||
best_tseg, &tseg1, &tseg2,
|
||||
NULL);
|
||||
|
||||
v64 = (u64)best_brp * 1000 * 1000 * 1000;
|
||||
do_div(v64, priv->clock.freq);
|
||||
bt->tq = (u32)v64;
|
||||
bt->prop_seg = tseg1 / 2;
|
||||
bt->phase_seg1 = tseg1 - bt->prop_seg;
|
||||
bt->phase_seg2 = tseg2;
|
||||
|
||||
can_sjw_set_default(bt);
|
||||
|
||||
err = can_sjw_check(dev, bt, btc, extack);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
bt->brp = best_brp;
|
||||
|
||||
/* real bitrate */
|
||||
bt->bitrate = priv->clock.freq /
|
||||
(bt->brp * can_bit_time(bt));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Checks the validity of the specified bit-timing parameters prop_seg,
|
||||
* phase_seg1, phase_seg2 and sjw and tries to determine the bitrate
|
||||
* prescaler value brp. You can find more information in the header
|
||||
* file linux/can/netlink.h.
|
||||
*/
|
||||
static int can_fixup_bittiming(const struct net_device *dev, struct can_bittiming *bt,
|
||||
const struct can_bittiming_const *btc,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
const unsigned int tseg1 = bt->prop_seg + bt->phase_seg1;
|
||||
const struct can_priv *priv = netdev_priv(dev);
|
||||
u64 brp64;
|
||||
int err;
|
||||
|
||||
if (tseg1 < btc->tseg1_min) {
|
||||
NL_SET_ERR_MSG_FMT(extack, "prop-seg + phase-seg1: %u less than tseg1-min: %u",
|
||||
tseg1, btc->tseg1_min);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (tseg1 > btc->tseg1_max) {
|
||||
NL_SET_ERR_MSG_FMT(extack, "prop-seg + phase-seg1: %u greater than tseg1-max: %u",
|
||||
tseg1, btc->tseg1_max);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (bt->phase_seg2 < btc->tseg2_min) {
|
||||
NL_SET_ERR_MSG_FMT(extack, "phase-seg2: %u less than tseg2-min: %u",
|
||||
bt->phase_seg2, btc->tseg2_min);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (bt->phase_seg2 > btc->tseg2_max) {
|
||||
NL_SET_ERR_MSG_FMT(extack, "phase-seg2: %u greater than tseg2-max: %u",
|
||||
bt->phase_seg2, btc->tseg2_max);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
can_sjw_set_default(bt);
|
||||
|
||||
err = can_sjw_check(dev, bt, btc, extack);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
brp64 = (u64)priv->clock.freq * (u64)bt->tq;
|
||||
if (btc->brp_inc > 1)
|
||||
do_div(brp64, btc->brp_inc);
|
||||
brp64 += 500000000UL - 1;
|
||||
do_div(brp64, 1000000000UL); /* the practicable BRP */
|
||||
if (btc->brp_inc > 1)
|
||||
brp64 *= btc->brp_inc;
|
||||
bt->brp = (u32)brp64;
|
||||
|
||||
if (bt->brp < btc->brp_min) {
|
||||
NL_SET_ERR_MSG_FMT(extack, "resulting brp: %u less than brp-min: %u",
|
||||
bt->brp, btc->brp_min);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (bt->brp > btc->brp_max) {
|
||||
NL_SET_ERR_MSG_FMT(extack, "resulting brp: %u greater than brp-max: %u",
|
||||
bt->brp, btc->brp_max);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bt->bitrate = priv->clock.freq / (bt->brp * can_bit_time(bt));
|
||||
bt->sample_point = ((CAN_SYNC_SEG + tseg1) * 1000) / can_bit_time(bt);
|
||||
bt->tq = DIV_U64_ROUND_CLOSEST(mul_u32_u32(bt->brp, NSEC_PER_SEC),
|
||||
priv->clock.freq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
1617
Devices/Libraries/Systems/can-utils/calc-bit-timing/can-calc-bit-timing.c
Executable file
1617
Devices/Libraries/Systems/can-utils/calc-bit-timing/can-calc-bit-timing.c
Executable file
File diff suppressed because it is too large
Load Diff
163
Devices/Libraries/Systems/can-utils/calc-bit-timing/compat.h
Executable file
163
Devices/Libraries/Systems/can-utils/calc-bit-timing/compat.h
Executable file
@@ -0,0 +1,163 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
#ifndef _COMPAT_H
|
||||
#define _COMPAT_H
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <linux/can/netlink.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
/* imported from kernel */
|
||||
|
||||
/* define in-kernel-types */
|
||||
typedef __u64 u64;
|
||||
typedef __u32 u32;
|
||||
|
||||
#define NSEC_PER_SEC 1000000000L
|
||||
|
||||
#define CAN_CALC_MAX_ERROR 50 /* in one-tenth of a percent */
|
||||
#define CAN_CALC_SYNC_SEG 1
|
||||
#define CAN_SYNC_SEG 1
|
||||
#define CAN_KBPS 1000
|
||||
#define KILO 1000UL
|
||||
|
||||
/**
|
||||
* abs - return absolute value of an argument
|
||||
* @x: the value. If it is unsigned type, it is converted to signed type first.
|
||||
* char is treated as if it was signed (regardless of whether it really is)
|
||||
* but the macro's return type is preserved as char.
|
||||
*
|
||||
* Return: an absolute value of x.
|
||||
*/
|
||||
#define abs(x) __abs_choose_expr(x, long long, \
|
||||
__abs_choose_expr(x, long, \
|
||||
__abs_choose_expr(x, int, \
|
||||
__abs_choose_expr(x, short, \
|
||||
__abs_choose_expr(x, char, \
|
||||
__builtin_choose_expr( \
|
||||
__builtin_types_compatible_p(typeof(x), char), \
|
||||
(char)({ signed char __x = (x); __x < 0 ? -__x:__x; }), \
|
||||
((void)0)))))))
|
||||
|
||||
#define __abs_choose_expr(x, type, other) __builtin_choose_expr( \
|
||||
__builtin_types_compatible_p(typeof(x), signed type) || \
|
||||
__builtin_types_compatible_p(typeof(x), unsigned type), \
|
||||
({ signed type __x = (x); __x < 0 ? -__x : __x; }), other)
|
||||
|
||||
/*
|
||||
* min()/max()/clamp() macros that also do
|
||||
* strict type-checking.. See the
|
||||
* "unnecessary" pointer comparison.
|
||||
*/
|
||||
#define min(x, y) ({ \
|
||||
typeof(x) _min1 = (x); \
|
||||
typeof(y) _min2 = (y); \
|
||||
(void) (&_min1 == &_min2); \
|
||||
_min1 < _min2 ? _min1 : _min2; })
|
||||
|
||||
#define max(x, y) ({ \
|
||||
typeof(x) _max1 = (x); \
|
||||
typeof(y) _max2 = (y); \
|
||||
(void) (&_max1 == &_max2); \
|
||||
_max1 > _max2 ? _max1 : _max2; })
|
||||
|
||||
/**
|
||||
* clamp - return a value clamped to a given range with strict typechecking
|
||||
* @val: current value
|
||||
* @lo: lowest allowable value
|
||||
* @hi: highest allowable value
|
||||
*
|
||||
* This macro does strict typechecking of lo/hi to make sure they are of the
|
||||
* same type as val. See the unnecessary pointer comparisons.
|
||||
*/
|
||||
#define clamp(val, lo, hi) min((typeof(val))max(val, lo), hi)
|
||||
|
||||
#define do_div(n, base) ({ \
|
||||
uint32_t __base = (base); \
|
||||
uint32_t __rem; \
|
||||
__rem = ((uint64_t)(n)) % __base; \
|
||||
(n) = ((uint64_t)(n)) / __base; \
|
||||
__rem; \
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* DIV_U64_ROUND_CLOSEST - unsigned 64bit divide with 32bit divisor rounded to nearest integer
|
||||
* @dividend: unsigned 64bit dividend
|
||||
* @divisor: unsigned 32bit divisor
|
||||
*
|
||||
* Divide unsigned 64bit dividend by unsigned 32bit divisor
|
||||
* and round to closest integer.
|
||||
*
|
||||
* Return: dividend / divisor rounded to nearest integer
|
||||
*/
|
||||
#define DIV_U64_ROUND_CLOSEST(dividend, divisor) \
|
||||
({ u32 _tmp = (divisor); div_u64((u64)(dividend) + _tmp / 2, _tmp); })
|
||||
|
||||
/**
|
||||
* div_u64_rem - unsigned 64bit divide with 32bit divisor with remainder
|
||||
* @dividend: unsigned 64bit dividend
|
||||
* @divisor: unsigned 32bit divisor
|
||||
* @remainder: pointer to unsigned 32bit remainder
|
||||
*
|
||||
* Return: sets ``*remainder``, then returns dividend / divisor
|
||||
*
|
||||
* This is commonly provided by 32bit archs to provide an optimized 64bit
|
||||
* divide.
|
||||
*/
|
||||
static inline u64 div_u64_rem(u64 dividend, u32 divisor, u32 *remainder)
|
||||
{
|
||||
*remainder = dividend % divisor;
|
||||
return dividend / divisor;
|
||||
}
|
||||
|
||||
static inline u64 div_u64(u64 dividend, u32 divisor)
|
||||
{
|
||||
u32 remainder;
|
||||
return div_u64_rem(dividend, divisor, &remainder);
|
||||
}
|
||||
|
||||
static inline u64 mul_u32_u32(u32 a, u32 b)
|
||||
{
|
||||
return (u64)a * b;
|
||||
}
|
||||
|
||||
/* */
|
||||
|
||||
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
|
||||
|
||||
/* we don't want to see these prints */
|
||||
#define netdev_err(dev, format, arg...) do { } while (0)
|
||||
#define netdev_warn(dev, format, arg...) do { } while (0)
|
||||
#define NL_SET_ERR_MSG_FMT(dev, format, arg...) do { } while (0)
|
||||
|
||||
struct calc_ref_clk {
|
||||
__u32 clk; /* CAN system clock frequency in Hz */
|
||||
const char *name;
|
||||
};
|
||||
|
||||
/*
|
||||
* minimal structs, just enough to be source level compatible
|
||||
*/
|
||||
struct can_priv {
|
||||
struct can_clock clock;
|
||||
};
|
||||
|
||||
struct net_device {
|
||||
struct can_priv priv;
|
||||
};
|
||||
|
||||
struct netlink_ext_ack {
|
||||
u32 __dummy;
|
||||
};
|
||||
|
||||
static inline void *netdev_priv(const struct net_device *dev)
|
||||
{
|
||||
return (void *)&dev->priv;
|
||||
}
|
||||
|
||||
#endif /*_COMPAT_H */
|
||||
180
Devices/Libraries/Systems/can-utils/can-j1939-install-kernel-module.md
Executable file
180
Devices/Libraries/Systems/can-utils/can-j1939-install-kernel-module.md
Executable file
@@ -0,0 +1,180 @@
|
||||
# can-j1939 kernel module installation #
|
||||
|
||||
|
||||
|
||||
### Problem
|
||||
|
||||
You already have **can0** or **vcan0** up and working, **can-utils** downloaded and compiled to **~/can/can-utils** and you can send and receive frames without problems. However, when you want to bring up **can-j1939** you get error like this:
|
||||
|
||||
```bash
|
||||
avra@vm-debian:~/can/can-utils$ sudo modprobe can-j1939
|
||||
modprobe: FATAL: Module can-j1939 not found in directory /lib/modules/5.7.0.0.bpo.2-amd64
|
||||
```
|
||||
|
||||
and also this:
|
||||
|
||||
```bash
|
||||
avra@vm-debian:~/can/can-utils$ testj1939
|
||||
testj1939: socket(j1939): Protocol not supported
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Solution
|
||||
|
||||
Above errors mean that **can-j1939** was not enabled in your kernel and you need to compile it manually. There are several ways to do it. Any Linux kernel since 5.4 has **can-j1939** module, but you will probably want to install fresher version, which leads to downloading kernel sources, enabling **can-j1939** module, recompiling kernel and installing it. I will be using Debian 10.5 x64 (buster testing) virtual machine.
|
||||
|
||||
|
||||
|
||||
#### 1. Download kernel source ####
|
||||
|
||||
We will download Debian patched kernel 5.8. First update your sources
|
||||
|
||||
```
|
||||
avra@vm-debian:~$ sudo apt update
|
||||
```
|
||||
|
||||
and then look at available Debian patched kernel source packages
|
||||
|
||||
```
|
||||
avra@vm-debian:~$ apt-cache search linux-source
|
||||
linux-source-4.19 - Linux kernel source for version 4.19 with Debian patches
|
||||
linux-source - Linux kernel source (meta-package)
|
||||
linux-source-5.4 - Linux kernel source for version 5.4 with Debian patches
|
||||
linux-source-5.5 - Linux kernel source for version 5.5 with Debian patches
|
||||
linux-source-5.6 - Linux kernel source for version 5.6 with Debian patches
|
||||
linux-source-5.7 - Linux kernel source for version 5.7 with Debian patches
|
||||
linux-source-5.8 - Linux kernel source for version 5.8 with Debian patches
|
||||
```
|
||||
|
||||
If kernel 5.8 does not show in your linux-sources list (it shows above in mine since I have already upgraded stock 4.19 kernel to backported 5.7), then you will need to add backports to your sources list. It is best to do it like this
|
||||
|
||||
```
|
||||
echo 'deb http://deb.debian.org/debian buster-backports main contrib' | sudo tee -a /etc/apt/sources.list.d/debian-backports.list
|
||||
```
|
||||
|
||||
Alternatively, or in case you have problems with installation of some packages, or you just want to have everything in a single list, here is what my **/etc/apt/sources.list** looks like (you will need to append at least last line to yours)
|
||||
|
||||
```
|
||||
deb http://security.debian.org/debian-security buster/updates main contrib
|
||||
deb-src http://security.debian.org/debian-security buster/updates main contrib
|
||||
|
||||
deb http://deb.debian.org/debian/ buster main contrib non-free
|
||||
deb-src http://deb.debian.org/debian/ buster main contrib non-free
|
||||
|
||||
deb http://deb.debian.org/debian buster-backports main contrib
|
||||
```
|
||||
|
||||
After adding backports in one way or another, try **sudo apt update** again, and after that **apt-cache search linux-source** should show kernel 5.8 in the list, so you can install it's source package
|
||||
|
||||
```
|
||||
sudo apt install linux-source-5.8
|
||||
```
|
||||
|
||||
and unpack it
|
||||
```
|
||||
avra@vm-debian:~$ cd /usr/src
|
||||
avra@vm-debian:/usr/src$ sudo tar -xaf linux-source-5.8.tar.xz
|
||||
avra@vm-debian:/usr/src$ cd linux-source-5.8
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### 2. Add can-j1939 module to kernel ####
|
||||
|
||||
First we need some packages for **menuconfig**
|
||||
|
||||
```
|
||||
sudo apt-get install libncurses5 libncurses5-dev
|
||||
```
|
||||
|
||||
copy and use our old configuration to run **menuconfig**
|
||||
|
||||
```
|
||||
avra@vm-debian:/usr/src/linux-source-5.8$ sudo cp /boot/config-$(uname -r) .config
|
||||
avra@vm-debian:/usr/src/linux-source-5.8$ sudo make menuconfig
|
||||
```
|
||||
|
||||
where we enable SAE J1939 kernel module as shown
|
||||
|
||||
```
|
||||
- Networking Support
|
||||
- Can bus subsystem support
|
||||
- <M> SAE J1939
|
||||
```
|
||||
|
||||
Now edit **/usr/src/linux-source-5.8/.config**, find CONFIG_SYSTEM_TRUSTED_KEYS, change it as following
|
||||
```
|
||||
CONFIG_SYSTEM_TRUSTED_KEYS=""
|
||||
```
|
||||
|
||||
and save the file.
|
||||
|
||||
|
||||
|
||||
#### 3. Compile and install kernel and modules
|
||||
|
||||
We will have to download necessary packages
|
||||
|
||||
```
|
||||
sudo apt install build-essential libssl-dev libelf-dev bison flex
|
||||
```
|
||||
|
||||
compile kernel (using threads to make it faster)
|
||||
|
||||
```
|
||||
avra@vm-debian:/usr/src/linux-source-5.8$ sudo make -j $(nproc)
|
||||
```
|
||||
|
||||
install
|
||||
|
||||
```
|
||||
avra@vm-debian:/usr/src/linux-source-5.8$ sudo make modules_install
|
||||
avra@vm-debian:/usr/src/linux-source-5.8$ sudo make install
|
||||
```
|
||||
|
||||
and update grub
|
||||
|
||||
```
|
||||
avra@vm-debian:/usr/src/linux-source-5.8$ sudo update-grub
|
||||
avra@vm-debian:/usr/src/linux-source-5.8$ sudo reboot
|
||||
```
|
||||
|
||||
Check if installation is correct with
|
||||
|
||||
```
|
||||
sudo modprobe can-j1939
|
||||
```
|
||||
|
||||
and if you get no error then you can enjoy **can-j1939**. If you get some error then you might check if this alternative command works:
|
||||
|
||||
```
|
||||
sudo insmod /lib/modules/5.8.10/kernel/net/can/j1939/can-j1939.ko
|
||||
```
|
||||
|
||||
If it does then all you need to do is
|
||||
|
||||
```
|
||||
sudo depmod -av
|
||||
```
|
||||
|
||||
reboot once, and **modprobe** command from the above should finally work.
|
||||
|
||||
|
||||
|
||||
#### 4. Install headers if needed
|
||||
|
||||
You might have a problem with headers not being updated. To check that open file **/usr/include/linux/can.h** with
|
||||
|
||||
```
|
||||
nano /usr/include/linux/can.h
|
||||
```
|
||||
|
||||
If in the struct **sockaddr_can** you don’t see **j1939**, then header files did not upgrade and you need to do it manually
|
||||
|
||||
```
|
||||
sudo cp /usr/src/linux-source-5.8/include/uapi/linux/can.h /usr/include/linux/can.h
|
||||
sudo cp /usr/src/linux-source-5.8/include/uapi/linux/can/j1939.h /usr/include/linux/can/
|
||||
```
|
||||
|
||||
That is the minimum for compiling some **J1939** C code, but you might want to upgrade other header files as well. That's up to you. Enjoy!
|
||||
197
Devices/Libraries/Systems/can-utils/can-j1939-kickstart.md
Executable file
197
Devices/Libraries/Systems/can-utils/can-j1939-kickstart.md
Executable file
@@ -0,0 +1,197 @@
|
||||
# Kickstart guide to can-j1939 on linux
|
||||
|
||||
## Prepare using VCAN
|
||||
|
||||
You may skip this step entirely if you have a functional
|
||||
**can0** bus on your system.
|
||||
|
||||
Load module, when *vcan* is not in-kernel
|
||||
|
||||
modprobe vcan
|
||||
|
||||
Create a virtual can0 device and start the device
|
||||
|
||||
ip link add can0 type vcan
|
||||
ip link set can0 up
|
||||
|
||||
## First steps with j1939
|
||||
|
||||
Use [testj1939](testj1939.c)
|
||||
|
||||
When *can-j1939* is compiled as module, opening a socket will load it,
|
||||
__or__ you can load it manually
|
||||
|
||||
modprobe can-j1939
|
||||
|
||||
Most of the subsequent examples will use 2 sockets programs (in 2 terminals).
|
||||
One will use CAN_J1939 sockets using *testj1939*,
|
||||
and the other will use CAN_RAW sockets using cansend+candump.
|
||||
|
||||
testj1939 can be told to print the used API calls by adding **-v** program argument.
|
||||
|
||||
### receive without source address
|
||||
|
||||
Do in terminal 1
|
||||
|
||||
testj1939 -B -r can0
|
||||
|
||||
Send raw CAN in terminal 2
|
||||
|
||||
cansend can0 1823ff40#0123
|
||||
|
||||
You should have this output in terminal 1
|
||||
|
||||
40 02300: 01 23
|
||||
|
||||
This means, from NAME 0, SA 40, PGN 02300 was received,
|
||||
with 2 databytes, *01* & *23*.
|
||||
|
||||
now emit this CAN message:
|
||||
|
||||
cansend can0 18234140#0123
|
||||
|
||||
In J1939, this means that ECU 0x40 sends directly to ECU 0x41
|
||||
Since we did not bind to address 0x41, this traffic
|
||||
is not meant for us and *testj1939* does not receive it.
|
||||
|
||||
### receive with source address
|
||||
|
||||
Terminal 1:
|
||||
|
||||
testj1939 -r can0:0x80
|
||||
|
||||
Terminal 2:
|
||||
|
||||
cansend can0 18238040#0123
|
||||
|
||||
Will emit this output
|
||||
|
||||
40 02300: 01 23
|
||||
|
||||
This is because the traffic had destination address __0x80__ .
|
||||
|
||||
### send
|
||||
|
||||
Open in terminal 1:
|
||||
|
||||
candump -L can0
|
||||
|
||||
And to these test in another terminal
|
||||
|
||||
testj1939 -B -s can0:0x80 can0:,0x3ffff
|
||||
|
||||
This produces **1BFFFF80#0123456789ABCDEF** on CAN.
|
||||
|
||||
Note: To be able to send a broadcast we need to use, we need to use "-B" flag.
|
||||
|
||||
### Multiple source addresses on 1 CAN device
|
||||
|
||||
testj1939 -B -s can0:0x90 can0:,0x3ffff
|
||||
|
||||
produces **1BFFFF90#0123456789ABCDEF** ,
|
||||
|
||||
### Use PDU1 PGN
|
||||
|
||||
testj1939 -B -s can0:0x80 can0:,0x12300
|
||||
|
||||
emits **1923FF80#0123456789ABCDEF** .
|
||||
|
||||
Note that the PGN is **0x12300**, and destination address is **0xff**.
|
||||
|
||||
### Use destination address info
|
||||
|
||||
Since in this example we use unicast source and destination addresses, we do
|
||||
not need to use "-B" (broadcast) flag.
|
||||
|
||||
The destination field may be set during sendto().
|
||||
*testj1939* implements that like this
|
||||
|
||||
testj1939 -s can0:0x80 can0:0x40,0x12300
|
||||
|
||||
emits **19234080#0123456789ABCDEF** .
|
||||
|
||||
The destination CAN iface __must__ always match the source CAN iface.
|
||||
Specifying one during bind is therefore sufficient.
|
||||
|
||||
testj1939 -s can0:0x80 :0x40,0x12300
|
||||
|
||||
emits the very same.
|
||||
|
||||
### Emit different PGNs using the same socket
|
||||
|
||||
The PGN is provided in both __bind( *sockname* )__ and
|
||||
__sendto( *peername* )__ , and only one is used.
|
||||
*peername* PGN has highest precedence.
|
||||
|
||||
For broadcasted transmissions
|
||||
|
||||
testj1939 -B -s can0:0x80 :,0x32100
|
||||
|
||||
emits **1B21FF80#0123456789ABCDEF**
|
||||
|
||||
Destination specific transmissions
|
||||
|
||||
testj1939 -s can0:0x80,0x12300 :0x40,0x32100
|
||||
|
||||
emits **1B214080#0123456789ABCDEF** .
|
||||
|
||||
It makes sometimes sense to omit the PGN in __bind( *sockname* )__ .
|
||||
|
||||
### Larger packets
|
||||
|
||||
J1939 transparently switches to *Transport Protocol* when packets
|
||||
do not fit into single CAN packets.
|
||||
|
||||
testj1939 -B -s20 can0:0x80 :,0x12300
|
||||
|
||||
emits:
|
||||
|
||||
18ECFF80#20140003FF002301
|
||||
18EBFF80#010123456789ABCD
|
||||
18EBFF80#02EF0123456789AB
|
||||
18EBFF80#03CDEF01234567FF
|
||||
|
||||
The fragments for broadcasted *Transport Protocol* are separated
|
||||
__50ms__ from each other.
|
||||
Destination specific *Transport Protocol* applies flow control
|
||||
and may emit CAN packets much faster.
|
||||
|
||||
First assign 0x90 to the local system.
|
||||
This becomes important because the kernel must interact in the
|
||||
transport protocol sessions before the complete packet is delivered.
|
||||
|
||||
testj1939 can0:0x90 -r &
|
||||
|
||||
Now test:
|
||||
|
||||
testj1939 -s20 can0:0x80 :0x90,0x12300
|
||||
|
||||
emits:
|
||||
|
||||
18EC9080#1014000303002301
|
||||
18EC8090#110301FFFF002301
|
||||
18EB9080#010123456789ABCD
|
||||
18EB9080#02EF0123456789AB
|
||||
18EB9080#03CDEF01234567FF
|
||||
18EC8090#13140003FF002301
|
||||
|
||||
The flow control causes a bit overhead.
|
||||
This overhead scales very good for larger J1939 packets.
|
||||
|
||||
## Advanced topics with j1939
|
||||
|
||||
### Change priority of J1939 packets
|
||||
|
||||
testj1939 -B -s can0:0x80 :,0x0100
|
||||
testj1939 -B -s -p3 can0:0x80 :,0x0200
|
||||
|
||||
emits
|
||||
|
||||
1801FF80#0123456789ABCDEF
|
||||
0C02FF80#0123456789ABCDEF
|
||||
|
||||
### using connect
|
||||
|
||||
### advanced filtering
|
||||
|
||||
## dynamic addressing
|
||||
98
Devices/Libraries/Systems/can-utils/can-j1939.md
Executable file
98
Devices/Libraries/Systems/can-utils/can-j1939.md
Executable file
@@ -0,0 +1,98 @@
|
||||
# CAN-J1939 on linux
|
||||
|
||||
The [Kickstart guide is here](can-j1939-kickstart.md)
|
||||
|
||||
## CAN on linux
|
||||
|
||||
See [Wikipedia:socketcan](http://en.wikipedia.org/wiki/Socketcan)
|
||||
|
||||
## J1939 networking in short
|
||||
|
||||
* Add addressing on top of CAN (destination address & broadcast)
|
||||
|
||||
* Any (max 1780) length packets.
|
||||
Packets of 9 or more use **Transport Protocol** (fragmentation)
|
||||
Such packets use different CANid for the same PGN.
|
||||
|
||||
* only **29**bit, non-**RTR** CAN frames
|
||||
|
||||
* CAN id is composed of
|
||||
* 0..8: SA (source address)
|
||||
* 9..26:
|
||||
* PDU1: PGN+DA (destination address)
|
||||
* PDU2: PGN
|
||||
* 27..29: PRIO
|
||||
|
||||
* SA / DA may be dynamically assigned via j1939-81
|
||||
Fixed rules of precedence in Specification, no master necessary
|
||||
|
||||
## J1939 on SocketCAN
|
||||
|
||||
J1939 is *just another protocol* that fits
|
||||
in the Berkely sockets.
|
||||
|
||||
socket(AF_CAN, SOCK_DGRAM, CAN_J1939)
|
||||
|
||||
## differences from CAN_RAW
|
||||
### addressing
|
||||
|
||||
SA, DA & PGN are used, not CAN id.
|
||||
|
||||
Berkeley socket API is used to communicate these to userspace:
|
||||
|
||||
* SA+PGN is put in sockname ([getsockname](http://man7.org/linux/man-pages/man2/getsockname.2.html))
|
||||
* DA+PGN is put in peername ([getpeername](http://man7.org/linux/man-pages/man2/getpeername.2.html))
|
||||
PGN is put in both structs
|
||||
|
||||
PRIO is a datalink property, and irrelevant for interpretation
|
||||
Therefore, PRIO is not in *sockname* or *peername*.
|
||||
|
||||
The *data* that is [recv][recvfrom] or [send][sendto] is the real payload.
|
||||
Unlike CAN_RAW, where addressing info is data.
|
||||
|
||||
### Packet size
|
||||
|
||||
J1939 handles packets of 8+ bytes with **Transport Protocol** fragmentation transparently.
|
||||
No fixed data size is necessary.
|
||||
|
||||
send(sock, data, 8, 0);
|
||||
|
||||
will emit a single CAN frame.
|
||||
|
||||
send(sock, data, 9, 0);
|
||||
|
||||
will use fragmentation, emitting 1+ CAN frames.
|
||||
|
||||
# Using J1939
|
||||
|
||||
## BSD socket implementation
|
||||
* socket
|
||||
* bind / connect
|
||||
* recvfrom / sendto
|
||||
* getsockname / getpeername
|
||||
|
||||
## Modified *struct sockaddr_can*
|
||||
|
||||
struct sockaddr_can {
|
||||
sa_family_t can_family;
|
||||
int can_ifindex;
|
||||
union {
|
||||
struct {
|
||||
__u64 name;
|
||||
__u32 pgn;
|
||||
__u8 addr;
|
||||
} j1939;
|
||||
} can_addr;
|
||||
}
|
||||
|
||||
* *can_addr.j1939.pgn* is PGN
|
||||
|
||||
* *can_addr.j1939.addr* & *can_addr.j1939.name*
|
||||
determine the ECU
|
||||
|
||||
* receiving address information,
|
||||
*addr* is always set,
|
||||
*name* is set when available.
|
||||
|
||||
* When providing address information,
|
||||
*name* != 0 indicates dynamic addressing
|
||||
59
Devices/Libraries/Systems/can-utils/can-tc-init-etf.sh
Executable file
59
Devices/Libraries/Systems/can-utils/can-tc-init-etf.sh
Executable file
@@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
# Copyright (C) 2022 Pengutronix, Marc Kleine-Budde <kernel@pengutronix.de>
|
||||
#
|
||||
# This script requires a kernel compiled with the following options:
|
||||
#
|
||||
# CONFIG_NET_SCH_PRIO
|
||||
# CONFIG_NET_SCH_ETF
|
||||
# CONFIG_NET_CLS_BASIC
|
||||
# CONFIG_NET_CLS_FW
|
||||
# CONFIG_NET_EMATCH
|
||||
# CONFIG_NET_EMATCH_CANID
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
IFACE=${1:-can0}
|
||||
MARK=${2:-1}
|
||||
|
||||
clear() {
|
||||
tc -batch - <<EOF
|
||||
|
||||
qdisc replace dev ${IFACE} root pfifo_fast
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
show() {
|
||||
tc -batch - <<EOF
|
||||
|
||||
qdisc show dev ${IFACE}
|
||||
filter show dev ${IFACE}
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
prio_etf_mark() {
|
||||
tc -batch - <<EOF
|
||||
|
||||
qdisc replace dev ${IFACE} parent root handle 100 prio \
|
||||
bands 3
|
||||
|
||||
qdisc replace dev ${IFACE} handle 1001 parent 100:1 etf clockid CLOCK_TAI \
|
||||
delta 200000
|
||||
|
||||
qdisc replace dev ${IFACE} handle 1002 parent 100:3 pfifo_fast
|
||||
|
||||
filter add dev ${IFACE} parent 100: prio 1 \
|
||||
handle ${MARK} fw flowid 100:1
|
||||
|
||||
filter add dev ${IFACE} parent 100: prio 2 \
|
||||
basic match canid (sff 0x0:0x0 eff 0x0:0x0) flowid 100:2
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
clear
|
||||
prio_etf_mark
|
||||
show
|
||||
431
Devices/Libraries/Systems/can-utils/canbusload.c
Executable file
431
Devices/Libraries/Systems/can-utils/canbusload.c
Executable file
@@ -0,0 +1,431 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
|
||||
/*
|
||||
* canbusload.c - monitor CAN bus load
|
||||
*
|
||||
* Copyright (c) 2002-2008 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* Send feedback to <linux-can@vger.kernel.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <libgen.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/raw.h>
|
||||
|
||||
#include "lib.h"
|
||||
#include "terminal.h"
|
||||
#include "canframelen.h"
|
||||
|
||||
#define MAXSOCK 16 /* max. number of CAN interfaces given on the cmdline */
|
||||
|
||||
#define PERCENTRES 5 /* resolution in percent for bargraph */
|
||||
#define NUMBAR (100 / PERCENTRES) /* number of bargraph elements */
|
||||
|
||||
extern int optind, opterr, optopt;
|
||||
|
||||
static struct {
|
||||
char devname[IFNAMSIZ + 1];
|
||||
unsigned int bitrate;
|
||||
unsigned int dbitrate;
|
||||
unsigned int recv_frames;
|
||||
unsigned int recv_bits_total;
|
||||
unsigned int recv_bits_payload;
|
||||
unsigned int recv_bits_dbitrate;
|
||||
} stat[MAXSOCK + 1];
|
||||
|
||||
static volatile int running = 1;
|
||||
static volatile sig_atomic_t signal_num;
|
||||
static int max_devname_len; /* to prevent frazzled device name output */
|
||||
static int max_bitrate_len;
|
||||
static int currmax;
|
||||
static unsigned char redraw;
|
||||
static unsigned char timestamp;
|
||||
static unsigned char color;
|
||||
static unsigned char bargraph;
|
||||
static enum cfl_mode mode = CFL_WORSTCASE;
|
||||
static char *prg;
|
||||
|
||||
static void print_usage(char *prg)
|
||||
{
|
||||
fprintf(stderr, "%s - monitor CAN bus load.\n", prg);
|
||||
fprintf(stderr, "\nUsage: %s [options] <CAN interface>+\n", prg);
|
||||
fprintf(stderr, " (use CTRL-C to terminate %s)\n\n", prg);
|
||||
fprintf(stderr, "Options:\n");
|
||||
fprintf(stderr, " -t (show current time on the first line)\n");
|
||||
fprintf(stderr, " -c (colorize lines)\n");
|
||||
fprintf(stderr, " -b (show bargraph in %d%% resolution)\n", PERCENTRES);
|
||||
fprintf(stderr, " -r (redraw the terminal - similar to top)\n");
|
||||
fprintf(stderr, " -i (ignore bitstuffing in bandwidth calculation)\n");
|
||||
fprintf(stderr, " -e (exact calculation of stuffed bits)\n");
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "Up to %d CAN interfaces with mandatory bitrate can be specified on the \n", MAXSOCK);
|
||||
fprintf(stderr, "commandline in the form: <ifname>@<bitrate>[,<dbitrate>]\n\n");
|
||||
fprintf(stderr, "The bitrate is mandatory as it is needed to know the CAN bus bitrate to\n");
|
||||
fprintf(stderr, "calculate the bus load percentage based on the received CAN frames.\n");
|
||||
fprintf(stderr, "Due to the bitstuffing estimation the calculated busload may exceed 100%%.\n");
|
||||
fprintf(stderr, "For each given interface the data is presented in one line which contains:\n\n");
|
||||
fprintf(stderr, "(interface) (received CAN frames) (used bits total) (used bits for payload)\n");
|
||||
fprintf(stderr, "\nExamples:\n");
|
||||
fprintf(stderr, "\nuser$> canbusload can0@100000 can1@500000 can2@500000 can3@500000 -r -t -b -c\n\n");
|
||||
fprintf(stderr, "%s 2014-02-01 21:13:16 (worst case bitstuffing)\n", prg);
|
||||
fprintf(stderr, " can0@100000 805 74491 36656 74%% |XXXXXXXXXXXXXX......|\n");
|
||||
fprintf(stderr, " can1@500000 796 75140 37728 15%% |XXX.................|\n");
|
||||
fprintf(stderr, " can2@500000 0 0 0 0%% |....................|\n");
|
||||
fprintf(stderr, " can3@500000 47 4633 2424 0%% |....................|\n");
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
static void sigterm(int signo)
|
||||
{
|
||||
running = 0;
|
||||
signal_num = signo;
|
||||
}
|
||||
|
||||
static void printstats(int signo)
|
||||
{
|
||||
int i, j, percent;
|
||||
|
||||
if (redraw)
|
||||
printf("%s", CSR_HOME);
|
||||
|
||||
if (timestamp) {
|
||||
time_t currtime;
|
||||
struct tm now;
|
||||
|
||||
if (time(&currtime) == (time_t)-1) {
|
||||
perror("time");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
localtime_r(&currtime, &now);
|
||||
|
||||
printf("%s %04d-%02d-%02d %02d:%02d:%02d ",
|
||||
prg,
|
||||
now.tm_year + 1900,
|
||||
now.tm_mon + 1,
|
||||
now.tm_mday,
|
||||
now.tm_hour,
|
||||
now.tm_min,
|
||||
now.tm_sec);
|
||||
|
||||
switch (mode) {
|
||||
|
||||
case CFL_NO_BITSTUFFING:
|
||||
/* plain bit calculation without bitstuffing */
|
||||
printf("(ignore bitstuffing)\n");
|
||||
break;
|
||||
|
||||
case CFL_WORSTCASE:
|
||||
/* worst case estimation - see above */
|
||||
printf("(worst case bitstuffing)\n");
|
||||
break;
|
||||
|
||||
case CFL_EXACT:
|
||||
/* exact calculation of stuffed bits based on frame content and CRC */
|
||||
printf("(exact bitstuffing)\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("(unknown bitstuffing)\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < currmax; i++) {
|
||||
if (color) {
|
||||
if (i % 2)
|
||||
printf("%s", FGRED);
|
||||
else
|
||||
printf("%s", FGBLUE);
|
||||
}
|
||||
|
||||
if (stat[i].bitrate)
|
||||
percent = ((stat[i].recv_bits_total - stat[i].recv_bits_dbitrate) * 100) / stat[i].bitrate +
|
||||
(stat[i].recv_bits_dbitrate * 100) / stat[i].dbitrate;
|
||||
else
|
||||
percent = 0;
|
||||
|
||||
printf(" %*s@%-*d %5d %7d %6d %6d %3d%%",
|
||||
max_devname_len, stat[i].devname,
|
||||
max_bitrate_len, stat[i].bitrate,
|
||||
stat[i].recv_frames,
|
||||
stat[i].recv_bits_total,
|
||||
stat[i].recv_bits_payload,
|
||||
stat[i].recv_bits_dbitrate,
|
||||
percent);
|
||||
|
||||
if (bargraph) {
|
||||
|
||||
printf(" |");
|
||||
|
||||
if (percent > 100)
|
||||
percent = 100;
|
||||
|
||||
for (j = 0; j < NUMBAR; j++) {
|
||||
if (j < percent / PERCENTRES)
|
||||
printf("X");
|
||||
else
|
||||
printf(".");
|
||||
}
|
||||
|
||||
printf("|");
|
||||
}
|
||||
|
||||
if (color)
|
||||
printf("%s", ATTRESET);
|
||||
|
||||
if (!redraw || (i < currmax - 1))
|
||||
printf("\n");
|
||||
|
||||
stat[i].recv_frames = 0;
|
||||
stat[i].recv_bits_total = 0;
|
||||
stat[i].recv_bits_dbitrate = 0;
|
||||
stat[i].recv_bits_payload = 0;
|
||||
}
|
||||
|
||||
if (!redraw)
|
||||
printf("\n");
|
||||
|
||||
fflush(stdout);
|
||||
|
||||
alarm(1);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
fd_set rdfs;
|
||||
int s[MAXSOCK];
|
||||
|
||||
int opt;
|
||||
char *ptr, *nptr;
|
||||
struct sockaddr_can addr;
|
||||
struct canfd_frame frame;
|
||||
int nbytes, i;
|
||||
struct ifreq ifr;
|
||||
|
||||
signal(SIGTERM, sigterm);
|
||||
signal(SIGHUP, sigterm);
|
||||
signal(SIGINT, sigterm);
|
||||
|
||||
signal(SIGALRM, printstats);
|
||||
|
||||
prg = basename(argv[0]);
|
||||
|
||||
while ((opt = getopt(argc, argv, "rtbcieh?")) != -1) {
|
||||
switch (opt) {
|
||||
case 'r':
|
||||
redraw = 1;
|
||||
break;
|
||||
|
||||
case 't':
|
||||
timestamp = 1;
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
bargraph = 1;
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
color = 1;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
mode = CFL_NO_BITSTUFFING;
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
mode = CFL_EXACT;
|
||||
break;
|
||||
|
||||
default:
|
||||
print_usage(prg);
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (optind == argc) {
|
||||
print_usage(prg);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
currmax = argc - optind; /* find real number of CAN devices */
|
||||
|
||||
if (currmax > MAXSOCK) {
|
||||
printf("More than %d CAN devices given on commandline!\n", MAXSOCK);
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (i = 0; i < currmax; i++) {
|
||||
ptr = argv[optind + i];
|
||||
|
||||
nbytes = strlen(ptr);
|
||||
if (nbytes >= (int)(IFNAMSIZ + sizeof("@1000000") + 1)) {
|
||||
printf("name of CAN device '%s' is too long!\n", ptr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
pr_debug("open %d '%s'.\n", i, ptr);
|
||||
|
||||
s[i] = socket(PF_CAN, SOCK_RAW, CAN_RAW);
|
||||
if (s[i] < 0) {
|
||||
perror("socket");
|
||||
return 1;
|
||||
}
|
||||
|
||||
nptr = strchr(ptr, '@');
|
||||
|
||||
if (!nptr) {
|
||||
fprintf(stderr, "Specify CAN interfaces in the form <CAN interface>@<bitrate>, e.g. can0@500000\n");
|
||||
print_usage(prg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
nbytes = nptr - ptr; /* interface name is up the first '@' */
|
||||
|
||||
if (nbytes >= (int)IFNAMSIZ) {
|
||||
printf("name of CAN device '%s' is too long!\n", ptr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
strncpy(stat[i].devname, ptr, nbytes);
|
||||
memset(&ifr.ifr_name, 0, sizeof(ifr.ifr_name));
|
||||
strncpy(ifr.ifr_name, ptr, nbytes);
|
||||
|
||||
if (nbytes > max_devname_len)
|
||||
max_devname_len = nbytes; /* for nice printing */
|
||||
|
||||
char *endp;
|
||||
stat[i].bitrate = strtol(nptr + 1, &endp, 0); /* bitrate is placed behind the '@' */
|
||||
if (*endp == ',')
|
||||
/* data bitrate is placed behind the ',' */
|
||||
stat[i].dbitrate = strtol(endp + 1, &endp, 0);
|
||||
else
|
||||
stat[i].dbitrate = stat[i].bitrate;
|
||||
|
||||
if (!stat[i].bitrate || stat[i].bitrate > 1000000) {
|
||||
printf("invalid bitrate for CAN device '%s'!\n", ptr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
nbytes = strlen(nptr + 1);
|
||||
if (nbytes > max_bitrate_len)
|
||||
max_bitrate_len = nbytes; /* for nice printing */
|
||||
|
||||
pr_debug("using interface name '%s'.\n", ifr.ifr_name);
|
||||
|
||||
/* try to switch the socket into CAN FD mode */
|
||||
const int canfd_on = 1;
|
||||
setsockopt(s[i], SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &canfd_on, sizeof(canfd_on));
|
||||
|
||||
if (ioctl(s[i], SIOCGIFINDEX, &ifr) < 0) {
|
||||
perror("SIOCGIFINDEX");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
addr.can_family = AF_CAN;
|
||||
addr.can_ifindex = ifr.ifr_ifindex;
|
||||
|
||||
if (bind(s[i], (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror("bind");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
alarm(1);
|
||||
|
||||
if (redraw)
|
||||
printf("%s", CLR_SCREEN);
|
||||
|
||||
while (running) {
|
||||
FD_ZERO(&rdfs);
|
||||
for (i = 0; i < currmax; i++)
|
||||
FD_SET(s[i], &rdfs);
|
||||
|
||||
if (select(s[currmax - 1] + 1, &rdfs, NULL, NULL, NULL) < 0) {
|
||||
//perror("pselect");
|
||||
continue;
|
||||
}
|
||||
|
||||
for (i = 0; i < currmax; i++) { /* check all CAN RAW sockets */
|
||||
|
||||
if (FD_ISSET(s[i], &rdfs)) {
|
||||
nbytes = read(s[i], &frame, sizeof(frame));
|
||||
|
||||
if (nbytes < 0) {
|
||||
perror("read");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (nbytes < (int)sizeof(struct can_frame)) {
|
||||
fprintf(stderr, "read: incomplete CAN frame\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
stat[i].recv_frames++;
|
||||
stat[i].recv_bits_payload += frame.len * 8;
|
||||
stat[i].recv_bits_dbitrate += can_frame_dbitrate_length(
|
||||
&frame, mode, sizeof(frame));
|
||||
stat[i].recv_bits_total += can_frame_length(&frame,
|
||||
mode, nbytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < currmax; i++)
|
||||
close(s[i]);
|
||||
|
||||
if (signal_num)
|
||||
return 128 + signal_num;
|
||||
|
||||
return 0;
|
||||
}
|
||||
906
Devices/Libraries/Systems/can-utils/candump.c
Executable file
906
Devices/Libraries/Systems/can-utils/candump.c
Executable file
@@ -0,0 +1,906 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
|
||||
/*
|
||||
* candump.c
|
||||
*
|
||||
* Copyright (c) 2002-2009 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* Send feedback to <linux-can@vger.kernel.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <libgen.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <net/if.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/raw.h>
|
||||
#include <linux/net_tstamp.h>
|
||||
|
||||
#include "terminal.h"
|
||||
#include "lib.h"
|
||||
|
||||
/* for hardware timestamps - since Linux 2.6.30 */
|
||||
#ifndef SO_TIMESTAMPING
|
||||
#define SO_TIMESTAMPING 37
|
||||
#endif
|
||||
|
||||
#define TIMESTAMPSZ 50 /* string 'absolute with date' requires max 49 bytes */
|
||||
|
||||
#define MAXSOCK 16 /* max. number of CAN interfaces given on the cmdline */
|
||||
#define MAXIFNAMES 30 /* size of receive name index to omit ioctls */
|
||||
#define MAXCOL 6 /* number of different colors for colorized output */
|
||||
#define ANYDEV "any" /* name of interface to receive from any CAN interface */
|
||||
#define ANL "\r\n" /* newline in ASC mode */
|
||||
|
||||
#define SILENT_INI 42 /* detect user setting on commandline */
|
||||
#define SILENT_OFF 0 /* no silent mode */
|
||||
#define SILENT_ANI 1 /* silent mode with animation */
|
||||
#define SILENT_ON 2 /* silent mode (completely silent) */
|
||||
|
||||
#define BOLD ATTBOLD
|
||||
#define RED (ATTBOLD FGRED)
|
||||
#define GREEN (ATTBOLD FGGREEN)
|
||||
#define YELLOW (ATTBOLD FGYELLOW)
|
||||
#define BLUE (ATTBOLD FGBLUE)
|
||||
#define MAGENTA (ATTBOLD FGMAGENTA)
|
||||
#define CYAN (ATTBOLD FGCYAN)
|
||||
|
||||
static const char col_on[MAXCOL][19] = { BLUE, RED, GREEN, BOLD, MAGENTA, CYAN };
|
||||
static const char col_off[] = ATTRESET;
|
||||
|
||||
struct if_info { /* bundled information per open socket */
|
||||
int s; /* socket */
|
||||
char *cmdlinename;
|
||||
__u32 dropcnt;
|
||||
__u32 last_dropcnt;
|
||||
};
|
||||
static struct if_info sock_info[MAXSOCK];
|
||||
|
||||
static char *progname;
|
||||
static char devname[MAXIFNAMES][IFNAMSIZ + 1];
|
||||
static int dindex[MAXIFNAMES];
|
||||
static int max_devname_len; /* to prevent frazzled device name output */
|
||||
static const int canfx_on = 1;
|
||||
|
||||
#define MAXANI 4
|
||||
static const char anichar[MAXANI] = { '|', '/', '-', '\\' };
|
||||
static const char extra_m_info[4][4] = { "- -", "B -", "- E", "B E" };
|
||||
|
||||
extern int optind, opterr, optopt;
|
||||
|
||||
static volatile int running = 1;
|
||||
static volatile sig_atomic_t signal_num;
|
||||
|
||||
static void print_usage(void)
|
||||
{
|
||||
fprintf(stderr, "%s - dump CAN bus traffic.\n", progname);
|
||||
fprintf(stderr, "\nUsage: %s [options] <CAN interface>+\n", progname);
|
||||
fprintf(stderr, " (use CTRL-C to terminate %s)\n\n", progname);
|
||||
fprintf(stderr, "Options:\n");
|
||||
fprintf(stderr, " -t <type> (timestamp: (a)bsolute/(d)elta/(z)ero/(A)bsolute w date)\n");
|
||||
fprintf(stderr, " -H (read hardware timestamps instead of system timestamps)\n");
|
||||
fprintf(stderr, " -c (increment color mode level)\n");
|
||||
fprintf(stderr, " -i (binary output - may exceed 80 chars/line)\n");
|
||||
fprintf(stderr, " -a (enable additional ASCII output)\n");
|
||||
fprintf(stderr, " -S (swap byte order in printed CAN data[] - marked with '%c' )\n", SWAP_DELIMITER);
|
||||
fprintf(stderr, " -s <level> (silent mode - %d: off (default) %d: animation %d: silent)\n", SILENT_OFF, SILENT_ANI, SILENT_ON);
|
||||
fprintf(stderr, " -l (log CAN-frames into file. Sets '-s %d' by default)\n", SILENT_ON);
|
||||
fprintf(stderr, " -f <fname> (log CAN-frames into file <fname>. Sets '-s %d' by default)\n", SILENT_ON);
|
||||
fprintf(stderr, " -L (use log file format on stdout)\n");
|
||||
fprintf(stderr, " -n <count> (terminate after reception of <count> CAN frames)\n");
|
||||
fprintf(stderr, " -r <size> (set socket receive buffer to <size>)\n");
|
||||
fprintf(stderr, " -D (Don't exit if a \"detected\" can device goes down)\n");
|
||||
fprintf(stderr, " -d (monitor dropped CAN frames)\n");
|
||||
fprintf(stderr, " -e (dump CAN error frames in human-readable format)\n");
|
||||
fprintf(stderr, " -8 (display raw DLC values in {} for Classical CAN)\n");
|
||||
fprintf(stderr, " -x (print extra message infos, rx/tx brs esi)\n");
|
||||
fprintf(stderr, " -T <msecs> (terminate after <msecs> if no frames were received)\n");
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "Up to %d CAN interfaces with optional filter sets can be specified\n", MAXSOCK);
|
||||
fprintf(stderr, "on the commandline in the form: <ifname>[,filter]*\n");
|
||||
fprintf(stderr, "\nFilters:\n");
|
||||
fprintf(stderr, " Comma separated filters can be specified for each given CAN interface:\n");
|
||||
fprintf(stderr, " <can_id>:<can_mask>\n (matches when <received_can_id> & mask == can_id & mask)\n");
|
||||
fprintf(stderr, " <can_id>~<can_mask>\n (matches when <received_can_id> & mask != can_id & mask)\n");
|
||||
fprintf(stderr, " #<error_mask>\n (set error frame filter, see include/linux/can/error.h)\n");
|
||||
fprintf(stderr, " [j|J]\n (join the given CAN filters - logical AND semantic)\n");
|
||||
fprintf(stderr, "\nCAN IDs, masks and data content are given and expected in hexadecimal values.\n");
|
||||
fprintf(stderr, "When the can_id is 8 digits long the CAN_EFF_FLAG is set for 29 bit EFF format.\n");
|
||||
fprintf(stderr, "Without any given filter all data frames are received ('0:0' default filter).\n");
|
||||
fprintf(stderr, "\nUse interface name '%s' to receive from all CAN interfaces.\n", ANYDEV);
|
||||
fprintf(stderr, "\nExamples:\n");
|
||||
fprintf(stderr, "%s -c -c -ta can0,123:7FF,400:700,#000000FF can2,400~7F0 can3 can8\n\n", progname);
|
||||
fprintf(stderr, "%s -l any,0~0,#FFFFFFFF\n (log only error frames but no(!) data frames)\n", progname);
|
||||
fprintf(stderr, "%s -l any,0:0,#FFFFFFFF\n (log error frames and also all data frames)\n", progname);
|
||||
fprintf(stderr, "%s vcan2,12345678:DFFFFFFF\n (match only for extended CAN ID 12345678)\n", progname);
|
||||
fprintf(stderr, "%s vcan2,123:7FF\n (matches CAN ID 123 - including EFF and RTR frames)\n", progname);
|
||||
fprintf(stderr, "%s vcan2,123:C00007FF\n (matches CAN ID 123 - only SFF and non-RTR frames)\n", progname);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
static void sigterm(int signo)
|
||||
{
|
||||
running = 0;
|
||||
signal_num = signo;
|
||||
}
|
||||
|
||||
static int idx2dindex(int ifidx, int socket)
|
||||
{
|
||||
int i;
|
||||
struct ifreq ifr;
|
||||
|
||||
for (i = 0; i < MAXIFNAMES; i++) {
|
||||
if (dindex[i] == ifidx)
|
||||
return i;
|
||||
}
|
||||
|
||||
/* create new interface index cache entry */
|
||||
|
||||
/* remove index cache zombies first */
|
||||
for (i = 0; i < MAXIFNAMES; i++) {
|
||||
if (dindex[i]) {
|
||||
ifr.ifr_ifindex = dindex[i];
|
||||
if (ioctl(socket, SIOCGIFNAME, &ifr) < 0)
|
||||
dindex[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < MAXIFNAMES; i++)
|
||||
if (!dindex[i]) /* free entry */
|
||||
break;
|
||||
|
||||
if (i == MAXIFNAMES) {
|
||||
fprintf(stderr, "Interface index cache only supports %d interfaces.\n",
|
||||
MAXIFNAMES);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
dindex[i] = ifidx;
|
||||
|
||||
ifr.ifr_ifindex = ifidx;
|
||||
if (ioctl(socket, SIOCGIFNAME, &ifr) < 0)
|
||||
perror("SIOCGIFNAME");
|
||||
|
||||
if (max_devname_len < (int)strlen(ifr.ifr_name))
|
||||
max_devname_len = strlen(ifr.ifr_name);
|
||||
|
||||
strcpy(devname[i], ifr.ifr_name);
|
||||
|
||||
pr_debug("new index %d (%s)\n", i, devname[i]);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static int sprint_timestamp(char *ts_buffer, const char timestamp,
|
||||
const struct timeval *tv, struct timeval *const last_tv)
|
||||
{
|
||||
int numchars = 0;
|
||||
|
||||
switch (timestamp) {
|
||||
case 'a': /* absolute with timestamp */
|
||||
numchars = sprintf(ts_buffer, "(%010llu.%06llu) ",
|
||||
(unsigned long long)tv->tv_sec,
|
||||
(unsigned long long)tv->tv_usec);
|
||||
break;
|
||||
|
||||
case 'A': /* absolute with date */
|
||||
{
|
||||
struct tm tm;
|
||||
char timestring[25];
|
||||
|
||||
tm = *localtime(&tv->tv_sec);
|
||||
strftime(timestring, 24, "%Y-%m-%d %H:%M:%S", &tm);
|
||||
numchars = sprintf(ts_buffer, "(%s.%06llu) ", timestring,
|
||||
(unsigned long long)tv->tv_usec);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'd': /* delta */
|
||||
case 'z': /* starting with zero */
|
||||
{
|
||||
struct timeval diff;
|
||||
|
||||
if (last_tv->tv_sec == 0) /* first init */
|
||||
*last_tv = *tv;
|
||||
diff.tv_sec = tv->tv_sec - last_tv->tv_sec;
|
||||
diff.tv_usec = tv->tv_usec - last_tv->tv_usec;
|
||||
if (diff.tv_usec < 0)
|
||||
diff.tv_sec--, diff.tv_usec += 1000000;
|
||||
if (diff.tv_sec < 0)
|
||||
diff.tv_sec = diff.tv_usec = 0;
|
||||
numchars = sprintf(ts_buffer, "(%03llu.%06llu) ",
|
||||
(unsigned long long)diff.tv_sec,
|
||||
(unsigned long long)diff.tv_usec);
|
||||
|
||||
if (timestamp == 'd')
|
||||
*last_tv = *tv; /* update for delta calculation */
|
||||
}
|
||||
break;
|
||||
|
||||
default: /* no timestamp output */
|
||||
break;
|
||||
}
|
||||
|
||||
if (numchars <= 0) {
|
||||
ts_buffer[0] = 0; /* empty terminated string */
|
||||
numchars = 0;
|
||||
}
|
||||
|
||||
return numchars;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int fd_epoll;
|
||||
struct epoll_event events_pending[MAXSOCK];
|
||||
struct epoll_event event_setup = {
|
||||
.events = EPOLLIN, /* prepare the common part */
|
||||
};
|
||||
unsigned char timestamp = 0;
|
||||
unsigned char logtimestamp = 'a';
|
||||
unsigned char hwtimestamp = 0;
|
||||
unsigned char down_causes_exit = 1;
|
||||
unsigned char dropmonitor = 0;
|
||||
unsigned char extra_msg_info = 0;
|
||||
unsigned char silent = SILENT_INI;
|
||||
unsigned char silentani = 0;
|
||||
unsigned char color = 0;
|
||||
unsigned char view = 0;
|
||||
unsigned char log = 0;
|
||||
unsigned char logfrmt = 0;
|
||||
int count = 0;
|
||||
int rcvbuf_size = 0;
|
||||
int opt, num_events;
|
||||
int currmax, numfilter;
|
||||
int join_filter;
|
||||
char *ptr, *nptr;
|
||||
struct sockaddr_can addr = {
|
||||
.can_family = AF_CAN,
|
||||
};
|
||||
struct can_raw_vcid_options vcid_opts = {
|
||||
.flags = CAN_RAW_XL_VCID_RX_FILTER,
|
||||
.rx_vcid = 0,
|
||||
.rx_vcid_mask = 0,
|
||||
};
|
||||
char ctrlmsg[CMSG_SPACE(sizeof(struct timeval)) +
|
||||
CMSG_SPACE(3 * sizeof(struct timespec)) +
|
||||
CMSG_SPACE(sizeof(__u32))];
|
||||
struct iovec iov;
|
||||
struct msghdr msg;
|
||||
struct cmsghdr *cmsg;
|
||||
struct can_filter *rfilter;
|
||||
can_err_mask_t err_mask;
|
||||
static cu_t cu; /* union for CAN CC/FD/XL frames */
|
||||
int nbytes, i;
|
||||
struct ifreq ifr;
|
||||
struct timeval tv, last_tv;
|
||||
int timeout_ms = -1; /* default to no timeout */
|
||||
FILE *logfile = NULL;
|
||||
char fname[83]; /* suggested by -Wformat-overflow= */
|
||||
const char *logname = NULL;
|
||||
static char afrbuf[AFRSZ]; /* ASCII CAN frame buffer size */
|
||||
static int alen;
|
||||
|
||||
signal(SIGTERM, sigterm);
|
||||
signal(SIGHUP, sigterm);
|
||||
signal(SIGINT, sigterm);
|
||||
|
||||
last_tv.tv_sec = 0;
|
||||
last_tv.tv_usec = 0;
|
||||
|
||||
progname = basename(argv[0]);
|
||||
|
||||
while ((opt = getopt(argc, argv, "t:HciaSs:lf:Ln:r:Dde8xT:h?")) != -1) {
|
||||
switch (opt) {
|
||||
case 't':
|
||||
timestamp = optarg[0];
|
||||
logtimestamp = optarg[0];
|
||||
if ((timestamp != 'a') && (timestamp != 'A') &&
|
||||
(timestamp != 'd') && (timestamp != 'z')) {
|
||||
fprintf(stderr, "%s: unknown timestamp mode '%c' - ignored\n",
|
||||
progname, optarg[0]);
|
||||
timestamp = 0;
|
||||
}
|
||||
if ((logtimestamp != 'a') && (logtimestamp != 'z')) {
|
||||
logtimestamp = 'a';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'H':
|
||||
hwtimestamp = 1;
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
color++;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
view |= CANLIB_VIEW_BINARY;
|
||||
break;
|
||||
|
||||
case 'a':
|
||||
view |= CANLIB_VIEW_ASCII;
|
||||
break;
|
||||
|
||||
case 'S':
|
||||
view |= CANLIB_VIEW_SWAP;
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
view |= CANLIB_VIEW_ERROR;
|
||||
break;
|
||||
|
||||
case '8':
|
||||
view |= CANLIB_VIEW_LEN8_DLC;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
silent = atoi(optarg);
|
||||
if (silent > SILENT_ON) {
|
||||
print_usage();
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
log = 1;
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
down_causes_exit = 0;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
dropmonitor = 1;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
extra_msg_info = 1;
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
logfrmt = 1;
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
logname = optarg;
|
||||
log = 1;
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
count = atoi(optarg);
|
||||
if (count < 1) {
|
||||
print_usage();
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
rcvbuf_size = atoi(optarg);
|
||||
if (rcvbuf_size < 1) {
|
||||
print_usage();
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'T':
|
||||
errno = 0;
|
||||
timeout_ms = strtol(optarg, NULL, 0);
|
||||
if (errno != 0) {
|
||||
print_usage();
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
print_usage();
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (optind == argc) {
|
||||
print_usage();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (logfrmt && view) {
|
||||
fprintf(stderr, "Log file format selected: Please disable ASCII/BINARY/SWAP/RAWDLC options!\n");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/* "-f -" is equal to "-L" (print logfile format on stdout) */
|
||||
if (log && logname && strcmp("-", logname) == 0) {
|
||||
log = 0; /* no logging into a file */
|
||||
logfrmt = 1; /* print logformat output to stdout */
|
||||
}
|
||||
|
||||
if (silent == SILENT_INI) {
|
||||
if (log) {
|
||||
fprintf(stderr, "Disabled standard output while logging.\n");
|
||||
silent = SILENT_ON; /* disable output on stdout */
|
||||
} else
|
||||
silent = SILENT_OFF; /* default output */
|
||||
}
|
||||
|
||||
currmax = argc - optind; /* find real number of CAN devices */
|
||||
|
||||
if (currmax > MAXSOCK) {
|
||||
fprintf(stderr, "More than %d CAN devices given on commandline!\n", MAXSOCK);
|
||||
return 1;
|
||||
}
|
||||
|
||||
fd_epoll = epoll_create(1);
|
||||
if (fd_epoll < 0) {
|
||||
perror("epoll_create");
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (i = 0; i < currmax; i++) {
|
||||
struct if_info *obj = &sock_info[i];
|
||||
ptr = argv[optind + i];
|
||||
nptr = strchr(ptr, ',');
|
||||
|
||||
pr_debug("open %d '%s'.\n", i, ptr);
|
||||
|
||||
obj->s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
|
||||
if (obj->s < 0) {
|
||||
perror("socket");
|
||||
return 1;
|
||||
}
|
||||
|
||||
event_setup.data.ptr = obj; /* remember the instance as private data */
|
||||
if (epoll_ctl(fd_epoll, EPOLL_CTL_ADD, obj->s, &event_setup)) {
|
||||
perror("failed to add socket to epoll");
|
||||
return 1;
|
||||
}
|
||||
|
||||
obj->cmdlinename = ptr; /* save pointer to cmdline name of this socket */
|
||||
|
||||
if (nptr)
|
||||
nbytes = nptr - ptr; /* interface name is up the first ',' */
|
||||
else
|
||||
nbytes = strlen(ptr); /* no ',' found => no filter definitions */
|
||||
|
||||
if (nbytes >= IFNAMSIZ) {
|
||||
fprintf(stderr, "name of CAN device '%s' is too long!\n", ptr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (nbytes > max_devname_len)
|
||||
max_devname_len = nbytes; /* for nice printing */
|
||||
|
||||
memset(&ifr.ifr_name, 0, sizeof(ifr.ifr_name));
|
||||
strncpy(ifr.ifr_name, ptr, nbytes);
|
||||
|
||||
pr_debug("using interface name '%s'.\n", ifr.ifr_name);
|
||||
|
||||
if (strcmp(ANYDEV, ifr.ifr_name) != 0) {
|
||||
if (ioctl(obj->s, SIOCGIFINDEX, &ifr) < 0) {
|
||||
perror("SIOCGIFINDEX");
|
||||
exit(1);
|
||||
}
|
||||
addr.can_ifindex = ifr.ifr_ifindex;
|
||||
} else
|
||||
addr.can_ifindex = 0; /* any can interface */
|
||||
|
||||
if (nptr) {
|
||||
/* found a ',' after the interface name => check for filters */
|
||||
|
||||
/* determine number of filters to alloc the filter space */
|
||||
numfilter = 0;
|
||||
ptr = nptr;
|
||||
while (ptr) {
|
||||
numfilter++;
|
||||
ptr++; /* hop behind the ',' */
|
||||
ptr = strchr(ptr, ','); /* exit condition */
|
||||
}
|
||||
|
||||
rfilter = malloc(sizeof(struct can_filter) * numfilter);
|
||||
if (!rfilter) {
|
||||
fprintf(stderr, "Failed to create filter space!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
numfilter = 0;
|
||||
err_mask = 0;
|
||||
join_filter = 0;
|
||||
|
||||
while (nptr) {
|
||||
|
||||
ptr = nptr + 1; /* hop behind the ',' */
|
||||
nptr = strchr(ptr, ','); /* update exit condition */
|
||||
|
||||
if (sscanf(ptr, "%x:%x",
|
||||
&rfilter[numfilter].can_id,
|
||||
&rfilter[numfilter].can_mask) == 2) {
|
||||
rfilter[numfilter].can_mask &= ~CAN_ERR_FLAG;
|
||||
if (*(ptr + 8) == ':')
|
||||
rfilter[numfilter].can_id |= CAN_EFF_FLAG;
|
||||
numfilter++;
|
||||
} else if (sscanf(ptr, "%x~%x",
|
||||
&rfilter[numfilter].can_id,
|
||||
&rfilter[numfilter].can_mask) == 2) {
|
||||
rfilter[numfilter].can_id |= CAN_INV_FILTER;
|
||||
rfilter[numfilter].can_mask &= ~CAN_ERR_FLAG;
|
||||
if (*(ptr + 8) == '~')
|
||||
rfilter[numfilter].can_id |= CAN_EFF_FLAG;
|
||||
numfilter++;
|
||||
} else if (*ptr == 'j' || *ptr == 'J') {
|
||||
join_filter = 1;
|
||||
} else if (sscanf(ptr, "#%x", &err_mask) != 1) {
|
||||
fprintf(stderr, "Error in filter option parsing: '%s'\n", ptr);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (err_mask)
|
||||
setsockopt(obj->s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER,
|
||||
&err_mask, sizeof(err_mask));
|
||||
|
||||
if (join_filter && setsockopt(obj->s, SOL_CAN_RAW, CAN_RAW_JOIN_FILTERS,
|
||||
&join_filter, sizeof(join_filter)) < 0) {
|
||||
perror("setsockopt CAN_RAW_JOIN_FILTERS not supported by your Linux Kernel");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (numfilter)
|
||||
setsockopt(obj->s, SOL_CAN_RAW, CAN_RAW_FILTER,
|
||||
rfilter, numfilter * sizeof(struct can_filter));
|
||||
|
||||
free(rfilter);
|
||||
|
||||
} /* if (nptr) */
|
||||
|
||||
/* try to switch the socket into CAN FD mode */
|
||||
setsockopt(obj->s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &canfx_on, sizeof(canfx_on));
|
||||
|
||||
/* try to switch the socket into CAN XL mode */
|
||||
setsockopt(obj->s, SOL_CAN_RAW, CAN_RAW_XL_FRAMES, &canfx_on, sizeof(canfx_on));
|
||||
|
||||
/* try to enable the CAN XL VCID pass through mode */
|
||||
setsockopt(obj->s, SOL_CAN_RAW, CAN_RAW_XL_VCID_OPTS, &vcid_opts, sizeof(vcid_opts));
|
||||
|
||||
if (rcvbuf_size) {
|
||||
int curr_rcvbuf_size;
|
||||
socklen_t curr_rcvbuf_size_len = sizeof(curr_rcvbuf_size);
|
||||
|
||||
/* try SO_RCVBUFFORCE first, if we run with CAP_NET_ADMIN */
|
||||
if (setsockopt(obj->s, SOL_SOCKET, SO_RCVBUFFORCE,
|
||||
&rcvbuf_size, sizeof(rcvbuf_size)) < 0) {
|
||||
pr_debug("SO_RCVBUFFORCE failed so try SO_RCVBUF ...\n");
|
||||
if (setsockopt(obj->s, SOL_SOCKET, SO_RCVBUF,
|
||||
&rcvbuf_size, sizeof(rcvbuf_size)) < 0) {
|
||||
perror("setsockopt SO_RCVBUF");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (getsockopt(obj->s, SOL_SOCKET, SO_RCVBUF,
|
||||
&curr_rcvbuf_size, &curr_rcvbuf_size_len) < 0) {
|
||||
perror("getsockopt SO_RCVBUF");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Only print a warning the first time we detect the adjustment */
|
||||
/* n.b.: The wanted size is doubled in Linux in net/sore/sock.c */
|
||||
if (!i && curr_rcvbuf_size < rcvbuf_size * 2)
|
||||
fprintf(stderr, "The socket receive buffer size was "
|
||||
"adjusted due to /proc/sys/net/core/rmem_max.\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (timestamp || log || logfrmt) {
|
||||
if (hwtimestamp) {
|
||||
const int timestamping_flags = (SOF_TIMESTAMPING_SOFTWARE |
|
||||
SOF_TIMESTAMPING_RX_SOFTWARE |
|
||||
SOF_TIMESTAMPING_RAW_HARDWARE);
|
||||
|
||||
if (setsockopt(obj->s, SOL_SOCKET, SO_TIMESTAMPING,
|
||||
×tamping_flags, sizeof(timestamping_flags)) < 0) {
|
||||
perror("setsockopt SO_TIMESTAMPING is not supported by your Linux kernel");
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
const int timestamp_on = 1;
|
||||
|
||||
if (setsockopt(obj->s, SOL_SOCKET, SO_TIMESTAMP,
|
||||
×tamp_on, sizeof(timestamp_on)) < 0) {
|
||||
perror("setsockopt SO_TIMESTAMP");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dropmonitor) {
|
||||
const int dropmonitor_on = 1;
|
||||
|
||||
if (setsockopt(obj->s, SOL_SOCKET, SO_RXQ_OVFL,
|
||||
&dropmonitor_on, sizeof(dropmonitor_on)) < 0) {
|
||||
perror("setsockopt SO_RXQ_OVFL not supported by your Linux Kernel");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (bind(obj->s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror("bind");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (log) {
|
||||
if (!logname) {
|
||||
time_t currtime;
|
||||
struct tm now;
|
||||
|
||||
if (time(&currtime) == (time_t)-1) {
|
||||
perror("time");
|
||||
return 1;
|
||||
}
|
||||
|
||||
localtime_r(&currtime, &now);
|
||||
|
||||
snprintf(fname, sizeof(fname), "candump-%04d-%02d-%02d_%02d%02d%02d.log",
|
||||
now.tm_year + 1900,
|
||||
now.tm_mon + 1,
|
||||
now.tm_mday,
|
||||
now.tm_hour,
|
||||
now.tm_min,
|
||||
now.tm_sec);
|
||||
|
||||
logname = fname;
|
||||
}
|
||||
|
||||
if (silent != SILENT_ON)
|
||||
fprintf(stderr, "Warning: Console output active while logging!\n");
|
||||
|
||||
fprintf(stderr, "Enabling Logfile '%s'\n", logname);
|
||||
|
||||
logfile = fopen(logname, "w");
|
||||
if (!logfile) {
|
||||
perror("logfile");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* these settings are static and can be held out of the hot path */
|
||||
iov.iov_base = &cu;
|
||||
msg.msg_name = &addr;
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_control = &ctrlmsg;
|
||||
|
||||
while (running) {
|
||||
num_events = epoll_wait(fd_epoll, events_pending, currmax, timeout_ms);
|
||||
if (num_events == -1) {
|
||||
if (errno != EINTR)
|
||||
running = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* handle timeout */
|
||||
if (!num_events && timeout_ms >= 0) {
|
||||
running = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (i = 0; i < num_events; i++) { /* check waiting CAN RAW sockets */
|
||||
struct if_info *obj = events_pending[i].data.ptr;
|
||||
int idx;
|
||||
char *extra_info = "";
|
||||
|
||||
/* these settings may be modified by recvmsg() */
|
||||
iov.iov_len = sizeof(cu);
|
||||
msg.msg_namelen = sizeof(addr);
|
||||
msg.msg_controllen = sizeof(ctrlmsg);
|
||||
msg.msg_flags = 0;
|
||||
|
||||
nbytes = recvmsg(obj->s, &msg, 0);
|
||||
idx = idx2dindex(addr.can_ifindex, obj->s);
|
||||
|
||||
if (nbytes < 0) {
|
||||
if ((errno == ENETDOWN) && !down_causes_exit) {
|
||||
fprintf(stderr, "%s: interface down\n", devname[idx]);
|
||||
continue;
|
||||
}
|
||||
perror("read");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* mark dual-use struct canfd_frame */
|
||||
if (nbytes < (int)CANXL_HDR_SIZE + CANXL_MIN_DLEN) {
|
||||
fprintf(stderr, "read: no CAN frame\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (cu.xl.flags & CANXL_XLF) {
|
||||
if (nbytes != (int)CANXL_HDR_SIZE + cu.xl.len) {
|
||||
printf("nbytes = %d\n", nbytes);
|
||||
fprintf(stderr, "read: no CAN XL frame\n");
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
if (nbytes == CAN_MTU)
|
||||
cu.fd.flags = 0;
|
||||
else if (nbytes == CANFD_MTU)
|
||||
cu.fd.flags |= CANFD_FDF;
|
||||
else {
|
||||
fprintf(stderr, "read: incomplete CAN CC/FD frame\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (count && (--count == 0))
|
||||
running = 0;
|
||||
|
||||
for (cmsg = CMSG_FIRSTHDR(&msg);
|
||||
cmsg && (cmsg->cmsg_level == SOL_SOCKET);
|
||||
cmsg = CMSG_NXTHDR(&msg,cmsg)) {
|
||||
if (cmsg->cmsg_type == SO_TIMESTAMP) {
|
||||
memcpy(&tv, CMSG_DATA(cmsg), sizeof(tv));
|
||||
} else if (cmsg->cmsg_type == SO_TIMESTAMPING) {
|
||||
struct timespec *stamp = (struct timespec *)CMSG_DATA(cmsg);
|
||||
|
||||
/*
|
||||
* stamp[0] is the software timestamp
|
||||
* stamp[1] is deprecated
|
||||
* stamp[2] is the raw hardware timestamp
|
||||
* See chapter 2.1.2 Receive timestamps in
|
||||
* linux/Documentation/networking/timestamping.txt
|
||||
*/
|
||||
tv.tv_sec = stamp[2].tv_sec;
|
||||
tv.tv_usec = stamp[2].tv_nsec / 1000;
|
||||
} else if (cmsg->cmsg_type == SO_RXQ_OVFL) {
|
||||
memcpy(&obj->dropcnt, CMSG_DATA(cmsg), sizeof(__u32));
|
||||
}
|
||||
}
|
||||
|
||||
/* check for (unlikely) dropped frames on this specific socket */
|
||||
if (obj->dropcnt != obj->last_dropcnt) {
|
||||
__u32 frames = obj->dropcnt - obj->last_dropcnt;
|
||||
|
||||
if (silent != SILENT_ON)
|
||||
printf("DROPCOUNT: dropped %u CAN frame%s on '%s' socket (total drops %u)\n",
|
||||
frames, (frames > 1)?"s":"", devname[idx], obj->dropcnt);
|
||||
|
||||
if (log)
|
||||
fprintf(logfile, "DROPCOUNT: dropped %u CAN frame%s on '%s' socket (total drops %u)\n",
|
||||
frames, (frames > 1)?"s":"", devname[idx], obj->dropcnt);
|
||||
|
||||
obj->last_dropcnt = obj->dropcnt;
|
||||
}
|
||||
|
||||
/* once we detected a EFF frame indent SFF frames accordingly */
|
||||
if (cu.fd.can_id & CAN_EFF_FLAG)
|
||||
view |= CANLIB_VIEW_INDENT_SFF;
|
||||
|
||||
if (extra_msg_info) {
|
||||
if (msg.msg_flags & MSG_DONTROUTE)
|
||||
extra_info = " T";
|
||||
else
|
||||
extra_info = " R";
|
||||
}
|
||||
|
||||
/* build common log format output */
|
||||
if ((log) || ((logfrmt) && (silent == SILENT_OFF))) {
|
||||
|
||||
alen = sprint_timestamp(afrbuf, logtimestamp,
|
||||
&tv, &last_tv);
|
||||
|
||||
alen += sprintf(afrbuf + alen, "%*s ",
|
||||
max_devname_len, devname[idx]);
|
||||
|
||||
alen += snprintf_canframe(afrbuf + alen, sizeof(afrbuf) - alen, &cu, 0);
|
||||
}
|
||||
|
||||
/* write CAN frame in log file style to logfile */
|
||||
if (log)
|
||||
fprintf(logfile, "%s%s\n", afrbuf, extra_info);
|
||||
|
||||
/* print CAN frame in log file style to stdout */
|
||||
if ((logfrmt) && (silent == SILENT_OFF)) {
|
||||
printf("%s%s\n", afrbuf, extra_info);
|
||||
goto out_fflush; /* no other output to stdout */
|
||||
}
|
||||
|
||||
/* print only animation */
|
||||
if (silent != SILENT_OFF) {
|
||||
if (silent == SILENT_ANI) {
|
||||
printf("%c\b", anichar[silentani %= MAXANI]);
|
||||
silentani++;
|
||||
}
|
||||
goto out_fflush; /* no other output to stdout */
|
||||
}
|
||||
|
||||
/* print (colored) long CAN frame style to stdout */
|
||||
alen = sprintf(afrbuf, " %s", (color > 2) ? col_on[idx % MAXCOL] : "");
|
||||
alen += sprint_timestamp(afrbuf + alen, timestamp, &tv, &last_tv);
|
||||
alen += sprintf(afrbuf + alen, " %s%*s",
|
||||
(color && (color < 3)) ? col_on[idx % MAXCOL] : "",
|
||||
max_devname_len, devname[idx]);
|
||||
|
||||
if (extra_msg_info) {
|
||||
if (msg.msg_flags & MSG_DONTROUTE)
|
||||
alen += sprintf(afrbuf + alen, " TX %s",
|
||||
extra_m_info[cu.fd.flags & 3]);
|
||||
else
|
||||
alen += sprintf(afrbuf + alen, " RX %s",
|
||||
extra_m_info[cu.fd.flags & 3]);
|
||||
}
|
||||
|
||||
alen += sprintf(afrbuf + alen, "%s ", (color == 1) ? col_off : "");
|
||||
alen += snprintf_long_canframe(afrbuf + alen, sizeof(afrbuf) - alen, &cu, view);
|
||||
|
||||
if ((view & CANLIB_VIEW_ERROR) && (cu.fd.can_id & CAN_ERR_FLAG)) {
|
||||
alen += sprintf(afrbuf + alen, "\n\t");
|
||||
alen += snprintf_can_error_frame(afrbuf + alen,
|
||||
sizeof(afrbuf) - alen,
|
||||
&cu.fd, "\n\t");
|
||||
}
|
||||
|
||||
printf("%s%s\n", afrbuf, (color > 1) ? col_off : "");
|
||||
out_fflush:
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < currmax; i++)
|
||||
close(sock_info[i].s);
|
||||
|
||||
close(fd_epoll);
|
||||
|
||||
if (log)
|
||||
fclose(logfile);
|
||||
|
||||
if (signal_num)
|
||||
return 128 + signal_num;
|
||||
|
||||
return 0;
|
||||
}
|
||||
626
Devices/Libraries/Systems/can-utils/canfdtest.c
Executable file
626
Devices/Libraries/Systems/can-utils/canfdtest.c
Executable file
@@ -0,0 +1,626 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* canfdtest.c - Full-duplex test program (DUT and host part)
|
||||
*
|
||||
* (C) 2009 by Vladislav Gribov, IXXAT Automation GmbH, <gribov@ixxat.de>
|
||||
* (C) 2009 Wolfgang Grandegger <wg@grandegger.com>
|
||||
* (C) 2021 Jean Gressmann, IAV GmbH, <jean.steven.gressmann@iav.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the version 2 of the GNU General Public License
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* Send feedback to <linux-can@vger.kernel.org>
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <libgen.h>
|
||||
#include <limits.h>
|
||||
#include <sched.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/raw.h>
|
||||
|
||||
#define CAN_MSG_ID_PING 0x77
|
||||
#define CAN_MSG_ID_PONG 0x78
|
||||
#define CAN_MSG_LEN 8
|
||||
#define CAN_MSG_COUNT 50
|
||||
#define CAN_MSG_WAIT 27
|
||||
|
||||
static int running = 1;
|
||||
static int verbose;
|
||||
static int sockfd;
|
||||
static int test_loops;
|
||||
static int exit_sig;
|
||||
static int inflight_count = CAN_MSG_COUNT;
|
||||
static int filter;
|
||||
static canid_t can_id_ping = CAN_MSG_ID_PING;
|
||||
static canid_t can_id_pong = CAN_MSG_ID_PONG;
|
||||
static bool has_pong_id;
|
||||
static bool is_can_fd;
|
||||
static bool bit_rate_switch;
|
||||
static int msg_len = CAN_MSG_LEN;
|
||||
static bool is_extended_frame_format;
|
||||
|
||||
static void print_usage(char *prg)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"%s - Full-duplex test program (DUT and host part).\n"
|
||||
"Usage: %s [options] [<can-interface>]\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
" -b (enable CAN FD Bit Rate Switch)\n"
|
||||
" -d (use CAN FD frames instead of classic CAN)\n"
|
||||
" -e (use 29-bit extended frame format instead of classic 11-bit one)\n"
|
||||
" -f COUNT (number of frames in flight, default: %d)\n"
|
||||
" -g (generate messages)\n"
|
||||
" -i ID (CAN ID to use for frames to DUT (ping), default %x)\n"
|
||||
" -l COUNT (test loop count)\n"
|
||||
" -o ID (CAN ID to use for frames to host (pong), default %x)\n"
|
||||
" -s SIZE (frame payload size in bytes)\n"
|
||||
" -v (low verbosity)\n"
|
||||
" -vv (high verbosity)\n"
|
||||
" -x (ignore other frames on bus)\n"
|
||||
" -xx (ignore locally generated and other frames on bus -- use for loopback testing)\n"
|
||||
"\n"
|
||||
"With the option '-g' CAN messages are generated and checked\n"
|
||||
"on <can-interface>, otherwise all messages received on the\n"
|
||||
"<can-interface> are sent back incrementing the CAN id and\n"
|
||||
"all data bytes. The program can be aborted with ^C.\n"
|
||||
"\n"
|
||||
"Using 'can0' as default CAN-interface.\n"
|
||||
"\n"
|
||||
"Examples:\n"
|
||||
"\ton DUT:\n"
|
||||
"%s -v can0\n"
|
||||
"\ton Host:\n"
|
||||
"%s -g -v can2\n",
|
||||
prg, prg, CAN_MSG_COUNT, CAN_MSG_ID_PING, CAN_MSG_ID_PONG, prg, prg);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void print_frame(canid_t id, const uint8_t *data, int dlc, int inc_data)
|
||||
{
|
||||
int i;
|
||||
|
||||
printf("%04x: ", id);
|
||||
if (id & CAN_RTR_FLAG) {
|
||||
printf("remote request");
|
||||
} else {
|
||||
printf("[%d]", dlc);
|
||||
for (i = 0; i < dlc; i++)
|
||||
printf(" %02x", (uint8_t)(data[i] + inc_data));
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static void print_compare(canid_t exp_id, const uint8_t *exp_data, uint8_t exp_dlc,
|
||||
canid_t rec_id, const uint8_t *rec_data, uint8_t rec_dlc,
|
||||
int inc)
|
||||
{
|
||||
printf("expected: ");
|
||||
print_frame(exp_id, exp_data, exp_dlc, inc);
|
||||
printf("received: ");
|
||||
print_frame(rec_id, rec_data, rec_dlc, 0);
|
||||
}
|
||||
|
||||
static canid_t normalize_canid(canid_t id)
|
||||
{
|
||||
if (is_extended_frame_format) {
|
||||
id &= CAN_EFF_MASK;
|
||||
id |= CAN_EFF_FLAG;
|
||||
} else {
|
||||
id &= CAN_SFF_MASK;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
static int compare_frame(const struct canfd_frame *exp, const struct canfd_frame *rec, int inc)
|
||||
{
|
||||
int i, err = 0;
|
||||
const canid_t expected_can_id = inc ? can_id_pong : can_id_ping;
|
||||
|
||||
if (rec->can_id != expected_can_id) {
|
||||
printf("Message ID mismatch!\n");
|
||||
print_compare(expected_can_id, exp->data, exp->len,
|
||||
rec->can_id, rec->data, rec->len, inc);
|
||||
running = 0;
|
||||
err = -1;
|
||||
} else if (rec->len != exp->len) {
|
||||
printf("Message length mismatch!\n");
|
||||
print_compare(expected_can_id, exp->data, exp->len,
|
||||
rec->can_id, rec->data, rec->len, inc);
|
||||
running = 0;
|
||||
err = -1;
|
||||
} else {
|
||||
for (i = 0; i < rec->len; i++) {
|
||||
if (rec->data[i] != (uint8_t)(exp->data[i] + inc)) {
|
||||
printf("Databyte %x mismatch!\n", i);
|
||||
print_compare(expected_can_id, exp->data, exp->len,
|
||||
rec->can_id, rec->data, rec->len, inc);
|
||||
running = 0;
|
||||
err = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static void millisleep(int msecs)
|
||||
{
|
||||
struct timespec rqtp, rmtp;
|
||||
int err;
|
||||
|
||||
/* sleep in ms */
|
||||
rqtp.tv_sec = msecs / 1000;
|
||||
rqtp.tv_nsec = msecs % 1000 * 1000000;
|
||||
|
||||
do {
|
||||
err = clock_nanosleep(CLOCK_MONOTONIC, 0, &rqtp, &rmtp);
|
||||
if (err != 0 && err != EINTR) {
|
||||
printf("t\n");
|
||||
break;
|
||||
}
|
||||
rqtp = rmtp;
|
||||
} while (err != 0);
|
||||
}
|
||||
|
||||
static void echo_progress(unsigned char data)
|
||||
{
|
||||
if (data == 0xff) {
|
||||
printf(".");
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
static void signal_handler(int signo)
|
||||
{
|
||||
close(sockfd);
|
||||
running = 0;
|
||||
exit_sig = signo;
|
||||
}
|
||||
|
||||
static int recv_frame(struct canfd_frame *frame, int *flags)
|
||||
{
|
||||
struct iovec iov = {
|
||||
.iov_base = frame,
|
||||
.iov_len = is_can_fd ? sizeof(struct canfd_frame) : sizeof(struct can_frame),
|
||||
};
|
||||
struct msghdr msg = {
|
||||
.msg_iov = &iov,
|
||||
.msg_iovlen = 1,
|
||||
};
|
||||
ssize_t ret;
|
||||
|
||||
ret = recvmsg(sockfd, &msg, 0);
|
||||
if (ret < 0) {
|
||||
perror("recvmsg() failed");
|
||||
return -1;
|
||||
}
|
||||
if ((size_t)ret != iov.iov_len) {
|
||||
fprintf(stderr, "recvmsg() returned %zd", ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (flags)
|
||||
*flags = msg.msg_flags;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int send_frame(struct canfd_frame *frame)
|
||||
{
|
||||
ssize_t ret, len;
|
||||
|
||||
if (is_can_fd)
|
||||
len = sizeof(struct canfd_frame);
|
||||
else
|
||||
len = sizeof(struct can_frame);
|
||||
|
||||
if (bit_rate_switch)
|
||||
frame->flags |= CANFD_BRS;
|
||||
|
||||
while ((ret = send(sockfd, frame, len, 0)) != len) {
|
||||
if (ret >= 0) {
|
||||
fprintf(stderr, "send returned %zd", ret);
|
||||
return -1;
|
||||
}
|
||||
if (errno != ENOBUFS) {
|
||||
perror("send failed");
|
||||
return -1;
|
||||
}
|
||||
if (verbose) {
|
||||
printf("N");
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int check_frame(const struct canfd_frame *frame)
|
||||
{
|
||||
int err = 0;
|
||||
int i;
|
||||
|
||||
if (frame->can_id != can_id_ping) {
|
||||
printf("Unexpected Message ID 0x%04x!\n", frame->can_id);
|
||||
err = -1;
|
||||
}
|
||||
|
||||
if (frame->len != msg_len) {
|
||||
printf("Unexpected Message length %d!\n", frame->len);
|
||||
err = -1;
|
||||
}
|
||||
|
||||
for (i = 1; i < frame->len; i++) {
|
||||
if (frame->data[i] != (uint8_t)(frame->data[i - 1] + 1)) {
|
||||
printf("Frame inconsistent!\n");
|
||||
print_frame(frame->can_id, frame->data, frame->len, 0);
|
||||
err = -1;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void inc_frame(struct canfd_frame *frame)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (has_pong_id)
|
||||
frame->can_id = can_id_pong;
|
||||
else
|
||||
frame->can_id = normalize_canid(frame->can_id + 1);
|
||||
|
||||
for (i = 0; i < frame->len; i++)
|
||||
frame->data[i]++;
|
||||
}
|
||||
|
||||
static int can_echo_dut(void)
|
||||
{
|
||||
unsigned int frame_count = 0;
|
||||
struct canfd_frame frame;
|
||||
int err = 0;
|
||||
|
||||
while (running) {
|
||||
int flags;
|
||||
|
||||
if (recv_frame(&frame, &flags))
|
||||
return -1;
|
||||
|
||||
if (filter > 1 && flags & MSG_DONTROUTE)
|
||||
continue;
|
||||
|
||||
frame_count++;
|
||||
if (verbose == 1) {
|
||||
echo_progress(frame.data[0]);
|
||||
} else if (verbose > 1) {
|
||||
if (verbose > 2)
|
||||
printf("%s %s: ",
|
||||
flags & MSG_DONTROUTE ? "DR" : " ",
|
||||
flags & MSG_CONFIRM ? "CF" : " ");
|
||||
print_frame(frame.can_id, frame.data, frame.len, 0);
|
||||
}
|
||||
|
||||
err = check_frame(&frame);
|
||||
inc_frame(&frame);
|
||||
if (send_frame(&frame))
|
||||
return -1;
|
||||
|
||||
/*
|
||||
* to force a interlacing of the frames send by DUT and PC
|
||||
* test tool a waiting time is injected
|
||||
*/
|
||||
if (frame_count == CAN_MSG_WAIT) {
|
||||
frame_count = 0;
|
||||
millisleep(3);
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int can_echo_gen(void)
|
||||
{
|
||||
struct canfd_frame *tx_frames;
|
||||
bool *recv_tx;
|
||||
unsigned char counter = 0;
|
||||
int send_pos = 0, recv_rx_pos = 0, recv_tx_pos = 0, unprocessed = 0, loops = 0;
|
||||
int err = 0;
|
||||
int i;
|
||||
|
||||
tx_frames = calloc(inflight_count, sizeof(*tx_frames));
|
||||
if (!tx_frames)
|
||||
return -1;
|
||||
|
||||
recv_tx = calloc(inflight_count, sizeof(*recv_tx));
|
||||
if (!recv_tx) {
|
||||
err = -1;
|
||||
goto out_free_tx_frames;
|
||||
}
|
||||
|
||||
while (running) {
|
||||
if (unprocessed < inflight_count) {
|
||||
/* still send messages */
|
||||
struct canfd_frame *tx_frame = &tx_frames[send_pos];
|
||||
|
||||
tx_frame->len = msg_len;
|
||||
tx_frame->can_id = can_id_ping;
|
||||
recv_tx[send_pos] = false;
|
||||
|
||||
for (i = 0; i < msg_len; i++)
|
||||
tx_frame->data[i] = counter + i;
|
||||
if (send_frame(tx_frame)) {
|
||||
err = -1;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
send_pos++;
|
||||
send_pos %= inflight_count;
|
||||
|
||||
unprocessed++;
|
||||
if (verbose == 1)
|
||||
echo_progress(counter);
|
||||
counter++;
|
||||
|
||||
if ((counter % 33) == 0)
|
||||
millisleep(3);
|
||||
else
|
||||
millisleep(1);
|
||||
} else {
|
||||
struct canfd_frame rx_frame;
|
||||
int flags;
|
||||
|
||||
if (recv_frame(&rx_frame, &flags)) {
|
||||
err = -1;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
if (filter > 1 &&
|
||||
((rx_frame.can_id == can_id_ping && !(flags & MSG_CONFIRM)) ||
|
||||
(rx_frame.can_id == can_id_pong && (flags & MSG_DONTROUTE))))
|
||||
continue;
|
||||
|
||||
if (verbose > 1) {
|
||||
if (verbose > 2)
|
||||
printf("%s %s: ",
|
||||
flags & MSG_DONTROUTE ? "DR" : " ",
|
||||
flags & MSG_CONFIRM ? "CF" : " ");
|
||||
print_frame(rx_frame.can_id, rx_frame.data, rx_frame.len, 0);
|
||||
}
|
||||
|
||||
/* own frame */
|
||||
if (flags & MSG_CONFIRM) {
|
||||
err = compare_frame(&tx_frames[recv_tx_pos], &rx_frame, 0);
|
||||
recv_tx[recv_tx_pos] = true;
|
||||
recv_tx_pos++;
|
||||
recv_tx_pos %= inflight_count;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!recv_tx[recv_rx_pos]) {
|
||||
printf("RX before TX!\n");
|
||||
print_frame(rx_frame.can_id, rx_frame.data, rx_frame.len, 0);
|
||||
running = 0;
|
||||
}
|
||||
/* compare with expected */
|
||||
err = compare_frame(&tx_frames[recv_rx_pos], &rx_frame, 1);
|
||||
recv_rx_pos++;
|
||||
recv_rx_pos %= inflight_count;
|
||||
|
||||
loops++;
|
||||
if (test_loops && loops >= test_loops)
|
||||
break;
|
||||
|
||||
unprocessed--;
|
||||
}
|
||||
}
|
||||
|
||||
printf("\nTest messages sent and received: %d\n", loops);
|
||||
|
||||
out_free:
|
||||
free(recv_tx);
|
||||
out_free_tx_frames:
|
||||
free(tx_frames);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct sockaddr_can addr;
|
||||
char *intf_name = "can0";
|
||||
int family = PF_CAN, type = SOCK_RAW, proto = CAN_RAW;
|
||||
int echo_gen = 0;
|
||||
int opt, err;
|
||||
int enable_socket_option = 1;
|
||||
|
||||
signal(SIGTERM, signal_handler);
|
||||
signal(SIGHUP, signal_handler);
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
while ((opt = getopt(argc, argv, "bdef:gi:l:o:s:vx?")) != -1) {
|
||||
switch (opt) {
|
||||
case 'b':
|
||||
bit_rate_switch = true;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
is_can_fd = true;
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
is_extended_frame_format = true;
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
inflight_count = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
echo_gen = 1;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
can_id_ping = strtoul(optarg, NULL, 16);
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
test_loops = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
can_id_pong = strtoul(optarg, NULL, 16);
|
||||
has_pong_id = true;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
msg_len = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
verbose++;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
filter++;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
default:
|
||||
print_usage(basename(argv[0]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* BRS can be enabled only if CAN FD is enabled */
|
||||
if (bit_rate_switch && !is_can_fd) {
|
||||
printf("Bit rate switch (-b) needs CAN FD (-d) to be enabled\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Make sure the message length is valid */
|
||||
if (msg_len <= 0) {
|
||||
printf("Message length must > 0\n");
|
||||
return 1;
|
||||
}
|
||||
if (is_can_fd) {
|
||||
if (msg_len > CANFD_MAX_DLEN) {
|
||||
printf("Message length must be <= %d bytes for CAN FD\n", CANFD_MAX_DLEN);
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
if (msg_len > CAN_MAX_DLEN) {
|
||||
printf("Message length must be <= %d bytes for CAN 2.0B\n", CAN_MAX_DLEN);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
can_id_ping = normalize_canid(can_id_ping);
|
||||
can_id_pong = normalize_canid(can_id_pong);
|
||||
|
||||
if ((argc - optind) == 1)
|
||||
intf_name = argv[optind];
|
||||
else if ((argc - optind))
|
||||
print_usage(basename(argv[0]));
|
||||
|
||||
printf("interface = %s, family = %d, type = %d, proto = %d\n",
|
||||
intf_name, family, type, proto);
|
||||
|
||||
if ((sockfd = socket(family, type, proto)) < 0) {
|
||||
perror("socket");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (echo_gen) {
|
||||
if (setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS,
|
||||
&enable_socket_option, sizeof(enable_socket_option)) == -1) {
|
||||
perror("setsockopt CAN_RAW_RECV_OWN_MSGS");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_can_fd) {
|
||||
if (setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FD_FRAMES,
|
||||
&enable_socket_option, sizeof(enable_socket_option)) == -1) {
|
||||
perror("setsockopt CAN_RAW_FD_FRAMES");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
addr.can_family = family;
|
||||
addr.can_ifindex = if_nametoindex(intf_name);
|
||||
if (!addr.can_ifindex) {
|
||||
perror("if_nametoindex");
|
||||
close(sockfd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror("bind");
|
||||
close(sockfd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!has_pong_id)
|
||||
can_id_pong = can_id_ping + 1;
|
||||
|
||||
if (filter) {
|
||||
const struct can_filter filters[] = {
|
||||
{
|
||||
.can_id = can_id_ping,
|
||||
.can_mask = CAN_EFF_FLAG | CAN_RTR_FLAG | CAN_EFF_MASK,
|
||||
},
|
||||
{
|
||||
.can_id = can_id_pong,
|
||||
.can_mask = CAN_EFF_FLAG | CAN_RTR_FLAG | CAN_EFF_MASK,
|
||||
},
|
||||
};
|
||||
|
||||
if (setsockopt(sockfd, SOL_CAN_RAW, CAN_RAW_FILTER, filters,
|
||||
sizeof(struct can_filter) * (1 + echo_gen))) {
|
||||
perror("setsockopt()");
|
||||
close(sockfd);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (echo_gen)
|
||||
err = can_echo_gen();
|
||||
else
|
||||
err = can_echo_dut();
|
||||
|
||||
if (verbose)
|
||||
printf("Exiting...\n");
|
||||
|
||||
close(sockfd);
|
||||
|
||||
if (exit_sig)
|
||||
return 128 + exit_sig;
|
||||
|
||||
return err;
|
||||
}
|
||||
283
Devices/Libraries/Systems/can-utils/canframelen.c
Executable file
283
Devices/Libraries/Systems/can-utils/canframelen.c
Executable file
@@ -0,0 +1,283 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
|
||||
/*
|
||||
* canframelen.c
|
||||
*
|
||||
* Copyright (c) 2013, 2014 Czech Technical University in Prague
|
||||
*
|
||||
* Author: Michal Sojka <sojkam1@fel.cvut.cz>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Czech Technical University in Prague nor the
|
||||
* names of its contributors may be used to endorse or promote
|
||||
* products derived from this software without specific prior
|
||||
* written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* Send feedback to <linux-can@vger.kernel.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "canframelen.h"
|
||||
|
||||
/**
|
||||
* Functions and types for CRC checks.
|
||||
*
|
||||
* Generated on Wed Jan 8 15:14:20 2014,
|
||||
* by pycrc v0.8.1, http://www.tty1.net/pycrc/
|
||||
* using the configuration:
|
||||
* Width = 15
|
||||
* Poly = 0x4599
|
||||
* XorIn = 0x0000
|
||||
* ReflectIn = False
|
||||
* XorOut = 0x0000
|
||||
* ReflectOut = False
|
||||
* Algorithm = table-driven
|
||||
*****************************************************************************/
|
||||
|
||||
typedef uint16_t crc_t;
|
||||
|
||||
/**
|
||||
* Static table used for the table_driven implementation.
|
||||
*****************************************************************************/
|
||||
static const crc_t crc_table[256] = {
|
||||
0x0000, 0x4599, 0x4eab, 0x0b32, 0x58cf, 0x1d56, 0x1664, 0x53fd, 0x7407, 0x319e, 0x3aac, 0x7f35, 0x2cc8, 0x6951, 0x6263, 0x27fa,
|
||||
0x2d97, 0x680e, 0x633c, 0x26a5, 0x7558, 0x30c1, 0x3bf3, 0x7e6a, 0x5990, 0x1c09, 0x173b, 0x52a2, 0x015f, 0x44c6, 0x4ff4, 0x0a6d,
|
||||
0x5b2e, 0x1eb7, 0x1585, 0x501c, 0x03e1, 0x4678, 0x4d4a, 0x08d3, 0x2f29, 0x6ab0, 0x6182, 0x241b, 0x77e6, 0x327f, 0x394d, 0x7cd4,
|
||||
0x76b9, 0x3320, 0x3812, 0x7d8b, 0x2e76, 0x6bef, 0x60dd, 0x2544, 0x02be, 0x4727, 0x4c15, 0x098c, 0x5a71, 0x1fe8, 0x14da, 0x5143,
|
||||
0x73c5, 0x365c, 0x3d6e, 0x78f7, 0x2b0a, 0x6e93, 0x65a1, 0x2038, 0x07c2, 0x425b, 0x4969, 0x0cf0, 0x5f0d, 0x1a94, 0x11a6, 0x543f,
|
||||
0x5e52, 0x1bcb, 0x10f9, 0x5560, 0x069d, 0x4304, 0x4836, 0x0daf, 0x2a55, 0x6fcc, 0x64fe, 0x2167, 0x729a, 0x3703, 0x3c31, 0x79a8,
|
||||
0x28eb, 0x6d72, 0x6640, 0x23d9, 0x7024, 0x35bd, 0x3e8f, 0x7b16, 0x5cec, 0x1975, 0x1247, 0x57de, 0x0423, 0x41ba, 0x4a88, 0x0f11,
|
||||
0x057c, 0x40e5, 0x4bd7, 0x0e4e, 0x5db3, 0x182a, 0x1318, 0x5681, 0x717b, 0x34e2, 0x3fd0, 0x7a49, 0x29b4, 0x6c2d, 0x671f, 0x2286,
|
||||
0x2213, 0x678a, 0x6cb8, 0x2921, 0x7adc, 0x3f45, 0x3477, 0x71ee, 0x5614, 0x138d, 0x18bf, 0x5d26, 0x0edb, 0x4b42, 0x4070, 0x05e9,
|
||||
0x0f84, 0x4a1d, 0x412f, 0x04b6, 0x574b, 0x12d2, 0x19e0, 0x5c79, 0x7b83, 0x3e1a, 0x3528, 0x70b1, 0x234c, 0x66d5, 0x6de7, 0x287e,
|
||||
0x793d, 0x3ca4, 0x3796, 0x720f, 0x21f2, 0x646b, 0x6f59, 0x2ac0, 0x0d3a, 0x48a3, 0x4391, 0x0608, 0x55f5, 0x106c, 0x1b5e, 0x5ec7,
|
||||
0x54aa, 0x1133, 0x1a01, 0x5f98, 0x0c65, 0x49fc, 0x42ce, 0x0757, 0x20ad, 0x6534, 0x6e06, 0x2b9f, 0x7862, 0x3dfb, 0x36c9, 0x7350,
|
||||
0x51d6, 0x144f, 0x1f7d, 0x5ae4, 0x0919, 0x4c80, 0x47b2, 0x022b, 0x25d1, 0x6048, 0x6b7a, 0x2ee3, 0x7d1e, 0x3887, 0x33b5, 0x762c,
|
||||
0x7c41, 0x39d8, 0x32ea, 0x7773, 0x248e, 0x6117, 0x6a25, 0x2fbc, 0x0846, 0x4ddf, 0x46ed, 0x0374, 0x5089, 0x1510, 0x1e22, 0x5bbb,
|
||||
0x0af8, 0x4f61, 0x4453, 0x01ca, 0x5237, 0x17ae, 0x1c9c, 0x5905, 0x7eff, 0x3b66, 0x3054, 0x75cd, 0x2630, 0x63a9, 0x689b, 0x2d02,
|
||||
0x276f, 0x62f6, 0x69c4, 0x2c5d, 0x7fa0, 0x3a39, 0x310b, 0x7492, 0x5368, 0x16f1, 0x1dc3, 0x585a, 0x0ba7, 0x4e3e, 0x450c, 0x0095
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the crc value with new data.
|
||||
*
|
||||
* \param crc The current crc value.
|
||||
* \param data Pointer to a buffer of \a data_len bytes.
|
||||
* \param data_len Number of bytes in the \a data buffer.
|
||||
* \return The updated crc value.
|
||||
*****************************************************************************/
|
||||
static crc_t crc_update_bytewise(crc_t crc, const unsigned char *data, size_t data_len)
|
||||
{
|
||||
unsigned int tbl_idx;
|
||||
|
||||
while (data_len--) {
|
||||
tbl_idx = ((crc >> 7) ^ *data) & 0xff;
|
||||
crc = (crc_table[tbl_idx] ^ (crc << 8)) & 0x7fff;
|
||||
|
||||
data++;
|
||||
}
|
||||
return crc & 0x7fff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the crc value with new data.
|
||||
*
|
||||
* \param crc The current crc value.
|
||||
* \param data Data value
|
||||
* \param bits The number of most significant bits in data used for CRC calculation
|
||||
* \return The updated crc value.
|
||||
*****************************************************************************/
|
||||
static crc_t crc_update_bitwise(crc_t crc, uint8_t data, size_t bits)
|
||||
{
|
||||
uint8_t i;
|
||||
bool bit;
|
||||
|
||||
for (i = 0x80; bits--; i >>= 1) {
|
||||
bit = crc & 0x4000;
|
||||
if (data & i) {
|
||||
bit = !bit;
|
||||
}
|
||||
crc <<= 1;
|
||||
if (bit) {
|
||||
crc ^= 0x4599;
|
||||
}
|
||||
}
|
||||
return crc & 0x7fff;
|
||||
}
|
||||
|
||||
static crc_t calc_bitmap_crc(uint8_t *bitmap, unsigned start, unsigned end)
|
||||
{
|
||||
crc_t crc = 0;
|
||||
|
||||
if (start % 8) {
|
||||
crc = crc_update_bitwise(crc, bitmap[start / 8] << (start % 8), 8 - start % 8);
|
||||
start += 8 - start % 8;
|
||||
}
|
||||
crc = crc_update_bytewise(crc, &bitmap[start / 8], (end - start) / 8);
|
||||
crc = crc_update_bitwise(crc, bitmap[end / 8], end % 8);
|
||||
return crc;
|
||||
}
|
||||
|
||||
static unsigned cfl_exact(struct can_frame *frame)
|
||||
{
|
||||
uint8_t bitmap[16];
|
||||
unsigned start = 0, end;
|
||||
crc_t crc;
|
||||
uint16_t crc_be;
|
||||
uint8_t mask, lookfor;
|
||||
unsigned i, stuffed;
|
||||
const int8_t clz[32] = /* count of leading zeros in 5 bit numbers */
|
||||
{ 5, 4, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
|
||||
/* Prepare bitmap */
|
||||
memset(bitmap, 0, sizeof(bitmap));
|
||||
if (frame->can_id & CAN_EFF_FLAG) {
|
||||
/* bit 7 0 7 0 7 0 7 0
|
||||
* bitmap[0-3] |.sBBBBBB BBBBBSIE EEEEEEEE EEEEEEEE| s = SOF, B = Base ID (11 bits), S = SRR, I = IDE, E = Extended ID (18 bits)
|
||||
* bitmap[4-7] |ER10DLC4 00000000 11111111 22222222| R = RTR, 0 = r0, 1 = r1, DLC4 = DLC, Data bytes
|
||||
* bitmap[8-11] |33333333 44444444 55555555 66666666| Data bytes
|
||||
* bitmap[12-15] |77777777 ........ ........ ........| Data bytes
|
||||
*/
|
||||
bitmap[0] = (frame->can_id & CAN_EFF_MASK) >> 23;
|
||||
bitmap[1] = ((frame->can_id >> 18) & 0x3f) << 3 |
|
||||
3 << 1 | /* SRR, IDE */
|
||||
((frame->can_id >> 17) & 0x01);
|
||||
bitmap[2] = (frame->can_id >> 9) & 0xff;
|
||||
bitmap[3] = (frame->can_id >> 1) & 0xff;
|
||||
bitmap[4] = (frame->can_id & 0x1) << 7 |
|
||||
(!!(frame->can_id & CAN_RTR_FLAG)) << 6 |
|
||||
0 << 4 | /* r1, r0 */
|
||||
(frame->can_dlc & 0xf);
|
||||
memcpy(&bitmap[5], &frame->data, frame->can_dlc);
|
||||
start = 1;
|
||||
end = 40 + 8*frame->can_dlc;
|
||||
} else {
|
||||
/* bit 7 0 7 0 7 0 7 0
|
||||
* bitmap[0-3] |.....sII IIIIIIII IRE0DLC4 00000000| s = SOF, I = ID (11 bits), R = RTR, E = IDE, DLC4 = DLC
|
||||
* bitmap[4-7] |11111111 22222222 33333333 44444444| Data bytes
|
||||
* bitmap[8-11] |55555555 66666666 77777777 ........| Data bytes
|
||||
*/
|
||||
bitmap[0] = (frame->can_id & CAN_SFF_MASK) >> 9;
|
||||
bitmap[1] = (frame->can_id >> 1) & 0xff;
|
||||
bitmap[2] = ((frame->can_id << 7) & 0xff) |
|
||||
(!!(frame->can_id & CAN_RTR_FLAG)) << 6 |
|
||||
0 << 4 | /* IDE, r0 */
|
||||
(frame->can_dlc & 0xf);
|
||||
memcpy(&bitmap[3], &frame->data, frame->can_dlc);
|
||||
start = 5;
|
||||
end = 24 + 8 * frame->can_dlc;
|
||||
}
|
||||
|
||||
/* Calc and append CRC */
|
||||
crc = calc_bitmap_crc(bitmap, start, end);
|
||||
crc_be = htons(crc << 1);
|
||||
assert(end % 8 == 0);
|
||||
memcpy(bitmap + end / 8, &crc_be, 2);
|
||||
end += 15;
|
||||
|
||||
/* Count stuffed bits */
|
||||
mask = 0x1f;
|
||||
lookfor = 0;
|
||||
i = start;
|
||||
stuffed = 0;
|
||||
while (i < end) {
|
||||
unsigned change;
|
||||
unsigned bits = (bitmap[i / 8] << 8 | bitmap[i / 8 + 1]) >> (16 - 5 - i % 8);
|
||||
lookfor = lookfor ? 0 : mask; /* We alternate between looking for a series of zeros or ones */
|
||||
change = (bits & mask) ^ lookfor; /* 1 indicates a change */
|
||||
if (change) { /* No bit was stuffed here */
|
||||
i += clz[change];
|
||||
mask = 0x1f; /* Next look for 5 same bits */
|
||||
} else {
|
||||
i += (mask == 0x1f) ? 5 : 4;
|
||||
if (i <= end) {
|
||||
stuffed++;
|
||||
mask = 0x1e; /* Next look for 4 bits (5th bit is the stuffed one) */
|
||||
}
|
||||
}
|
||||
}
|
||||
return end - start + stuffed +
|
||||
3 + /* CRC del, ACK, ACK del */
|
||||
7 + /* EOF */
|
||||
3; /* IFS */
|
||||
}
|
||||
|
||||
unsigned can_frame_dbitrate_length(struct canfd_frame *frame, enum cfl_mode mode, int mtu)
|
||||
{
|
||||
if (mtu != CANFD_MTU || !(frame->flags & CANFD_BRS))
|
||||
return 0;
|
||||
switch (mode) {
|
||||
case CFL_NO_BITSTUFFING:
|
||||
return 1 /* brs/crcdel */ + 1 /* esi */ + 4 /* dlc */ +
|
||||
((frame->len >= 16) ? 21 : 17) +
|
||||
frame->len * 8;
|
||||
case CFL_WORSTCASE:
|
||||
return can_frame_dbitrate_length(frame, CFL_NO_BITSTUFFING, mtu) * 5 / 4;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned can_frame_length(struct canfd_frame *frame, enum cfl_mode mode, int mtu)
|
||||
{
|
||||
int eff = (frame->can_id & CAN_EFF_FLAG);
|
||||
|
||||
if (mtu == CANFD_MTU)
|
||||
/* not correct, but close ? */
|
||||
switch (mode) {
|
||||
case CFL_NO_BITSTUFFING:
|
||||
return 1 + (eff ? 29 : 11) + ((frame->len >= 16) ? 21 : 17) +
|
||||
5 /* r1, ide, edl, r0, brs/crcdel, */ + 12 /* trail */ +
|
||||
frame->len * 8;
|
||||
case CFL_WORSTCASE:
|
||||
return can_frame_length(frame, CFL_NO_BITSTUFFING, mtu) * 5 / 4;
|
||||
case CFL_EXACT:
|
||||
return 0; /* exact bittiming for CANFD not supported yet */
|
||||
}
|
||||
else if (mtu != CAN_MTU)
|
||||
return 0; /* Only CAN2.0 and CANFD supported now */
|
||||
|
||||
switch (mode) {
|
||||
case CFL_NO_BITSTUFFING:
|
||||
return (eff ? 67 : 47) + frame->len * 8;
|
||||
case CFL_WORSTCASE:
|
||||
return (eff ? 80 : 55) + frame->len * 10;
|
||||
case CFL_EXACT:
|
||||
return cfl_exact((struct can_frame*)frame);
|
||||
}
|
||||
return 0; /* Unknown mode */
|
||||
}
|
||||
85
Devices/Libraries/Systems/can-utils/canframelen.h
Executable file
85
Devices/Libraries/Systems/can-utils/canframelen.h
Executable file
@@ -0,0 +1,85 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
|
||||
/*
|
||||
* canframelen.h
|
||||
*
|
||||
* Copyright (c) 2013, 2014 Czech Technical University in Prague
|
||||
*
|
||||
* Author: Michal Sojka <sojkam1@fel.cvut.cz>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Czech Technical University in Prague nor the
|
||||
* names of its contributors may be used to endorse or promote
|
||||
* products derived from this software without specific prior
|
||||
* written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* Send feedback to <linux-can@vger.kernel.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CANFRAMELEN_H
|
||||
#define CANFRAMELEN_H
|
||||
|
||||
#include <linux/can.h>
|
||||
|
||||
/**
|
||||
* Frame length calculation modes.
|
||||
*
|
||||
* CFL_WORSTCASE corresponds to *worst* case calculation for
|
||||
* stuff-bits - see (1)-(3) in [1]. The worst case number of bits on
|
||||
* the wire can be calculated as:
|
||||
*
|
||||
* (34 + 8n - 1)/4 + 34 + 8n + 13 for SFF frames (11 bit CAN-ID) => 55 + 10n
|
||||
* (54 + 8n - 1)/4 + 54 + 8n + 13 for EFF frames (29 bit CAN-ID) => 80 + 10n
|
||||
*
|
||||
* while 'n' is the data length code (number of payload bytes)
|
||||
*
|
||||
* [1] "Controller Area Network (CAN) schedulability analysis:
|
||||
* Refuted, revisited and revised", Real-Time Syst (2007)
|
||||
* 35:239-272.
|
||||
*
|
||||
*/
|
||||
enum cfl_mode {
|
||||
CFL_NO_BITSTUFFING, /* plain bit calculation without bitstuffing */
|
||||
CFL_WORSTCASE, /* worst case estimation - see above */
|
||||
CFL_EXACT, /* exact calculation of stuffed bits based on frame
|
||||
* content and CRC */
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the number of bits a frame needs on the wire (including
|
||||
* inter frame space).
|
||||
*
|
||||
* Mode determines how to deal with stuffed bits.
|
||||
*/
|
||||
unsigned can_frame_length(struct canfd_frame *frame, enum cfl_mode mode, int mtu);
|
||||
unsigned can_frame_dbitrate_length(struct canfd_frame *frame, enum cfl_mode mode, int mtu);
|
||||
|
||||
#endif
|
||||
1090
Devices/Libraries/Systems/can-utils/cangen.c
Executable file
1090
Devices/Libraries/Systems/can-utils/cangen.c
Executable file
File diff suppressed because it is too large
Load Diff
1030
Devices/Libraries/Systems/can-utils/cangw.c
Executable file
1030
Devices/Libraries/Systems/can-utils/cangw.c
Executable file
File diff suppressed because it is too large
Load Diff
467
Devices/Libraries/Systems/can-utils/canlogserver.c
Executable file
467
Devices/Libraries/Systems/can-utils/canlogserver.c
Executable file
@@ -0,0 +1,467 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
|
||||
/*
|
||||
* canlogserver.c
|
||||
*
|
||||
* Copyright (c) 2002-2007 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* Send feedback to <linux-can@vger.kernel.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <libgen.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/raw.h>
|
||||
#include <linux/sockios.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#define MAXDEV 6 /* change sscanf()'s manually if changed here */
|
||||
#define ANYDEV "any"
|
||||
#define ANL "\r\n" /* newline in ASC mode */
|
||||
|
||||
#define DEFPORT 28700
|
||||
|
||||
static char devname[MAXDEV][IFNAMSIZ+1];
|
||||
static int dindex[MAXDEV];
|
||||
static int max_devname_len;
|
||||
|
||||
extern int optind, opterr, optopt;
|
||||
|
||||
static volatile int running = 1;
|
||||
static volatile sig_atomic_t signal_num;
|
||||
|
||||
static void print_usage(char *prg)
|
||||
{
|
||||
fprintf(stderr, "%s - log CAN frames and serves them.\n", prg);
|
||||
fprintf(stderr, "\nUsage: %s [options] <CAN interface>+\n", prg);
|
||||
fprintf(stderr, " (use CTRL-C to terminate %s)\n\n", prg);
|
||||
fprintf(stderr, "Options:\n");
|
||||
fprintf(stderr, " -m <mask> (ID filter mask. Default 0x00000000) *\n");
|
||||
fprintf(stderr, " -v <value> (ID filter value. Default 0x00000000) *\n");
|
||||
fprintf(stderr, " -i <0|1> (invert the specified ID filter) *\n");
|
||||
fprintf(stderr, " -e <emask> (mask for error frames)\n");
|
||||
fprintf(stderr, " -p <port> (listen on port <port>. Default: %d)\n", DEFPORT);
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "* The CAN ID filter matches, when ...\n");
|
||||
fprintf(stderr, " <received_can_id> & mask == value & mask\n");
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "When using more than one CAN interface the options\n");
|
||||
fprintf(stderr, "m/v/i/e have comma separated values e.g. '-m 0,7FF,0'\n");
|
||||
fprintf(stderr, "\nUse interface name '%s' to receive from all CAN interfaces.\n", ANYDEV);
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "After running canlogserver, connect to it via TCP to get logged data.\n");
|
||||
fprintf(stderr, "e.g. with 'nc localhost %d'\n", DEFPORT);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
static int idx2dindex(int ifidx, int socket)
|
||||
{
|
||||
int i;
|
||||
struct ifreq ifr;
|
||||
|
||||
for (i=0; i<MAXDEV; i++) {
|
||||
if (dindex[i] == ifidx)
|
||||
return i;
|
||||
}
|
||||
|
||||
/* create new interface index cache entry */
|
||||
|
||||
/* remove index cache zombies first */
|
||||
for (i=0; i < MAXDEV; i++) {
|
||||
if (dindex[i]) {
|
||||
ifr.ifr_ifindex = dindex[i];
|
||||
if (ioctl(socket, SIOCGIFNAME, &ifr) < 0)
|
||||
dindex[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (i=0; i < MAXDEV; i++)
|
||||
if (!dindex[i]) /* free entry */
|
||||
break;
|
||||
|
||||
if (i == MAXDEV) {
|
||||
printf("Interface index cache only supports %d interfaces.\n", MAXDEV);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
dindex[i] = ifidx;
|
||||
|
||||
ifr.ifr_ifindex = ifidx;
|
||||
if (ioctl(socket, SIOCGIFNAME, &ifr) < 0)
|
||||
perror("SIOCGIFNAME");
|
||||
|
||||
if (max_devname_len < (int)strlen(ifr.ifr_name))
|
||||
max_devname_len = strlen(ifr.ifr_name);
|
||||
|
||||
strcpy(devname[i], ifr.ifr_name);
|
||||
|
||||
pr_debug("new index %d (%s)\n", i, devname[i]);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a Signalhandler. When we get a signal, that a child
|
||||
* terminated, we wait for it, so the zombie will disappear.
|
||||
*/
|
||||
static void childdied(int i)
|
||||
{
|
||||
wait(NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a Signalhandler for a caught SIGTERM
|
||||
*/
|
||||
static void shutdown_gra(int i)
|
||||
{
|
||||
running = 0;
|
||||
signal_num = i;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct sigaction signalaction;
|
||||
sigset_t sigset;
|
||||
fd_set rdfs;
|
||||
int s[MAXDEV];
|
||||
int socki, accsocket;
|
||||
canid_t mask[MAXDEV] = {0};
|
||||
canid_t value[MAXDEV] = {0};
|
||||
int inv_filter[MAXDEV] = {0};
|
||||
can_err_mask_t err_mask[MAXDEV] = {0};
|
||||
int opt, ret;
|
||||
int currmax = 1; /* we assume at least one can bus ;-) */
|
||||
struct sockaddr_can addr;
|
||||
struct can_raw_vcid_options vcid_opts = {
|
||||
.flags = CAN_RAW_XL_VCID_RX_FILTER,
|
||||
.rx_vcid = 0,
|
||||
.rx_vcid_mask = 0,
|
||||
};
|
||||
struct can_filter rfilter;
|
||||
static cu_t cu; /* union for CAN CC/FD/XL frames */
|
||||
const int canfx_on = 1;
|
||||
int nbytes, i, j;
|
||||
struct ifreq ifr;
|
||||
struct timeval tv;
|
||||
int port = DEFPORT;
|
||||
struct sockaddr_in inaddr;
|
||||
struct sockaddr_in clientaddr;
|
||||
socklen_t sin_size = sizeof(clientaddr);
|
||||
static char afrbuf[AFRSZ];
|
||||
|
||||
sigemptyset(&sigset);
|
||||
signalaction.sa_handler = &childdied;
|
||||
signalaction.sa_mask = sigset;
|
||||
signalaction.sa_flags = 0;
|
||||
sigaction(SIGCHLD, &signalaction, NULL); /* install signal for dying child */
|
||||
signalaction.sa_handler = &shutdown_gra;
|
||||
signalaction.sa_mask = sigset;
|
||||
signalaction.sa_flags = 0;
|
||||
sigaction(SIGTERM, &signalaction, NULL); /* install Signal for termination */
|
||||
sigaction(SIGINT, &signalaction, NULL); /* install Signal for termination */
|
||||
|
||||
while ((opt = getopt(argc, argv, "m:v:i:e:p:?")) != -1) {
|
||||
|
||||
switch (opt) {
|
||||
case 'm':
|
||||
i = sscanf(optarg, "%x,%x,%x,%x,%x,%x",
|
||||
&mask[0], &mask[1], &mask[2],
|
||||
&mask[3], &mask[4], &mask[5]);
|
||||
if (i > currmax)
|
||||
currmax = i;
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
i = sscanf(optarg, "%x,%x,%x,%x,%x,%x",
|
||||
&value[0], &value[1], &value[2],
|
||||
&value[3], &value[4], &value[5]);
|
||||
if (i > currmax)
|
||||
currmax = i;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
i = sscanf(optarg, "%d,%d,%d,%d,%d,%d",
|
||||
&inv_filter[0], &inv_filter[1], &inv_filter[2],
|
||||
&inv_filter[3], &inv_filter[4], &inv_filter[5]);
|
||||
if (i > currmax)
|
||||
currmax = i;
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
i = sscanf(optarg, "%x,%x,%x,%x,%x,%x",
|
||||
&err_mask[0], &err_mask[1], &err_mask[2],
|
||||
&err_mask[3], &err_mask[4], &err_mask[5]);
|
||||
if (i > currmax)
|
||||
currmax = i;
|
||||
break;
|
||||
case 'p':
|
||||
port = atoi(optarg);
|
||||
break;
|
||||
default:
|
||||
print_usage(basename(argv[0]));
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (optind == argc) {
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/* count in options higher than device count ? */
|
||||
if (optind + currmax > argc) {
|
||||
printf("low count of CAN devices!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
currmax = argc - optind; /* find real number of CAN devices */
|
||||
|
||||
if (currmax > MAXDEV) {
|
||||
printf("More than %d CAN devices!\n", MAXDEV);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
socki = socket(PF_INET, SOCK_STREAM, 0);
|
||||
if (socki < 0) {
|
||||
perror("socket");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
inaddr.sin_family = AF_INET;
|
||||
inaddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
inaddr.sin_port = htons(port);
|
||||
|
||||
while(bind(socki, (struct sockaddr*)&inaddr, sizeof(inaddr)) < 0) {
|
||||
struct timespec f = {
|
||||
.tv_nsec = 100 * 1000 * 1000,
|
||||
};
|
||||
|
||||
printf(".");fflush(NULL);
|
||||
nanosleep(&f, NULL);
|
||||
}
|
||||
|
||||
if (listen(socki, 3) != 0) {
|
||||
perror("listen");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
while(1) {
|
||||
accsocket = accept(socki, (struct sockaddr*)&clientaddr, &sin_size);
|
||||
if (accsocket > 0) {
|
||||
//printf("accepted\n");
|
||||
if (!fork())
|
||||
break;
|
||||
close(accsocket);
|
||||
}
|
||||
else if (errno != EINTR) {
|
||||
perror("accept");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
for (i=0; i<currmax; i++) {
|
||||
|
||||
pr_debug("open %d '%s' m%08X v%08X i%d e%d.\n",
|
||||
i, argv[optind+i], mask[i], value[i],
|
||||
inv_filter[i], err_mask[i]);
|
||||
|
||||
if ((s[i] = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
|
||||
perror("socket");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (mask[i] || value[i]) {
|
||||
|
||||
printf("CAN ID filter[%d] for %s set to "
|
||||
"mask = %08X, value = %08X %s\n",
|
||||
i, argv[optind+i], mask[i], value[i],
|
||||
(inv_filter[i]) ? "(inv_filter)" : "");
|
||||
|
||||
rfilter.can_id = value[i];
|
||||
rfilter.can_mask = mask[i];
|
||||
if (inv_filter[i])
|
||||
rfilter.can_id |= CAN_INV_FILTER;
|
||||
|
||||
setsockopt(s[i], SOL_CAN_RAW, CAN_RAW_FILTER,
|
||||
&rfilter, sizeof(rfilter));
|
||||
}
|
||||
|
||||
if (err_mask[i])
|
||||
setsockopt(s[i], SOL_CAN_RAW, CAN_RAW_ERR_FILTER,
|
||||
&err_mask[i], sizeof(err_mask[i]));
|
||||
|
||||
/* try to switch the socket into CAN FD mode */
|
||||
setsockopt(s[i], SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &canfx_on, sizeof(canfx_on));
|
||||
|
||||
/* try to switch the socket into CAN XL mode */
|
||||
setsockopt(s[i], SOL_CAN_RAW, CAN_RAW_XL_FRAMES, &canfx_on, sizeof(canfx_on));
|
||||
|
||||
/* try to enable the CAN XL VCID pass through mode */
|
||||
setsockopt(s[i], SOL_CAN_RAW, CAN_RAW_XL_VCID_OPTS, &vcid_opts, sizeof(vcid_opts));
|
||||
|
||||
j = strlen(argv[optind+i]);
|
||||
|
||||
if (!(j < IFNAMSIZ)) {
|
||||
printf("name of CAN device '%s' is too long!\n", argv[optind+i]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (j > max_devname_len)
|
||||
max_devname_len = j; /* for nice printing */
|
||||
|
||||
addr.can_family = AF_CAN;
|
||||
|
||||
if (strcmp(ANYDEV, argv[optind + i]) != 0) {
|
||||
strcpy(ifr.ifr_name, argv[optind+i]);
|
||||
if (ioctl(s[i], SIOCGIFINDEX, &ifr) < 0) {
|
||||
perror("SIOCGIFINDEX");
|
||||
exit(1);
|
||||
}
|
||||
addr.can_ifindex = ifr.ifr_ifindex;
|
||||
} else
|
||||
addr.can_ifindex = 0; /* any can interface */
|
||||
|
||||
if (bind(s[i], (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror("bindcan");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
while (running) {
|
||||
|
||||
FD_ZERO(&rdfs);
|
||||
for (i=0; i<currmax; i++)
|
||||
FD_SET(s[i], &rdfs);
|
||||
|
||||
if ((ret = select(s[currmax-1]+1, &rdfs, NULL, NULL, NULL)) < 0) {
|
||||
//perror("select");
|
||||
running = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (i=0; i<currmax; i++) { /* check all CAN RAW sockets */
|
||||
|
||||
if (FD_ISSET(s[i], &rdfs)) {
|
||||
|
||||
socklen_t len = sizeof(addr);
|
||||
int idx;
|
||||
|
||||
if ((nbytes = recvfrom(s[i], &cu, sizeof(cu), 0,
|
||||
(struct sockaddr*)&addr, &len)) < 0) {
|
||||
perror("read");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (nbytes < (int)CANXL_HDR_SIZE + CANXL_MIN_DLEN) {
|
||||
fprintf(stderr, "read: no CAN frame\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (cu.xl.flags & CANXL_XLF) {
|
||||
if (nbytes != (int)CANXL_HDR_SIZE + cu.xl.len) {
|
||||
printf("nbytes = %d\n", nbytes);
|
||||
fprintf(stderr, "read: no CAN XL frame\n");
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
/* mark dual-use struct canfd_frame */
|
||||
if (nbytes == CAN_MTU) {
|
||||
cu.fd.flags = 0;
|
||||
} else if (nbytes == CANFD_MTU) {
|
||||
cu.fd.flags |= CANFD_FDF;
|
||||
} else {
|
||||
fprintf(stderr, "read: incomplete CAN CC/FD frame\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (ioctl(s[i], SIOCGSTAMP, &tv) < 0)
|
||||
perror("SIOCGSTAMP");
|
||||
|
||||
|
||||
idx = idx2dindex(addr.can_ifindex, s[i]);
|
||||
|
||||
sprintf(afrbuf, "(%llu.%06llu) %*s ",
|
||||
(unsigned long long)tv.tv_sec, (unsigned long long)tv.tv_usec, max_devname_len, devname[idx]);
|
||||
snprintf_canframe(afrbuf + strlen(afrbuf), sizeof(afrbuf) - strlen(afrbuf), &cu, 0);
|
||||
strcat(afrbuf, "\n");
|
||||
|
||||
if (write(accsocket, afrbuf, strlen(afrbuf)) < 0) {
|
||||
perror("writeaccsock");
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if 0
|
||||
/* print CAN frame in log file style to stdout */
|
||||
printf("%s", afrbuf);
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for (i=0; i<currmax; i++)
|
||||
close(s[i]);
|
||||
|
||||
close(accsocket);
|
||||
|
||||
if (signal_num)
|
||||
return 128 + signal_num;
|
||||
|
||||
return 0;
|
||||
}
|
||||
592
Devices/Libraries/Systems/can-utils/canplayer.c
Executable file
592
Devices/Libraries/Systems/can-utils/canplayer.c
Executable file
@@ -0,0 +1,592 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
|
||||
/*
|
||||
* canplayer.c - replay a compact CAN frame logfile to CAN devices
|
||||
*
|
||||
* Copyright (c) 2002-2007 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* Send feedback to <linux-can@vger.kernel.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <libgen.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/raw.h>
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#define DEFAULT_GAP 1 /* ms */
|
||||
#define DEFAULT_LOOPS 1 /* only one replay */
|
||||
#define CHANNELS 20 /* anyone using more than 20 CAN interfaces at a time? */
|
||||
#define STDOUTIDX 65536 /* interface index for printing on stdout - bigger than max uint16 */
|
||||
|
||||
#if (IFNAMSIZ != 16)
|
||||
#error "IFNAMSIZ value does not to DEVSZ calculation!"
|
||||
#endif
|
||||
|
||||
#define DEVSZ 22 /* IFNAMSZ + 6 */
|
||||
#define TIMESZ sizeof("(1345212884.318850) ")
|
||||
#define BUFSZ (TIMESZ + DEVSZ + AFRSZ)
|
||||
|
||||
/* adapt sscanf() functions below on error */
|
||||
#if (AFRSZ != 6300)
|
||||
#error "AFRSZ value does not fit sscanf restrictions!"
|
||||
#endif
|
||||
#if (DEVSZ != 22)
|
||||
#error "DEVSZ value does not fit sscanf restrictions!"
|
||||
#endif
|
||||
|
||||
struct assignment {
|
||||
char txif[IFNAMSIZ];
|
||||
int txifidx;
|
||||
char rxif[IFNAMSIZ];
|
||||
};
|
||||
static struct assignment asgn[CHANNELS];
|
||||
const int canfx_on = 1;
|
||||
|
||||
extern int optind, opterr, optopt;
|
||||
|
||||
static void print_usage(char *prg)
|
||||
{
|
||||
fprintf(stderr, "%s - replay a compact CAN frame logfile to CAN devices.\n", prg);
|
||||
fprintf(stderr, "\nUsage: %s <options> [interface assignment]*\n\n", prg);
|
||||
fprintf(stderr, "Options:\n");
|
||||
fprintf(stderr, " -I <infile> (default stdin)\n");
|
||||
fprintf(stderr,
|
||||
" -l <num> "
|
||||
"(process input file <num> times)\n"
|
||||
" "
|
||||
"(Use 'i' for infinite loop - default: %d)\n",
|
||||
DEFAULT_LOOPS);
|
||||
fprintf(stderr, " -t (ignore timestamps: "
|
||||
"send frames immediately)\n");
|
||||
fprintf(stderr, " -i (interactive - wait "
|
||||
"for ENTER key to process next frame)\n");
|
||||
fprintf(stderr, " -n <count> (terminate after "
|
||||
"processing <count> CAN frames)\n");
|
||||
fprintf(stderr,
|
||||
" -g <ms> (gap in milli "
|
||||
"seconds - default: %d ms)\n",
|
||||
DEFAULT_GAP);
|
||||
fprintf(stderr, " -s <s> (skip gaps in "
|
||||
"timestamps > 's' seconds)\n");
|
||||
fprintf(stderr, " -x (disable local "
|
||||
"loopback of sent CAN frames)\n");
|
||||
fprintf(stderr, " -v (verbose: print "
|
||||
"sent CAN frames)\n");
|
||||
fprintf(stderr, " -h (show "
|
||||
"this help message)\n\n");
|
||||
fprintf(stderr, "Interface assignment:\n");
|
||||
fprintf(stderr, " 0..n assignments like <write-if>=<log-if>\n\n");
|
||||
fprintf(stderr, " e.g. vcan2=can0 (send frames received from can0 on "
|
||||
"vcan2)\n");
|
||||
fprintf(stderr, " extra hook: stdout=can0 (print logfile line marked with can0 on "
|
||||
"stdout)\n");
|
||||
fprintf(stderr, " No assignments => send frames to the interface(s) they "
|
||||
"had been received from\n\n");
|
||||
fprintf(stderr, "Lines in the logfile not beginning with '(' (start of "
|
||||
"timestamp) are ignored.\n\n");
|
||||
}
|
||||
|
||||
/* copied from /usr/src/linux/include/linux/time.h ...
|
||||
* lhs < rhs: return <0
|
||||
* lhs == rhs: return 0
|
||||
* lhs > rhs: return >0
|
||||
*/
|
||||
static inline int timeval_compare(struct timeval *lhs, struct timeval *rhs)
|
||||
{
|
||||
if (lhs->tv_sec < rhs->tv_sec)
|
||||
return -1;
|
||||
if (lhs->tv_sec > rhs->tv_sec)
|
||||
return 1;
|
||||
return lhs->tv_usec - rhs->tv_usec;
|
||||
}
|
||||
|
||||
static inline void create_diff_tv(struct timeval *today, struct timeval *diff, struct timeval *log)
|
||||
{
|
||||
/* create diff_tv so that log_tv + diff_tv = today_tv */
|
||||
diff->tv_sec = today->tv_sec - log->tv_sec;
|
||||
diff->tv_usec = today->tv_usec - log->tv_usec;
|
||||
}
|
||||
|
||||
static inline int frames_to_send(struct timeval *today, struct timeval *diff, struct timeval *log)
|
||||
{
|
||||
/* return value <0 when log + diff < today */
|
||||
|
||||
struct timeval cmp;
|
||||
|
||||
cmp.tv_sec = log->tv_sec + diff->tv_sec;
|
||||
cmp.tv_usec = log->tv_usec + diff->tv_usec;
|
||||
|
||||
if (cmp.tv_usec >= 1000000) {
|
||||
cmp.tv_usec -= 1000000;
|
||||
cmp.tv_sec++;
|
||||
}
|
||||
|
||||
if (cmp.tv_usec < 0) {
|
||||
cmp.tv_usec += 1000000;
|
||||
cmp.tv_sec--;
|
||||
}
|
||||
|
||||
return timeval_compare(&cmp, today);
|
||||
}
|
||||
|
||||
static int get_txidx(char *logif_name)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < CHANNELS; i++) {
|
||||
if (asgn[i].rxif[0] == 0) /* end of table content */
|
||||
break;
|
||||
if (strcmp(asgn[i].rxif, logif_name) == 0) /* found device name */
|
||||
break;
|
||||
}
|
||||
|
||||
if ((i == CHANNELS) || (asgn[i].rxif[0] == 0))
|
||||
return 0; /* not found */
|
||||
|
||||
return asgn[i].txifidx; /* return interface index */
|
||||
}
|
||||
|
||||
static char *get_txname(char *logif_name)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < CHANNELS; i++) {
|
||||
if (asgn[i].rxif[0] == 0) /* end of table content */
|
||||
break;
|
||||
if (strcmp(asgn[i].rxif, logif_name) == 0) /* found device name */
|
||||
break;
|
||||
}
|
||||
|
||||
if ((i == CHANNELS) || (asgn[i].rxif[0] == 0))
|
||||
return 0; /* not found */
|
||||
|
||||
return asgn[i].txif; /* return interface name */
|
||||
}
|
||||
|
||||
static int add_assignment(char *mode, int socket, char *txname,
|
||||
char *rxname, int verbose)
|
||||
{
|
||||
struct ifreq ifr;
|
||||
int i;
|
||||
|
||||
/* find free entry */
|
||||
for (i = 0; i < CHANNELS; i++) {
|
||||
if (asgn[i].txif[0] == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == CHANNELS) {
|
||||
fprintf(stderr, "Assignment table exceeded!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strlen(txname) >= IFNAMSIZ) {
|
||||
fprintf(stderr, "write-if interface name '%s' too long!", txname);
|
||||
return 1;
|
||||
}
|
||||
strcpy(asgn[i].txif, txname);
|
||||
|
||||
if (strlen(rxname) >= IFNAMSIZ) {
|
||||
fprintf(stderr, "log-if interface name '%s' too long!", rxname);
|
||||
return 1;
|
||||
}
|
||||
strcpy(asgn[i].rxif, rxname);
|
||||
|
||||
if (strcmp(txname, "stdout") != 0) {
|
||||
strcpy(ifr.ifr_name, txname);
|
||||
if (ioctl(socket, SIOCGIFINDEX, &ifr) < 0) {
|
||||
perror("SIOCGIFINDEX");
|
||||
fprintf(stderr, "write-if interface name '%s' is wrong!\n", txname);
|
||||
return 1;
|
||||
}
|
||||
asgn[i].txifidx = ifr.ifr_ifindex;
|
||||
} else
|
||||
asgn[i].txifidx = STDOUTIDX;
|
||||
|
||||
if (verbose > 1) /* use -v -v to see this */
|
||||
printf("added %s assignment: log-if=%s write-if=%s write-if-idx=%d\n", mode, asgn[i].rxif, asgn[i].txif, asgn[i].txifidx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
static char buf[BUFSZ], device[DEVSZ], afrbuf[AFRSZ];
|
||||
struct sockaddr_can addr;
|
||||
struct can_raw_vcid_options vcid_opts = {
|
||||
.flags = CAN_RAW_XL_VCID_TX_PASS,
|
||||
};
|
||||
static cu_t cu;
|
||||
static struct timeval today_tv, log_tv, last_log_tv, diff_tv;
|
||||
struct timespec sleep_ts;
|
||||
int s; /* CAN_RAW socket */
|
||||
FILE *infile = stdin;
|
||||
unsigned long gap = DEFAULT_GAP;
|
||||
int use_timestamps = 1;
|
||||
int interactive = 0; /* wait for ENTER keypress to process next frame */
|
||||
int count = 0; /* end replay after sending count frames. 0 = disabled */
|
||||
static int verbose, opt, delay_loops;
|
||||
static unsigned long skipgap;
|
||||
static int loopback_disable = 0;
|
||||
static int infinite_loops = 0;
|
||||
static int loops = DEFAULT_LOOPS;
|
||||
int assignments; /* assignments defined on the commandline */
|
||||
int txidx; /* sendto() interface index */
|
||||
int eof, txmtu, i, j;
|
||||
char *fret;
|
||||
unsigned long long sec, usec;
|
||||
|
||||
while ((opt = getopt(argc, argv, "I:l:tin:g:s:xvh")) != -1) {
|
||||
switch (opt) {
|
||||
case 'I':
|
||||
infile = fopen(optarg, "r");
|
||||
if (!infile) {
|
||||
perror("infile");
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
if (optarg[0] == 'i')
|
||||
infinite_loops = 1;
|
||||
else if (!(loops = atoi(optarg))) {
|
||||
fprintf(stderr, "Invalid argument for option -l !\n");
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 't':
|
||||
use_timestamps = 0;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
interactive = 1;
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
count = atoi(optarg);
|
||||
if (count < 1) {
|
||||
print_usage(basename(argv[0]));
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
gap = strtoul(optarg, NULL, 10);
|
||||
break;
|
||||
|
||||
case 's':
|
||||
skipgap = strtoul(optarg, NULL, 10);
|
||||
if (skipgap < 1) {
|
||||
fprintf(stderr, "Invalid argument for option -s !\n");
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
loopback_disable = 1;
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
verbose++;
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
print_usage(basename(argv[0]));
|
||||
exit(EXIT_SUCCESS);
|
||||
break;
|
||||
|
||||
default:
|
||||
print_usage(basename(argv[0]));
|
||||
exit(EXIT_FAILURE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assignments = argc - optind; /* find real number of user assignments */
|
||||
|
||||
if (infile == stdin) { /* no jokes with stdin */
|
||||
infinite_loops = 0;
|
||||
loops = 1;
|
||||
}
|
||||
|
||||
if (verbose > 1) { /* use -v -v to see this */
|
||||
if (infinite_loops)
|
||||
printf("infinite_loops\n");
|
||||
else
|
||||
printf("%d loops\n", loops);
|
||||
}
|
||||
|
||||
/* ignore timestamps from logfile when in single step keypress mode */
|
||||
if (interactive) {
|
||||
use_timestamps = 0;
|
||||
printf("interactive mode: press ENTER to process next CAN frame ...\n");
|
||||
}
|
||||
|
||||
sleep_ts.tv_sec = gap / 1000;
|
||||
sleep_ts.tv_nsec = (gap % 1000) * 1000000;
|
||||
|
||||
/* open socket */
|
||||
if ((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
|
||||
perror("socket");
|
||||
return 1;
|
||||
}
|
||||
|
||||
addr.can_family = AF_CAN;
|
||||
addr.can_ifindex = 0;
|
||||
|
||||
/* disable unneeded default receive filter on this RAW socket */
|
||||
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
|
||||
|
||||
/* try to switch the socket into CAN FD mode */
|
||||
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &canfx_on, sizeof(canfx_on));
|
||||
|
||||
/* try to switch the socket into CAN XL mode */
|
||||
setsockopt(s, SOL_CAN_RAW, CAN_RAW_XL_FRAMES, &canfx_on, sizeof(canfx_on));
|
||||
|
||||
/* try to enable the CAN XL VCID pass through mode */
|
||||
setsockopt(s, SOL_CAN_RAW, CAN_RAW_XL_VCID_OPTS, &vcid_opts, sizeof(vcid_opts));
|
||||
|
||||
if (loopback_disable) {
|
||||
int loopback = 0;
|
||||
|
||||
setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));
|
||||
}
|
||||
|
||||
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror("bind");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (assignments) {
|
||||
/* add & check user assignments from commandline */
|
||||
for (i = 0; i < assignments; i++) {
|
||||
if (strlen(argv[optind + i]) >= BUFSZ) {
|
||||
fprintf(stderr, "Assignment too long!\n");
|
||||
print_usage(basename(argv[0]));
|
||||
return 1;
|
||||
}
|
||||
strcpy(buf, argv[optind + i]);
|
||||
for (j = 0; j < (int)BUFSZ; j++) { /* find '=' in assignment */
|
||||
if (buf[j] == '=')
|
||||
break;
|
||||
}
|
||||
if ((j == BUFSZ) || (buf[j] != '=')) {
|
||||
fprintf(stderr, "'=' missing in assignment!\n");
|
||||
print_usage(basename(argv[0]));
|
||||
return 1;
|
||||
}
|
||||
buf[j] = 0; /* cut string in two pieces */
|
||||
if (add_assignment("user", s, &buf[0], &buf[j + 1], verbose))
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
while (infinite_loops || loops--) {
|
||||
if (infile != stdin)
|
||||
rewind(infile); /* for each loop */
|
||||
|
||||
if (verbose > 1) /* use -v -v to see this */
|
||||
printf(">>>>>>>>> start reading file. remaining loops = %d\n", loops);
|
||||
|
||||
/* read first non-comment frame from logfile */
|
||||
while ((fret = fgets(buf, BUFSZ - 1, infile)) != NULL && buf[0] != '(') {
|
||||
if (strlen(buf) >= BUFSZ - 2) {
|
||||
fprintf(stderr, "comment line too long for input buffer\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fret)
|
||||
goto out; /* nothing to read */
|
||||
|
||||
eof = 0;
|
||||
|
||||
if (sscanf(buf, "(%llu.%llu) %21s %6299s", &sec, &usec, device, afrbuf) != 4) {
|
||||
fprintf(stderr, "incorrect line format in logfile\n");
|
||||
return 1;
|
||||
}
|
||||
log_tv.tv_sec = sec;
|
||||
log_tv.tv_usec = usec;
|
||||
|
||||
/*
|
||||
* ensure the fractions of seconds are 6 decimal places long to catch
|
||||
* 3rd party or handcrafted logfiles that treat the timestamp as float
|
||||
*/
|
||||
if (strchr(buf, ')') - strchr(buf, '.') != 7) {
|
||||
fprintf(stderr, "timestamp format in logfile requires 6 decimal places\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (use_timestamps) { /* throttle sending due to logfile timestamps */
|
||||
|
||||
gettimeofday(&today_tv, NULL);
|
||||
create_diff_tv(&today_tv, &diff_tv, &log_tv);
|
||||
last_log_tv = log_tv;
|
||||
}
|
||||
|
||||
while (!eof) {
|
||||
while ((!use_timestamps) || (frames_to_send(&today_tv, &diff_tv, &log_tv) < 0)) {
|
||||
/* wait for keypress to process next frame */
|
||||
if (interactive)
|
||||
getchar();
|
||||
|
||||
/* log_tv/device/afrbuf are valid here */
|
||||
|
||||
if (strlen(device) >= IFNAMSIZ) {
|
||||
fprintf(stderr, "log interface name '%s' too long!", device);
|
||||
return 1;
|
||||
}
|
||||
|
||||
txidx = get_txidx(device); /* get ifindex for sending the frame */
|
||||
|
||||
if ((!txidx) && (!assignments)) {
|
||||
/* ifindex not found and no user assignments */
|
||||
/* => assign this device automatically */
|
||||
if (add_assignment("auto", s, device, device, verbose))
|
||||
return 1;
|
||||
txidx = get_txidx(device);
|
||||
}
|
||||
|
||||
if (txidx == STDOUTIDX) { /* hook to print logfile lines on stdout */
|
||||
|
||||
printf("%s", buf); /* print the line AS-IS without extra \n */
|
||||
fflush(stdout);
|
||||
|
||||
} else if (txidx > 0) { /* only send to valid CAN devices */
|
||||
|
||||
txmtu = parse_canframe(afrbuf, &cu); /* dual-use frame */
|
||||
if (!txmtu) {
|
||||
fprintf(stderr, "wrong CAN frame format: '%s'!", afrbuf);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* CAN XL frames need real frame length for sending */
|
||||
if (txmtu == CANXL_MTU)
|
||||
txmtu = CANXL_HDR_SIZE + cu.xl.len;
|
||||
|
||||
addr.can_family = AF_CAN;
|
||||
addr.can_ifindex = txidx; /* send via this interface */
|
||||
|
||||
if (sendto(s, &cu, txmtu, 0, (struct sockaddr *)&addr, sizeof(addr)) != txmtu) {
|
||||
perror("sendto");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
printf("%s (%s) ", get_txname(device), device);
|
||||
snprintf_long_canframe(afrbuf, sizeof(afrbuf), &cu, CANLIB_VIEW_INDENT_SFF);
|
||||
printf("%s\n", afrbuf);
|
||||
}
|
||||
|
||||
if (count && (--count == 0))
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* read next non-comment frame from logfile */
|
||||
while ((fret = fgets(buf, BUFSZ - 1, infile)) != NULL && buf[0] != '(') {
|
||||
if (strlen(buf) >= BUFSZ - 2) {
|
||||
fprintf(stderr, "comment line too long for input buffer\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fret) {
|
||||
eof = 1; /* this file is completely processed */
|
||||
break;
|
||||
}
|
||||
|
||||
if (sscanf(buf, "(%llu.%llu) %s %s", &sec, &usec, device, afrbuf) != 4) {
|
||||
fprintf(stderr, "incorrect line format in logfile\n");
|
||||
return 1;
|
||||
}
|
||||
log_tv.tv_sec = sec;
|
||||
log_tv.tv_usec = usec;
|
||||
|
||||
/*
|
||||
* ensure the fractions of seconds are 6 decimal places long to catch
|
||||
* 3rd party or handcrafted logfiles that treat the timestamp as float
|
||||
*/
|
||||
if (strchr(buf, ')') - strchr(buf, '.') != 7) {
|
||||
fprintf(stderr, "timestamp format in logfile requires 6 decimal places\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (use_timestamps) {
|
||||
gettimeofday(&today_tv, NULL);
|
||||
|
||||
/* test for logfile timestamps jumping backwards OR */
|
||||
/* if the user likes to skip long gaps in the timestamps */
|
||||
if ((last_log_tv.tv_sec > log_tv.tv_sec) || (skipgap && labs(last_log_tv.tv_sec - log_tv.tv_sec) > (long)skipgap))
|
||||
create_diff_tv(&today_tv, &diff_tv, &log_tv);
|
||||
|
||||
last_log_tv = log_tv;
|
||||
}
|
||||
|
||||
} /* while frames_to_send ... */
|
||||
|
||||
if (nanosleep(&sleep_ts, NULL))
|
||||
return 1;
|
||||
|
||||
delay_loops++; /* private statistics */
|
||||
gettimeofday(&today_tv, NULL);
|
||||
|
||||
} /* while (!eof) */
|
||||
|
||||
} /* while (infinite_loops || loops--) */
|
||||
|
||||
out:
|
||||
|
||||
close(s);
|
||||
fclose(infile);
|
||||
|
||||
if (verbose > 1) /* use -v -v to see this */
|
||||
printf("%d delay_loops\n", delay_loops);
|
||||
|
||||
return 0;
|
||||
}
|
||||
214
Devices/Libraries/Systems/can-utils/cansend.c
Executable file
214
Devices/Libraries/Systems/can-utils/cansend.c
Executable file
@@ -0,0 +1,214 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
|
||||
/*
|
||||
* cansend.c - send CAN-frames via CAN_RAW sockets
|
||||
*
|
||||
* Copyright (c) 2002-2007 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* Send feedback to <linux-can@vger.kernel.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/raw.h>
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
static void print_usage(char *prg)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"%s - send CAN-frames via CAN_RAW sockets.\n"
|
||||
"\n"
|
||||
"Usage: %s <device> <can_frame>.\n"
|
||||
"\n"
|
||||
"<can_frame>:\n"
|
||||
" <can_id>#{data} for CAN CC (Classical CAN 2.0B) data frames\n"
|
||||
" <can_id>#R{len} for CAN CC (Classical CAN 2.0B) data frames\n"
|
||||
" <can_id>#{data}_{dlc} for CAN CC (Classical CAN 2.0B) data frames\n"
|
||||
" <can_id>#R{len}_{dlc} for CAN CC (Classical CAN 2.0B) data frames\n"
|
||||
" <can_id>##<flags>{data} for CAN FD frames\n"
|
||||
" <vcid><prio>#<flags>:<sdt>:<af>#<data> for CAN XL frames\n"
|
||||
"\n"
|
||||
"<can_id>:\n"
|
||||
" 3 (SFF) or 8 (EFF) hex chars\n"
|
||||
"{data}:\n"
|
||||
" 0..8 (0..64 CAN FD) ASCII hex-values (optionally separated by '.')\n"
|
||||
"{len}:\n"
|
||||
" an optional 0..8 value as RTR frames can contain a valid dlc field\n"
|
||||
"_{dlc}:\n"
|
||||
" an optional 9..F data length code value when payload length is 8\n"
|
||||
"<flags>:\n"
|
||||
" a single ASCII Hex value (0 .. F) which defines canfd_frame.flags\n"
|
||||
"\n"
|
||||
"<vcid>:\n"
|
||||
" 2 hex chars - virtual CAN network identifier (00 .. FF)\n"
|
||||
"<prio>:\n"
|
||||
" 3 hex chars - 11 bit priority value (000 .. 7FF)\n"
|
||||
"<flags>:\n"
|
||||
" 2 hex chars values (00 .. FF) which defines canxl_frame.flags\n"
|
||||
"<sdt>:\n"
|
||||
" 2 hex chars values (00 .. FF) which defines canxl_frame.sdt\n"
|
||||
"<af>:\n"
|
||||
" 8 hex chars - 32 bit acceptance field (canxl_frame.af)\n"
|
||||
"<data>:\n"
|
||||
" 1..2048 ASCII hex-values (optionally separated by '.')\n"
|
||||
"\n"
|
||||
"Examples:\n"
|
||||
" 5A1#11.2233.44556677.88 / 123#DEADBEEF / 5AA# / 123##1 / 213##311223344 /\n"
|
||||
" 1F334455#1122334455667788_B / 123#R / 00000123#R3 / 333#R8_E /\n"
|
||||
" 45123#81:00:12345678#11223344.556677 / 00242#81:07:40000123#112233\n"
|
||||
"\n",
|
||||
prg, prg);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int s; /* can raw socket */
|
||||
int required_mtu;
|
||||
int mtu;
|
||||
int enable_canfx = 1;
|
||||
struct sockaddr_can addr;
|
||||
struct can_raw_vcid_options vcid_opts = {
|
||||
.flags = CAN_RAW_XL_VCID_TX_PASS,
|
||||
};
|
||||
static cu_t cu;
|
||||
struct ifreq ifr;
|
||||
|
||||
/* check command line options */
|
||||
if (argc != 3) {
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* parse CAN frame */
|
||||
required_mtu = parse_canframe(argv[2], &cu);
|
||||
if (!required_mtu) {
|
||||
fprintf(stderr, "\nWrong CAN-frame format!\n\n");
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* open socket */
|
||||
if ((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
|
||||
perror("socket");
|
||||
return 1;
|
||||
}
|
||||
|
||||
strncpy(ifr.ifr_name, argv[1], IFNAMSIZ - 1);
|
||||
ifr.ifr_name[IFNAMSIZ - 1] = '\0';
|
||||
ifr.ifr_ifindex = if_nametoindex(ifr.ifr_name);
|
||||
if (!ifr.ifr_ifindex) {
|
||||
perror("if_nametoindex");
|
||||
return 1;
|
||||
}
|
||||
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.can_family = AF_CAN;
|
||||
addr.can_ifindex = ifr.ifr_ifindex;
|
||||
|
||||
if (required_mtu > (int)CAN_MTU) {
|
||||
/* check if the frame fits into the CAN netdevice */
|
||||
if (ioctl(s, SIOCGIFMTU, &ifr) < 0) {
|
||||
perror("SIOCGIFMTU");
|
||||
return 1;
|
||||
}
|
||||
mtu = ifr.ifr_mtu;
|
||||
|
||||
if (mtu == (int)CANFD_MTU) {
|
||||
/* interface is ok - try to switch the socket into CAN FD mode */
|
||||
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES,
|
||||
&enable_canfx, sizeof(enable_canfx))){
|
||||
printf("error when enabling CAN FD support\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (mtu >= (int)CANXL_MIN_MTU) {
|
||||
/* interface is ok - try to switch the socket into CAN XL mode */
|
||||
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_XL_FRAMES,
|
||||
&enable_canfx, sizeof(enable_canfx))){
|
||||
printf("error when enabling CAN XL support\n");
|
||||
return 1;
|
||||
}
|
||||
/* try to enable the CAN XL VCID pass through mode */
|
||||
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_XL_VCID_OPTS,
|
||||
&vcid_opts, sizeof(vcid_opts))) {
|
||||
printf("error when enabling CAN XL VCID pass through\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ensure discrete CAN FD length values 0..8, 12, 16, 20, 24, 32, 64 */
|
||||
if (required_mtu == CANFD_MTU)
|
||||
cu.fd.len = can_fd_dlc2len(can_fd_len2dlc(cu.fd.len));
|
||||
|
||||
/* CAN XL frames need real frame length for sending */
|
||||
if (required_mtu == CANXL_MTU)
|
||||
required_mtu = CANXL_HDR_SIZE + cu.xl.len;
|
||||
|
||||
/*
|
||||
* disable default receive filter on this RAW socket This is
|
||||
* obsolete as we do not read from the socket at all, but for
|
||||
* this reason we can remove the receive list in the Kernel to
|
||||
* save a little (really a very little!) CPU usage.
|
||||
*/
|
||||
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0);
|
||||
|
||||
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror("bind");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* send frame */
|
||||
if (write(s, &cu, required_mtu) != required_mtu) {
|
||||
perror("write");
|
||||
return 1;
|
||||
}
|
||||
|
||||
close(s);
|
||||
|
||||
return 0;
|
||||
}
|
||||
446
Devices/Libraries/Systems/can-utils/cansequence.c
Executable file
446
Devices/Libraries/Systems/can-utils/cansequence.c
Executable file
@@ -0,0 +1,446 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
// Copyright (c) 2007, 2008, 2009, 2010, 2014, 2015, 2019, 2023 Pengutronix,
|
||||
// Marc Kleine-Budde <kernel@pengutronix.de>
|
||||
// Copyright (c) 2005 Pengutronix,
|
||||
// Sascha Hauer <kernel@pengutronix.de>
|
||||
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <libgen.h>
|
||||
#include <limits.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <net/if.h>
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/raw.h>
|
||||
|
||||
#define CAN_ID_DEFAULT (2)
|
||||
#define ANYDEV "any" /* name of interface to receive from any CAN interface */
|
||||
|
||||
extern int optind, opterr, optopt;
|
||||
|
||||
static int s = -1;
|
||||
static bool running = true;
|
||||
static volatile sig_atomic_t signal_num;
|
||||
static bool infinite = true;
|
||||
static bool canfd = false;
|
||||
static bool canfd_strict = false;
|
||||
static unsigned int drop_until_quit;
|
||||
static unsigned int drop_count;
|
||||
static bool use_poll = false;
|
||||
|
||||
static unsigned int loopcount = 1;
|
||||
static int verbose;
|
||||
|
||||
static struct canfd_frame frame = {
|
||||
.len = 1,
|
||||
};
|
||||
static struct can_filter filter[] = {
|
||||
{
|
||||
.can_id = CAN_ID_DEFAULT,
|
||||
},
|
||||
};
|
||||
|
||||
static void print_usage(char *prg)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"Usage: %s [<can-interface>] [Options]\n"
|
||||
"\n"
|
||||
"cansequence sends CAN messages with a rising sequence number as payload.\n"
|
||||
"When the -r option is given, cansequence expects to receive these messages\n"
|
||||
"and prints an error message if a wrong sequence number is encountered.\n"
|
||||
"The main purpose of this program is to test the reliability of CAN links.\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
" -e, --extended send/receive extended frames\n"
|
||||
" -f, --canfd send/receive CAN-FD CAN frames\n"
|
||||
" -s, --strict refuse classical CAN frames in CAN-FD mode\n"
|
||||
" -b, --brs send CAN-FD CAN frames with bitrate switch (BRS)\n"
|
||||
" -i, --identifier=ID CAN Identifier (default = %u)\n"
|
||||
" --loop=COUNT send message COUNT times\n"
|
||||
" -p, --poll use poll(2) to wait for buffer space while sending\n"
|
||||
" -q, --quit <num> quit if <num> wrong sequences are encountered\n"
|
||||
" -r, --receive work as receiver\n"
|
||||
" -v, --verbose be verbose (twice to be even more verbose\n"
|
||||
" -h, --help this help\n",
|
||||
prg, CAN_ID_DEFAULT);
|
||||
}
|
||||
|
||||
static void sig_handler(int signo)
|
||||
{
|
||||
running = false;
|
||||
signal_num = signo;
|
||||
}
|
||||
|
||||
static void do_receive()
|
||||
{
|
||||
uint8_t ctrlmsg[CMSG_SPACE(sizeof(struct timeval)) + CMSG_SPACE(sizeof(__u32))];
|
||||
struct iovec iov = {
|
||||
.iov_base = &frame,
|
||||
};
|
||||
struct msghdr msg = {
|
||||
.msg_iov = &iov,
|
||||
.msg_iovlen = 1,
|
||||
.msg_control = &ctrlmsg,
|
||||
};
|
||||
const int dropmonitor_on = 1;
|
||||
bool sequence_init = true;
|
||||
unsigned int sequence_wrap = 0;
|
||||
uint32_t sequence_mask = 0xff;
|
||||
uint32_t sequence_rx = 0;
|
||||
uint8_t sequence_delta = 0;
|
||||
uint32_t sequence = 0;
|
||||
unsigned int overflow_old = 0;
|
||||
can_err_mask_t err_mask = CAN_ERR_MASK;
|
||||
size_t mtu;
|
||||
|
||||
if (canfd)
|
||||
mtu = CANFD_MTU;
|
||||
else
|
||||
mtu = CAN_MTU;
|
||||
|
||||
if (setsockopt(s, SOL_SOCKET, SO_RXQ_OVFL,
|
||||
&dropmonitor_on, sizeof(dropmonitor_on)) < 0) {
|
||||
perror("setsockopt() SO_RXQ_OVFL not supported by your Linux Kernel");
|
||||
}
|
||||
|
||||
/* enable recv. of error messages */
|
||||
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &err_mask, sizeof(err_mask))) {
|
||||
perror("setsockopt()");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* enable recv. now */
|
||||
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, filter, sizeof(filter))) {
|
||||
perror("setsockopt()");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
while ((infinite || loopcount--) && running) {
|
||||
ssize_t nbytes;
|
||||
|
||||
msg.msg_iov[0].iov_len = mtu;
|
||||
msg.msg_controllen = sizeof(ctrlmsg);
|
||||
msg.msg_flags = 0;
|
||||
|
||||
nbytes = recvmsg(s, &msg, 0);
|
||||
if (nbytes < 0) {
|
||||
perror("recvmsg()");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (frame.can_id & CAN_ERR_FLAG) {
|
||||
fprintf(stderr,
|
||||
"sequence CNT: %6u, ERRORFRAME %7x %02x %02x %02x %02x %02x %02x %02x %02x\n",
|
||||
sequence, frame.can_id,
|
||||
frame.data[0], frame.data[1], frame.data[2], frame.data[3],
|
||||
frame.data[4], frame.data[5], frame.data[6], frame.data[7]);
|
||||
continue;
|
||||
}
|
||||
|
||||
sequence_rx = frame.data[0];
|
||||
|
||||
if (canfd_strict && nbytes == CAN_MTU) {
|
||||
if (verbose > 1)
|
||||
printf("sequence CNT: 0x%07x RX: 0x%02x (ignoring classical CAN frame)\n", sequence, sequence_rx);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sequence_init) {
|
||||
sequence_init = false;
|
||||
sequence = sequence_rx;
|
||||
}
|
||||
|
||||
sequence_delta = sequence_rx - (uint8_t)sequence;
|
||||
if (sequence_delta) {
|
||||
struct cmsghdr *cmsg;
|
||||
uint32_t overflow = 0;
|
||||
uint32_t overflow_delta;
|
||||
|
||||
drop_count++;
|
||||
|
||||
for (cmsg = CMSG_FIRSTHDR(&msg);
|
||||
cmsg && (cmsg->cmsg_level == SOL_SOCKET);
|
||||
cmsg = CMSG_NXTHDR(&msg,cmsg)) {
|
||||
if (cmsg->cmsg_type == SO_RXQ_OVFL) {
|
||||
memcpy(&overflow, CMSG_DATA(cmsg), sizeof(overflow));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
overflow_delta = overflow - overflow_old;
|
||||
|
||||
fprintf(stderr,
|
||||
"sequence CNT: 0x%07x RX: 0x%02x expected: 0x%02x missing: %4u skt overflow delta: %4u absolute: %4u hw - skt: %5d incident: %4u %s%s\n",
|
||||
sequence, sequence_rx, /* CNT, RX */
|
||||
sequence & sequence_mask, sequence_delta, /* expected, missing */
|
||||
overflow_delta, overflow, /* skb overflow delta, absolute */
|
||||
sequence_delta - overflow_delta, /* hw - skb */
|
||||
drop_count, /* incident */
|
||||
overflow_delta ? "[SOCKET]" : "",
|
||||
overflow_delta != sequence_delta ?
|
||||
((overflow_delta - sequence_delta > 0 && (overflow_delta - sequence_delta) & 0xff) ?
|
||||
"[HARDWARE]" : "[HARDWARE?]") : "");
|
||||
|
||||
if (drop_count == drop_until_quit)
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
sequence = sequence_rx;
|
||||
overflow_old = overflow;
|
||||
} else if (verbose > 1) {
|
||||
printf("sequence CNT: 0x%07x RX: 0x%02x\n", sequence, sequence_rx);
|
||||
}
|
||||
|
||||
sequence++;
|
||||
if (verbose && !(sequence & sequence_mask))
|
||||
printf("sequence wrap around (%d)\n", sequence_wrap++);
|
||||
}
|
||||
}
|
||||
|
||||
static void do_send()
|
||||
{
|
||||
unsigned int seq_wrap = 0;
|
||||
uint8_t sequence = 0;
|
||||
size_t mtu;
|
||||
|
||||
if (canfd)
|
||||
mtu = CANFD_MTU;
|
||||
else
|
||||
mtu = CAN_MTU;
|
||||
|
||||
while ((infinite || loopcount--) && running) {
|
||||
ssize_t len;
|
||||
|
||||
if (verbose > 1)
|
||||
printf("sending frame. sequence number: %d\n", sequence);
|
||||
|
||||
again:
|
||||
len = write(s, &frame, mtu);
|
||||
if (len == -1) {
|
||||
switch (errno) {
|
||||
case ENOBUFS: {
|
||||
int err;
|
||||
struct pollfd fds[] = {
|
||||
{
|
||||
.fd = s,
|
||||
.events = POLLOUT,
|
||||
},
|
||||
};
|
||||
|
||||
if (!use_poll) {
|
||||
perror("write");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
err = poll(fds, 1, 1000);
|
||||
if (err == 0 || (err == -1 && errno != EINTR)) {
|
||||
perror("poll()");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
case EINTR: /* fallthrough */
|
||||
goto again;
|
||||
default:
|
||||
perror("write");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
frame.data[0]++;
|
||||
sequence++;
|
||||
|
||||
if (verbose && !sequence)
|
||||
printf("sequence wrap around (%d)\n", seq_wrap++);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct sigaction act = {
|
||||
.sa_handler = sig_handler,
|
||||
};
|
||||
struct sockaddr_can addr = {
|
||||
.can_family = AF_CAN,
|
||||
};
|
||||
char *interface = "can0";
|
||||
bool extended = false;
|
||||
bool brs = false;
|
||||
bool receive = false;
|
||||
int opt;
|
||||
|
||||
sigaction(SIGINT, &act, NULL);
|
||||
sigaction(SIGTERM, &act, NULL);
|
||||
sigaction(SIGHUP, &act, NULL);
|
||||
|
||||
struct option long_options[] = {
|
||||
{ "extended", no_argument, 0, 'e' },
|
||||
{ "canfd", no_argument, 0, 'f' },
|
||||
{ "strict", no_argument, 0, 's' },
|
||||
{ "brs", no_argument, 0, 'b' },
|
||||
{ "identifier", required_argument, 0, 'i' },
|
||||
{ "loop", required_argument, 0, 'l' },
|
||||
{ "poll", no_argument, 0, 'p' },
|
||||
{ "quit", optional_argument, 0, 'q' },
|
||||
{ "receive", no_argument, 0, 'r' },
|
||||
{ "verbose", no_argument, 0, 'v' },
|
||||
{ "help", no_argument, 0, 'h' },
|
||||
{ 0, 0, 0, 0 },
|
||||
};
|
||||
|
||||
while ((opt = getopt_long(argc, argv, "efsbi:pq::rvh?", long_options, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case 'e':
|
||||
extended = true;
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
canfd = true;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
canfd_strict = true;
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
brs = true; /* bitrate switch implies CAN-FD */
|
||||
canfd = true;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
filter->can_id = strtoul(optarg, NULL, 0);
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
receive = true;
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
if (optarg) {
|
||||
loopcount = strtoul(optarg, NULL, 0);
|
||||
infinite = false;
|
||||
} else {
|
||||
infinite = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
use_poll = true;
|
||||
break;
|
||||
|
||||
case 'q':
|
||||
if (optarg)
|
||||
drop_until_quit = strtoul(optarg, NULL, 0);
|
||||
else
|
||||
drop_until_quit = 1;
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
verbose++;
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
case '?':
|
||||
print_usage(basename(argv[0]));
|
||||
exit(EXIT_SUCCESS);
|
||||
break;
|
||||
|
||||
default:
|
||||
print_usage(basename(argv[0]));
|
||||
exit(EXIT_FAILURE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (argv[optind] != NULL)
|
||||
interface = argv[optind];
|
||||
|
||||
if (extended) {
|
||||
filter->can_mask = CAN_EFF_MASK;
|
||||
filter->can_id &= CAN_EFF_MASK;
|
||||
filter->can_id |= CAN_EFF_FLAG;
|
||||
} else {
|
||||
filter->can_mask = CAN_SFF_MASK;
|
||||
filter->can_id &= CAN_SFF_MASK;
|
||||
}
|
||||
frame.can_id = filter->can_id;
|
||||
filter->can_mask |= CAN_EFF_FLAG;
|
||||
|
||||
printf("interface = %s\n", interface);
|
||||
|
||||
s = socket(AF_CAN, SOCK_RAW, CAN_RAW);
|
||||
if (s < 0) {
|
||||
perror("socket()");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (strcmp(ANYDEV, interface)) {
|
||||
addr.can_ifindex = if_nametoindex(interface);
|
||||
if (!addr.can_ifindex) {
|
||||
perror("if_nametoindex()");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
/* first don't recv. any msgs */
|
||||
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0)) {
|
||||
perror("setsockopt()");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (canfd) {
|
||||
const int enable_canfd = 1;
|
||||
struct ifreq ifr;
|
||||
|
||||
strncpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));
|
||||
|
||||
/* check if the frame fits into the CAN netdevice */
|
||||
if (ioctl(s, SIOCGIFMTU, &ifr) < 0) {
|
||||
perror("SIOCGIFMTU");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (ifr.ifr_mtu != CANFD_MTU && ifr.ifr_mtu != CANXL_MTU) {
|
||||
printf("CAN interface is only Classical CAN capable - sorry.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* interface is ok - try to switch the socket into CAN FD mode */
|
||||
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &enable_canfd, sizeof(enable_canfd))) {
|
||||
printf("error when enabling CAN FD support\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
canfd_strict = false;
|
||||
}
|
||||
|
||||
if (brs)
|
||||
frame.flags |= CANFD_BRS;
|
||||
|
||||
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror("bind()");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (receive)
|
||||
do_receive();
|
||||
else
|
||||
do_send();
|
||||
|
||||
if (signal_num)
|
||||
return 128 + signal_num;
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
972
Devices/Libraries/Systems/can-utils/cansniffer.c
Executable file
972
Devices/Libraries/Systems/can-utils/cansniffer.c
Executable file
@@ -0,0 +1,972 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
|
||||
/*
|
||||
* cansniffer.c - volatile CAN content visualizer
|
||||
*
|
||||
* Copyright (c) 2002-2007 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* Send feedback to <linux-can@vger.kernel.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <libgen.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/raw.h>
|
||||
#include <linux/sockios.h>
|
||||
|
||||
#include "terminal.h"
|
||||
|
||||
#define SETFNAME "sniffset."
|
||||
#define SETFDFNAME "sniffset_fd."
|
||||
#define FNAME_MAX_LEN 40
|
||||
|
||||
#define ANYDEV "any"
|
||||
#define MAX_SLOTS 2048
|
||||
|
||||
#define CANFD_OFF 0 /* set to OFF */
|
||||
#define CANFD_ON 1 /* set to ON */
|
||||
#define CANFD_AUTO 2 /* unspecified => check for first received frame */
|
||||
|
||||
/* flags */
|
||||
|
||||
#define ENABLE 1 /* by filter or user */
|
||||
#define DISPLAY 2 /* is on the screen */
|
||||
#define UPDATE 4 /* needs to be printed on the screen */
|
||||
#define CLRSCR 8 /* clear screen in next loop */
|
||||
|
||||
/* flags testing & setting */
|
||||
|
||||
#define is_set(id, flag) (sniftab[id].flags & flag)
|
||||
#define is_clr(id, flag) (!(sniftab[id].flags & flag))
|
||||
|
||||
#define do_set(id, flag) (sniftab[id].flags |= flag)
|
||||
#define do_clr(id, flag) (sniftab[id].flags &= ~flag)
|
||||
|
||||
/* time defaults */
|
||||
|
||||
#define TIMEOUT 500 /* in 10ms */
|
||||
#define HOLD 100 /* in 10ms */
|
||||
#define LOOP 20 /* in 10ms */
|
||||
|
||||
#define ATTCOLOR ATTBOLD FGRED
|
||||
|
||||
#define LDL " | " /* long delimiter */
|
||||
#define SDL "|" /* short delimiter for binary on 80 chars terminal */
|
||||
|
||||
#define CC_SEP '#' /* interface name separator for Classical CAN */
|
||||
#define FD_SEP '*' /* interface name separator for CAN FD */
|
||||
|
||||
static struct snif {
|
||||
int flags;
|
||||
long hold;
|
||||
long timeout;
|
||||
struct timeval laststamp;
|
||||
struct timeval currstamp;
|
||||
struct canfd_frame last;
|
||||
struct canfd_frame current;
|
||||
struct canfd_frame marker;
|
||||
struct canfd_frame notch;
|
||||
} sniftab[MAX_SLOTS];
|
||||
|
||||
extern int optind, opterr, optopt;
|
||||
|
||||
static int idx;
|
||||
static int running = 1;
|
||||
static volatile sig_atomic_t signal_num;
|
||||
static int clearscreen = 1;
|
||||
static int print_eff;
|
||||
static int print_ascii = 1;
|
||||
static int notch;
|
||||
static int max_dlen = CAN_MAX_DLEN;
|
||||
static long timeout = TIMEOUT;
|
||||
static long hold = HOLD;
|
||||
static long loop = LOOP;
|
||||
static long canfd_mode = CANFD_AUTO;
|
||||
static unsigned char binary;
|
||||
static unsigned char binary8;
|
||||
static unsigned char binary_gap;
|
||||
static unsigned char color;
|
||||
static unsigned char name_sep = CC_SEP;
|
||||
static char *interface;
|
||||
static char *vdl = LDL; /* variable delimiter */
|
||||
static char *ldl = LDL; /* long delimiter */
|
||||
|
||||
void print_snifline(int slot);
|
||||
int handle_keyb(void);
|
||||
int handle_frame(int fd, long currcms);
|
||||
int handle_timeo(long currcms);
|
||||
int writesettings(char* name);
|
||||
int readsettings(char* name);
|
||||
int sniftab_index(canid_t id);
|
||||
|
||||
void switchvdl(char *delim)
|
||||
{
|
||||
/* reduce delimiter size for EFF IDs in binary display of up
|
||||
to 8 data bytes payload to fit into 80 chars per line */
|
||||
if (binary8)
|
||||
vdl = delim;
|
||||
}
|
||||
|
||||
int comp(const void *elem1, const void *elem2)
|
||||
{
|
||||
unsigned long f = ((struct snif*)elem1)->current.can_id;
|
||||
unsigned long s = ((struct snif*)elem2)->current.can_id;
|
||||
|
||||
if (f > s)
|
||||
return 1;
|
||||
if (f < s)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void print_usage(char *prg)
|
||||
{
|
||||
const char manual [] = {
|
||||
"commands that can be entered at runtime:\n"
|
||||
" q<ENTER> - quit\n"
|
||||
" b<ENTER> - toggle binary / HEX-ASCII output\n"
|
||||
" 8<ENTER> - toggle binary / HEX-ASCII output (small for EFF on 80 chars)\n"
|
||||
" B<ENTER> - toggle binary with gap / HEX-ASCII output (exceeds 80 chars!)\n"
|
||||
" c<ENTER> - toggle color mode\n"
|
||||
" @<ENTER> - toggle ASCII output (disabled for CAN FD by default)\n"
|
||||
" <SPACE><ENTER> - force a clear screen\n"
|
||||
" #<ENTER> - notch currently marked/changed bits (can be used repeatedly)\n"
|
||||
" *<ENTER> - clear notched marked\n"
|
||||
" rMYNAME<ENTER> - read settings file (filter/notch)\n"
|
||||
" wMYNAME<ENTER> - write settings file (filter/notch)\n"
|
||||
" a<ENTER> - enable 'a'll SFF CAN-IDs to sniff\n"
|
||||
" n<ENTER> - enable 'n'one SFF CAN-IDs to sniff\n"
|
||||
" A<ENTER> - enable 'A'll EFF CAN-IDs to sniff\n"
|
||||
" N<ENTER> - enable 'N'one EFF CAN-IDs to sniff\n"
|
||||
" +FILTER<ENTER> - add CAN-IDs to sniff\n"
|
||||
" -FILTER<ENTER> - remove CAN-IDs to sniff\n"
|
||||
"\n"
|
||||
"FILTER can be a single CAN-ID or a CAN-ID/Bitmask:\n"
|
||||
"\n"
|
||||
" single SFF 11 bit IDs:\n"
|
||||
" +1F5<ENTER> - add SFF CAN-ID 0x1F5\n"
|
||||
" -42E<ENTER> - remove SFF CAN-ID 0x42E\n"
|
||||
"\n"
|
||||
" single EFF 29 bit IDs:\n"
|
||||
" +18FEDF55<ENTER> - add EFF CAN-ID 0x18FEDF55\n"
|
||||
" -00000090<ENTER> - remove EFF CAN-ID 0x00000090\n"
|
||||
"\n"
|
||||
" CAN-ID/Bitmask SFF:\n"
|
||||
" -42E7FF<ENTER> - remove SFF CAN-ID 0x42E (using Bitmask)\n"
|
||||
" -500700<ENTER> - remove SFF CAN-IDs 0x500 - 0x5FF\n"
|
||||
" +400600<ENTER> - add SFF CAN-IDs 0x400 - 0x5FF\n"
|
||||
" +000000<ENTER> - add all SFF CAN-IDs\n"
|
||||
" -000000<ENTER> - remove all SFF CAN-IDs\n"
|
||||
"\n"
|
||||
" CAN-ID/Bitmask EFF:\n"
|
||||
" -0000000000000000<ENTER> - remove all EFF CAN-IDs\n"
|
||||
" +12345678000000FF<ENTER> - add EFF CAN IDs xxxxxx78\n"
|
||||
" +0000000000000000<ENTER> - add all EFF CAN-IDs\n"
|
||||
"\n"
|
||||
"if (id & filter) == (sniff-id & filter) the action (+/-) is performed,\n"
|
||||
"which is quite easy when the filter is 000 resp. 00000000 for EFF.\n"
|
||||
"\n"
|
||||
};
|
||||
|
||||
fprintf(stderr, "%s - volatile CAN content visualizer.\n", prg);
|
||||
fprintf(stderr, "\nUsage: %s [can-interface]\n", prg);
|
||||
fprintf(stderr, "Options:\n");
|
||||
fprintf(stderr, " -q (quiet - all IDs deactivated)\n");
|
||||
fprintf(stderr, " -r <name> (read %sname from file)\n", SETFNAME);
|
||||
fprintf(stderr, " -e (fix extended frame format output - no auto detect)\n");
|
||||
fprintf(stderr, " -b (start with binary mode)\n");
|
||||
fprintf(stderr, " -8 (start with binary mode - for EFF on 80 chars)\n");
|
||||
fprintf(stderr, " -B (start with binary mode with gap - exceeds 80 chars!)\n");
|
||||
fprintf(stderr, " -c (color changes)\n");
|
||||
fprintf(stderr, " -f <mode> (CAN FD mode: 0 = OFF, 1 = ON, 2 = auto detect, default: %d)\n", CANFD_AUTO);
|
||||
fprintf(stderr, " -t <time> (timeout for ID display [x10ms] default: %d, 0 = OFF)\n", TIMEOUT);
|
||||
fprintf(stderr, " -h <time> (hold marker on changes [x10ms] default: %d)\n", HOLD);
|
||||
fprintf(stderr, " -l <time> (loop time (display) [x10ms] default: %d)\n", LOOP);
|
||||
fprintf(stderr, " -? (print this help text)\n");
|
||||
fprintf(stderr, "Use interface name '%s' to receive from all can-interfaces.\n", ANYDEV);
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "%s", manual);
|
||||
}
|
||||
|
||||
void sigterm(int signo)
|
||||
{
|
||||
running = 0;
|
||||
signal_num = signo;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
fd_set rdfs;
|
||||
int s;
|
||||
long currcms = 0;
|
||||
long lastcms = 0;
|
||||
unsigned char quiet = 0;
|
||||
int opt, ret = 0;
|
||||
struct timeval timeo, start_tv, tv;
|
||||
struct sockaddr_can addr;
|
||||
int i;
|
||||
|
||||
signal(SIGTERM, sigterm);
|
||||
signal(SIGHUP, sigterm);
|
||||
signal(SIGINT, sigterm);
|
||||
|
||||
for (i = 0; i < MAX_SLOTS ;i++) /* default: enable all slots */
|
||||
do_set(i, ENABLE);
|
||||
|
||||
while ((opt = getopt(argc, argv, "r:t:h:l:f:qeb8Bc?")) != -1) {
|
||||
switch (opt) {
|
||||
case 'r':
|
||||
if (readsettings(optarg) < 0) {
|
||||
fprintf(stderr, "Unable to read setting file '%s%s'!\n", SETFNAME, optarg);
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 't':
|
||||
sscanf(optarg, "%ld", &timeout);
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
sscanf(optarg, "%ld", &hold);
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
sscanf(optarg, "%ld", &loop);
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
sscanf(optarg, "%ld", &canfd_mode);
|
||||
if ((canfd_mode != CANFD_ON) && (canfd_mode != CANFD_OFF))
|
||||
canfd_mode = CANFD_AUTO;
|
||||
break;
|
||||
|
||||
case 'q':
|
||||
quiet = 1;
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
print_eff = 1;
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
binary = 1;
|
||||
binary_gap = 0;
|
||||
break;
|
||||
|
||||
case '8':
|
||||
binary = 1;
|
||||
binary8 = 1; /* enable variable delimiter for EFF */
|
||||
switchvdl(SDL); /* switch directly to short delimiter */
|
||||
binary_gap = 0;
|
||||
break;
|
||||
|
||||
case 'B':
|
||||
binary = 1;
|
||||
binary_gap = 1;
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
color = 1;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unknown option %c\n", opt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (optind == argc) {
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (quiet)
|
||||
for (i = 0; i < MAX_SLOTS; i++)
|
||||
do_clr(i, ENABLE);
|
||||
|
||||
if (strlen(argv[optind]) >= IFNAMSIZ) {
|
||||
printf("name of CAN device '%s' is too long!\n", argv[optind]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
interface = argv[optind];
|
||||
|
||||
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
|
||||
if (s < 0) {
|
||||
perror("socket");
|
||||
return 1;
|
||||
}
|
||||
|
||||
addr.can_family = AF_CAN;
|
||||
addr.can_ifindex = 0; /* 'any' CAN interface */
|
||||
|
||||
/* check for specific CAN interface */
|
||||
if (strcmp(ANYDEV, argv[optind]) != 0) {
|
||||
addr.can_ifindex = if_nametoindex(argv[optind]);
|
||||
if (!addr.can_ifindex) {
|
||||
perror("if_nametoindex");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* enable CAN FD if not disabled by command line option */
|
||||
if (canfd_mode != CANFD_OFF) {
|
||||
const int enable_canfd = 1;
|
||||
|
||||
if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES,
|
||||
&enable_canfd, sizeof(enable_canfd))){
|
||||
printf("error when enabling CAN FD support\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* might be changed in CANFD_AUTO mode */
|
||||
max_dlen = CANFD_MAX_DLEN;
|
||||
name_sep = FD_SEP;
|
||||
print_ascii = 0; /* don't print ASCII for CAN FD by default */
|
||||
}
|
||||
|
||||
ret = bind(s, (struct sockaddr *)&addr, sizeof(addr));
|
||||
if (ret < 0) {
|
||||
perror("bind");
|
||||
close(s);
|
||||
return ret;
|
||||
}
|
||||
|
||||
gettimeofday(&start_tv, NULL);
|
||||
tv.tv_sec = tv.tv_usec = 0;
|
||||
|
||||
printf("%s", CSR_HIDE); /* hide cursor */
|
||||
|
||||
while (running) {
|
||||
|
||||
FD_ZERO(&rdfs);
|
||||
FD_SET(0, &rdfs);
|
||||
FD_SET(s, &rdfs);
|
||||
|
||||
timeo.tv_sec = 0;
|
||||
timeo.tv_usec = 10000 * loop;
|
||||
|
||||
ret = select(s+1, &rdfs, NULL, NULL, &timeo);
|
||||
if (ret < 0) {
|
||||
//perror("select");
|
||||
running = 0;
|
||||
continue;
|
||||
} else
|
||||
ret = 0;
|
||||
|
||||
gettimeofday(&tv, NULL);
|
||||
currcms = (tv.tv_sec - start_tv.tv_sec) * 100 + (tv.tv_usec / 10000);
|
||||
|
||||
if (FD_ISSET(0, &rdfs))
|
||||
running &= handle_keyb();
|
||||
|
||||
if (FD_ISSET(s, &rdfs))
|
||||
running &= handle_frame(s, currcms);
|
||||
|
||||
if (currcms - lastcms >= loop) {
|
||||
running &= handle_timeo(currcms);
|
||||
lastcms = currcms;
|
||||
}
|
||||
}
|
||||
|
||||
printf("%s", CSR_SHOW); /* show cursor */
|
||||
|
||||
close(s);
|
||||
|
||||
if (signal_num)
|
||||
return 128 + signal_num;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void do_modify_sniftab(unsigned int value, unsigned int mask, char cmd)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < idx ;i++) {
|
||||
if ((sniftab[i].current.can_id & mask) == (value & mask)) {
|
||||
if (cmd == '+')
|
||||
do_set(i, ENABLE);
|
||||
else
|
||||
do_clr(i, ENABLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int handle_keyb(void)
|
||||
{
|
||||
char cmd [25] = {0};
|
||||
int i, clen;
|
||||
unsigned int mask;
|
||||
unsigned int value;
|
||||
|
||||
if (read(0, cmd, 24) > (long)strlen("+1234567812345678\n"))
|
||||
return 1; /* ignore */
|
||||
|
||||
if (strlen(cmd) > 0)
|
||||
cmd[strlen(cmd)-1] = 0; /* chop off trailing newline */
|
||||
|
||||
clen = strlen(&cmd[1]); /* content length behind command */
|
||||
|
||||
switch (cmd[0]) {
|
||||
|
||||
case '+':
|
||||
case '-':
|
||||
if (clen == 6) {
|
||||
/* masking strict SFF ID content vvvmmm */
|
||||
sscanf(&cmd[1], "%x", &value);
|
||||
mask = value | 0xFFFF800; /* cleared flags! */
|
||||
value >>= 12;
|
||||
value &= 0x7FF;
|
||||
do_modify_sniftab(value, mask, cmd[0]);
|
||||
break;
|
||||
} else if (clen == 16) {
|
||||
sscanf(&cmd[9], "%x", &mask);
|
||||
cmd[9] = 0; /* terminate 'value' */
|
||||
sscanf(&cmd[1], "%x", &value);
|
||||
mask |= CAN_EFF_FLAG;
|
||||
value |= CAN_EFF_FLAG;
|
||||
do_modify_sniftab(value, mask, cmd[0]);
|
||||
break;
|
||||
}
|
||||
|
||||
/* check for single SFF/EFF CAN ID length */
|
||||
if (clen != 3 && clen != 8)
|
||||
break;
|
||||
|
||||
/* enable/disable single SFF/EFF CAN ID */
|
||||
sscanf(&cmd[1], "%x", &value);
|
||||
if (clen == 8)
|
||||
value |= CAN_EFF_FLAG;
|
||||
|
||||
i = sniftab_index(value);
|
||||
if (i < 0)
|
||||
break; /* No Match */
|
||||
|
||||
if (cmd[0] == '+')
|
||||
do_set(i, ENABLE);
|
||||
else
|
||||
do_clr(i, ENABLE);
|
||||
|
||||
break;
|
||||
|
||||
case 'a' : /* all SFF CAN IDs */
|
||||
value = 0;
|
||||
mask = 0xFFFF800; /* cleared flags! */
|
||||
do_modify_sniftab(value, mask, '+');
|
||||
break;
|
||||
|
||||
case 'n' : /* none SFF CAN IDs */
|
||||
value = 0;
|
||||
mask = 0xFFFF800; /* cleared flags! */
|
||||
do_modify_sniftab(value, mask, '-');
|
||||
break;
|
||||
|
||||
case 'A' : /* all EFF CAN IDs */
|
||||
value = CAN_EFF_FLAG;
|
||||
mask = CAN_EFF_FLAG;
|
||||
do_modify_sniftab(value, mask, '+');
|
||||
break;
|
||||
|
||||
case 'N' : /* none EFF CAN IDs */
|
||||
value = CAN_EFF_FLAG;
|
||||
mask = CAN_EFF_FLAG;
|
||||
do_modify_sniftab(value, mask, '-');
|
||||
break;
|
||||
|
||||
case 'w' :
|
||||
if (writesettings(&cmd[1]))
|
||||
return 0;
|
||||
break;
|
||||
|
||||
case 'r' :
|
||||
if (readsettings(&cmd[1]) < 0)
|
||||
return 0;
|
||||
break;
|
||||
|
||||
case 'q' :
|
||||
running = 0;
|
||||
break;
|
||||
|
||||
case '@' :
|
||||
/* toggle ASCII output */
|
||||
print_ascii ^= 1;
|
||||
break;
|
||||
|
||||
case 'B' :
|
||||
binary_gap = 1;
|
||||
switchvdl(LDL);
|
||||
if (binary)
|
||||
binary = 0;
|
||||
else
|
||||
binary = 1;
|
||||
|
||||
break;
|
||||
|
||||
case '8' :
|
||||
binary8 = 1;
|
||||
/* fallthrough */
|
||||
|
||||
case 'b' :
|
||||
binary_gap = 0;
|
||||
if (binary) {
|
||||
binary = 0;
|
||||
switchvdl(LDL);
|
||||
} else {
|
||||
binary = 1;
|
||||
switchvdl(SDL);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'c' :
|
||||
if (color)
|
||||
color = 0;
|
||||
else
|
||||
color = 1;
|
||||
|
||||
break;
|
||||
|
||||
case ' ' :
|
||||
clearscreen = 1;
|
||||
break;
|
||||
|
||||
case '#' :
|
||||
notch = 1;
|
||||
break;
|
||||
|
||||
case '*' :
|
||||
for (i = 0; i < idx; i++)
|
||||
memset(&sniftab[i].notch.data, 0, max_dlen);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
clearscreen = 1;
|
||||
|
||||
return 1; /* ok */
|
||||
}
|
||||
|
||||
int handle_frame(int fd, long currcms)
|
||||
{
|
||||
bool rx_changed = false;
|
||||
bool run_qsort = false;
|
||||
int nbytes, i, pos;
|
||||
struct canfd_frame cf;
|
||||
|
||||
nbytes = read(fd, &cf, sizeof(cf));
|
||||
if (nbytes < 0) {
|
||||
perror("raw read");
|
||||
return 0; /* quit */
|
||||
}
|
||||
|
||||
if ((nbytes != CAN_MTU) && (nbytes != CANFD_MTU)) {
|
||||
printf("received strange frame data length %d!\n", nbytes);
|
||||
return 0; /* quit */
|
||||
}
|
||||
|
||||
/* CAN FD auto mode: switch based on first reception */
|
||||
if (canfd_mode == CANFD_AUTO) {
|
||||
if (nbytes == CAN_MTU) {
|
||||
canfd_mode = CANFD_OFF;
|
||||
/* change back auto defaults for Classical CAN */
|
||||
max_dlen = CAN_MAX_DLEN;
|
||||
name_sep = CC_SEP;
|
||||
print_ascii = 1;
|
||||
} else {
|
||||
canfd_mode = CANFD_ON;
|
||||
}
|
||||
}
|
||||
|
||||
/* filter for Classical CAN */
|
||||
if ((canfd_mode == CANFD_OFF) && (nbytes == CANFD_MTU))
|
||||
return 1; /* skip handling */
|
||||
|
||||
/* filter for CAN FD */
|
||||
if ((canfd_mode == CANFD_ON) && (nbytes == CAN_MTU))
|
||||
return 1; /* skip handling */
|
||||
|
||||
if (!print_eff && (cf.can_id & CAN_EFF_FLAG)) {
|
||||
print_eff = 1;
|
||||
clearscreen = 1;
|
||||
}
|
||||
|
||||
pos = sniftab_index(cf.can_id);
|
||||
if (pos < 0) {
|
||||
/* CAN ID not existing */
|
||||
if (idx >= MAX_SLOTS) {
|
||||
/* informative exit */
|
||||
perror("number of different CAN IDs exceeded MAX_SLOTS");
|
||||
return 0; /* quit */
|
||||
}
|
||||
/* assign new slot */
|
||||
pos = idx++;
|
||||
rx_changed = true;
|
||||
run_qsort = true;
|
||||
}
|
||||
else {
|
||||
if (cf.len == sniftab[pos].current.len)
|
||||
for (i = 0; i < cf.len; i++) {
|
||||
if (cf.data[i] != sniftab[pos].current.data[i] ) {
|
||||
rx_changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
rx_changed = true;
|
||||
}
|
||||
|
||||
/* print received frame even if the data didn't change to get a gap time */
|
||||
if ((sniftab[pos].laststamp.tv_sec == 0) && (sniftab[pos].laststamp.tv_usec == 0))
|
||||
rx_changed = true;
|
||||
|
||||
if (rx_changed == true) {
|
||||
sniftab[pos].laststamp = sniftab[pos].currstamp;
|
||||
ioctl(fd, SIOCGSTAMP, &sniftab[pos].currstamp);
|
||||
|
||||
sniftab[pos].current = cf;
|
||||
for (i = 0; i < max_dlen; i++)
|
||||
sniftab[pos].marker.data[i] |= sniftab[pos].current.data[i] ^ sniftab[pos].last.data[i];
|
||||
|
||||
sniftab[pos].timeout = (timeout)?(currcms + timeout):0;
|
||||
|
||||
if (is_clr(pos, DISPLAY))
|
||||
clearscreen = 1; /* new entry -> new drawing */
|
||||
|
||||
do_set(pos, DISPLAY);
|
||||
do_set(pos, UPDATE);
|
||||
}
|
||||
|
||||
if (run_qsort == true)
|
||||
qsort(sniftab, idx, sizeof(sniftab[0]), comp);
|
||||
|
||||
return 1; /* ok */
|
||||
}
|
||||
|
||||
int handle_timeo(long currcms)
|
||||
{
|
||||
int i, j;
|
||||
int force_redraw = 0;
|
||||
static unsigned int frame_count;
|
||||
|
||||
if (clearscreen) {
|
||||
if (print_eff)
|
||||
printf("%s%sXX|ms%s-- ID --%sdata ... < %s %c l=%ld h=%ld t=%ld slots=%d >",
|
||||
CLR_SCREEN, CSR_HOME, vdl, vdl, interface, name_sep, loop, hold, timeout, idx);
|
||||
else
|
||||
printf("%s%sXX|ms%sID %sdata ... < %s %c l=%ld h=%ld t=%ld slots=%d >",
|
||||
CLR_SCREEN, CSR_HOME, ldl, ldl, interface, name_sep, loop, hold, timeout, idx);
|
||||
|
||||
force_redraw = 1;
|
||||
clearscreen = 0;
|
||||
}
|
||||
|
||||
if (notch) {
|
||||
for (i = 0; i < idx; i++) {
|
||||
for (j = 0; j < max_dlen; j++)
|
||||
sniftab[i].notch.data[j] |= sniftab[i].marker.data[j];
|
||||
}
|
||||
notch = 0;
|
||||
}
|
||||
|
||||
printf("%s", CSR_HOME);
|
||||
printf("%02d\n", frame_count++); /* rolling display update counter */
|
||||
frame_count %= 100;
|
||||
|
||||
for (i = 0; i < idx; i++) {
|
||||
if is_set(i, ENABLE) {
|
||||
if is_set(i, DISPLAY) {
|
||||
if (is_set(i, UPDATE) || (force_redraw)) {
|
||||
print_snifline(i);
|
||||
sniftab[i].hold = currcms + hold;
|
||||
do_clr(i, UPDATE);
|
||||
}
|
||||
else if ((sniftab[i].hold) && (sniftab[i].hold < currcms)) {
|
||||
memset(&sniftab[i].marker.data, 0, max_dlen);
|
||||
print_snifline(i);
|
||||
sniftab[i].hold = 0; /* disable update by hold */
|
||||
}
|
||||
else
|
||||
printf("%s", CSR_DOWN); /* skip my line */
|
||||
|
||||
if (sniftab[i].timeout && sniftab[i].timeout < currcms) {
|
||||
do_clr(i, DISPLAY);
|
||||
do_clr(i, UPDATE);
|
||||
clearscreen = 1; /* removed entry -> new drawing next time */
|
||||
}
|
||||
}
|
||||
sniftab[i].last = sniftab[i].current;
|
||||
}
|
||||
}
|
||||
|
||||
return 1; /* ok */
|
||||
}
|
||||
|
||||
void print_snifline(int slot)
|
||||
{
|
||||
long diffsec = sniftab[slot].currstamp.tv_sec - sniftab[slot].laststamp.tv_sec;
|
||||
long diffusec = sniftab[slot].currstamp.tv_usec - sniftab[slot].laststamp.tv_usec;
|
||||
int dlc_diff = sniftab[slot].last.len - sniftab[slot].current.len;
|
||||
canid_t cid = sniftab[slot].current.can_id;
|
||||
int i,j;
|
||||
|
||||
if (diffusec < 0)
|
||||
diffsec--, diffusec += 1000000;
|
||||
|
||||
if (diffsec < 0)
|
||||
diffsec = diffusec = 0;
|
||||
|
||||
if (diffsec >= 100)
|
||||
diffsec = 99, diffusec = 999999;
|
||||
|
||||
if (cid & CAN_EFF_FLAG)
|
||||
printf("%02ld%03ld%s%08X%s", diffsec, diffusec/1000, vdl, cid & CAN_EFF_MASK, vdl);
|
||||
else if (print_eff)
|
||||
printf("%02ld%03ld%s---- %03X%s", diffsec, diffusec/1000, vdl, cid & CAN_SFF_MASK, vdl);
|
||||
else
|
||||
printf("%02ld%03ld%s%03X%s", diffsec, diffusec/1000, ldl, cid & CAN_SFF_MASK, ldl);
|
||||
|
||||
if (binary) {
|
||||
for (i = 0; i < sniftab[slot].current.len; i++) {
|
||||
for (j=7; j >= 0; j--) {
|
||||
if ((color) && (sniftab[slot].marker.data[i] & 1<<j) &&
|
||||
(!(sniftab[slot].notch.data[i] & 1<<j)))
|
||||
if (sniftab[slot].current.data[i] & 1<<j)
|
||||
printf("%s1%s", ATTCOLOR, ATTRESET);
|
||||
else
|
||||
printf("%s0%s", ATTCOLOR, ATTRESET);
|
||||
else
|
||||
if (sniftab[slot].current.data[i] & 1<<j)
|
||||
putchar('1');
|
||||
else
|
||||
putchar('0');
|
||||
}
|
||||
if (binary_gap)
|
||||
putchar(' ');
|
||||
}
|
||||
|
||||
/*
|
||||
* when the len decreased (dlc_diff > 0),
|
||||
* we need to blank the former data printout
|
||||
*/
|
||||
for (i = 0; i < dlc_diff; i++) {
|
||||
printf(" ");
|
||||
if (binary_gap)
|
||||
putchar(' ');
|
||||
}
|
||||
|
||||
} else { /* not binary -> hex data and ASCII output */
|
||||
|
||||
for (i = 0; i < sniftab[slot].current.len; i++)
|
||||
if ((color) && (sniftab[slot].marker.data[i] & ~sniftab[slot].notch.data[i]))
|
||||
printf("%s%02X%s ", ATTCOLOR, sniftab[slot].current.data[i], ATTRESET);
|
||||
else
|
||||
printf("%02X ", sniftab[slot].current.data[i]);
|
||||
|
||||
if (print_ascii) {
|
||||
/* jump to common start for ASCII output */
|
||||
if (sniftab[slot].current.len < max_dlen)
|
||||
printf("%*s", (max_dlen - sniftab[slot].current.len) * 3, "");
|
||||
|
||||
for (i = 0; i < sniftab[slot].current.len; i++)
|
||||
if ((sniftab[slot].current.data[i] > 0x1F) &&
|
||||
(sniftab[slot].current.data[i] < 0x7F))
|
||||
if ((color) && (sniftab[slot].marker.data[i] & ~sniftab[slot].notch.data[i]))
|
||||
printf("%s%c%s", ATTCOLOR, sniftab[slot].current.data[i], ATTRESET);
|
||||
else
|
||||
putchar(sniftab[slot].current.data[i]);
|
||||
else
|
||||
putchar('.');
|
||||
|
||||
/*
|
||||
* when the len decreased (dlc_diff > 0),
|
||||
* we need to blank the former data printout
|
||||
*/
|
||||
for (i = 0; i < dlc_diff; i++)
|
||||
putchar(' ');
|
||||
} else {
|
||||
/*
|
||||
* when the len decreased (dlc_diff > 0),
|
||||
* we need to blank the former data printout
|
||||
*/
|
||||
for (i = 0; i < dlc_diff; i++)
|
||||
printf(" ");
|
||||
}
|
||||
}
|
||||
|
||||
putchar('\n');
|
||||
|
||||
memset(&sniftab[slot].marker.data, 0, max_dlen);
|
||||
}
|
||||
|
||||
int writesettings(char* name)
|
||||
{
|
||||
int fd;
|
||||
char fname[FNAME_MAX_LEN + 1];
|
||||
int i,j;
|
||||
char buf[13]= {0};
|
||||
|
||||
if (canfd_mode == CANFD_OFF)
|
||||
strcpy(fname, SETFNAME);
|
||||
else if (canfd_mode == CANFD_ON)
|
||||
strcpy(fname, SETFDFNAME);
|
||||
else {
|
||||
printf("writesettings failed due to unspecified CAN FD mode\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
strncat(fname, name, FNAME_MAX_LEN - strlen(fname));
|
||||
fd = open(fname, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
|
||||
if (fd <= 0) {
|
||||
printf("unable to write setting file '%s'!\n", fname);
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (i = 0; i < idx ;i++) {
|
||||
sprintf(buf, "<%08X>%c.", sniftab[i].current.can_id, (is_set(i, ENABLE))?'1':'0');
|
||||
if (write(fd, buf, 12) < 0) {
|
||||
perror("write");
|
||||
return 1;
|
||||
}
|
||||
for (j = 0; j < max_dlen ; j++) {
|
||||
sprintf(buf, "%02X", sniftab[i].notch.data[j]);
|
||||
if (write(fd, buf, 2) < 0) {
|
||||
perror("write");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (write(fd, "\n", 1) < 0) {
|
||||
perror("write");
|
||||
return 1;
|
||||
}
|
||||
/* Classical CAN: 12 + 16 + 1 = 29 bytes per entry */
|
||||
/* CAN FD: 12 + 128 + 1 = 141 bytes per entry */
|
||||
}
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int readsettings(char* name)
|
||||
{
|
||||
int fd;
|
||||
char fname[FNAME_MAX_LEN + 1];
|
||||
char buf[142] = {0};
|
||||
int entrylen;
|
||||
int j;
|
||||
bool done = false;
|
||||
|
||||
if (canfd_mode == CANFD_OFF) {
|
||||
entrylen = 29;
|
||||
strcpy(fname, SETFNAME);
|
||||
} else if (canfd_mode == CANFD_ON) {
|
||||
entrylen = 141;
|
||||
strcpy(fname, SETFDFNAME);
|
||||
} else {
|
||||
printf("readsettings failed due to unspecified CAN FD mode\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
strncat(fname, name, FNAME_MAX_LEN - strlen(fname));
|
||||
fd = open(fname, O_RDONLY);
|
||||
|
||||
if (fd <= 0) {
|
||||
return -1;
|
||||
}
|
||||
idx = 0;
|
||||
while (!done) {
|
||||
if (read(fd, &buf, entrylen) != entrylen) {
|
||||
done = true;
|
||||
continue;
|
||||
}
|
||||
unsigned long id = strtoul(&buf[1], NULL, 16);
|
||||
|
||||
sniftab[idx].current.can_id = id;
|
||||
|
||||
if (buf[10] & 1)
|
||||
do_set(idx, ENABLE);
|
||||
else
|
||||
do_clr(idx, ENABLE);
|
||||
|
||||
for (j = max_dlen - 1; j >= 0 ; j--) {
|
||||
sniftab[idx].notch.data[j] =
|
||||
(__u8) strtoul(&buf[2*j+12], NULL, 16) & 0xFF;
|
||||
buf[2*j+12] = 0; /* cut off each time */
|
||||
}
|
||||
|
||||
if (++idx >= MAX_SLOTS)
|
||||
break;
|
||||
|
||||
}
|
||||
close(fd);
|
||||
return idx;
|
||||
}
|
||||
|
||||
int sniftab_index(canid_t id)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < idx; i++)
|
||||
if (id == sniftab[i].current.can_id)
|
||||
return i;
|
||||
|
||||
return -1; /* No match */
|
||||
}
|
||||
16
Devices/Libraries/Systems/can-utils/check_cc.sh
Executable file
16
Devices/Libraries/Systems/can-utils/check_cc.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
# check_cc.sh - Helper to test userspace compilation support
|
||||
# Copyright (c) 2015 Andrew Lutomirski
|
||||
|
||||
CC="$1"
|
||||
TESTPROG="$2"
|
||||
shift 2
|
||||
|
||||
if [ -n "$CC" ] && $CC -o /dev/null "$TESTPROG" -O0 "$@"; then
|
||||
echo 1
|
||||
else
|
||||
echo 0
|
||||
fi
|
||||
|
||||
exit 0
|
||||
5
Devices/Libraries/Systems/can-utils/cmake/aarch64-linux-gnu-clang.cmake
Executable file
5
Devices/Libraries/Systems/can-utils/cmake/aarch64-linux-gnu-clang.cmake
Executable file
@@ -0,0 +1,5 @@
|
||||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||
|
||||
set(CMAKE_C_COMPILER clang)
|
||||
set(CMAKE_C_COMPILER_TARGET aarch64-linux-gnu)
|
||||
5
Devices/Libraries/Systems/can-utils/cmake/aarch64-linux-gnu-gcc.cmake
Executable file
5
Devices/Libraries/Systems/can-utils/cmake/aarch64-linux-gnu-gcc.cmake
Executable file
@@ -0,0 +1,5 @@
|
||||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||
|
||||
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
|
||||
set(CMAKE_C_COMPILER_TARGET aarch64-linux-gnu)
|
||||
@@ -0,0 +1,5 @@
|
||||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR arm)
|
||||
|
||||
set(CMAKE_C_COMPILER clang)
|
||||
set(CMAKE_C_COMPILER_TARGET arm-linux-gnueabihf)
|
||||
5
Devices/Libraries/Systems/can-utils/cmake/arm-linux-gnueabihf-gcc.cmake
Executable file
5
Devices/Libraries/Systems/can-utils/cmake/arm-linux-gnueabihf-gcc.cmake
Executable file
@@ -0,0 +1,5 @@
|
||||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR arm)
|
||||
|
||||
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
|
||||
set(CMAKE_C_COMPILER_TARGET arm-linux-gnueabihf)
|
||||
24
Devices/Libraries/Systems/can-utils/cmake/make_uninstall.cmake
Executable file
24
Devices/Libraries/Systems/can-utils/cmake/make_uninstall.cmake
Executable file
@@ -0,0 +1,24 @@
|
||||
if(NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt")
|
||||
message(FATAL_ERROR
|
||||
"Cannot find install manifest: \
|
||||
${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt"
|
||||
)
|
||||
endif()
|
||||
|
||||
file(READ "${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt" files)
|
||||
string(REGEX REPLACE "[\r\n]" ";" files "${files}")
|
||||
|
||||
foreach(file ${files})
|
||||
message(STATUS "Uninstalling ${file}")
|
||||
if(EXISTS "${file}")
|
||||
file(REMOVE ${file})
|
||||
if (EXISTS "${file}")
|
||||
message(
|
||||
FATAL_ERROR "Problem when removing ${file}, \
|
||||
please check your permissions"
|
||||
)
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "File ${file} does not exist.")
|
||||
endif()
|
||||
endforeach()
|
||||
5
Devices/Libraries/Systems/can-utils/cmake/mips-linux-gnu-gcc.cmake
Executable file
5
Devices/Libraries/Systems/can-utils/cmake/mips-linux-gnu-gcc.cmake
Executable file
@@ -0,0 +1,5 @@
|
||||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR mips)
|
||||
|
||||
set(CMAKE_C_COMPILER mips-linux-gnu-gcc)
|
||||
set(CMAKE_C_COMPILER_TARGET mips-linux-gnu)
|
||||
27
Devices/Libraries/Systems/can-utils/fork_test.c
Executable file
27
Devices/Libraries/Systems/can-utils/fork_test.c
Executable file
@@ -0,0 +1,27 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2023 Dario Binacchi <dario.binacchi@amarulasolutions.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the version 2 of the GNU General Public License
|
||||
* as published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
fork();
|
||||
|
||||
return 0;
|
||||
}
|
||||
302
Devices/Libraries/Systems/can-utils/include/linux/can.h
Executable file
302
Devices/Libraries/Systems/can-utils/include/linux/can.h
Executable file
@@ -0,0 +1,302 @@
|
||||
/* SPDX-License-Identifier: ((GPL-2.0-only WITH Linux-syscall-note) OR BSD-3-Clause) */
|
||||
/*
|
||||
* linux/can.h
|
||||
*
|
||||
* Definitions for CAN network layer (socket addr / CAN frame / CAN filter)
|
||||
*
|
||||
* Authors: Oliver Hartkopp <oliver.hartkopp@volkswagen.de>
|
||||
* Urs Thuermann <urs.thuermann@volkswagen.de>
|
||||
* Copyright (c) 2002-2007 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _UAPI_CAN_H
|
||||
#define _UAPI_CAN_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/socket.h>
|
||||
#include <linux/stddef.h> /* for offsetof */
|
||||
|
||||
/* controller area network (CAN) kernel definitions */
|
||||
|
||||
/* special address description flags for the CAN_ID */
|
||||
#define CAN_EFF_FLAG 0x80000000U /* EFF/SFF is set in the MSB */
|
||||
#define CAN_RTR_FLAG 0x40000000U /* remote transmission request */
|
||||
#define CAN_ERR_FLAG 0x20000000U /* error message frame */
|
||||
|
||||
/* valid bits in CAN ID for frame formats */
|
||||
#define CAN_SFF_MASK 0x000007FFU /* standard frame format (SFF) */
|
||||
#define CAN_EFF_MASK 0x1FFFFFFFU /* extended frame format (EFF) */
|
||||
#define CAN_ERR_MASK 0x1FFFFFFFU /* omit EFF, RTR, ERR flags */
|
||||
#define CANXL_PRIO_MASK CAN_SFF_MASK /* 11 bit priority mask */
|
||||
|
||||
/*
|
||||
* Controller Area Network Identifier structure
|
||||
*
|
||||
* bit 0-28 : CAN identifier (11/29 bit)
|
||||
* bit 29 : error message frame flag (0 = data frame, 1 = error message)
|
||||
* bit 30 : remote transmission request flag (1 = rtr frame)
|
||||
* bit 31 : frame format flag (0 = standard 11 bit, 1 = extended 29 bit)
|
||||
*/
|
||||
typedef __u32 canid_t;
|
||||
|
||||
#define CAN_SFF_ID_BITS 11
|
||||
#define CAN_EFF_ID_BITS 29
|
||||
#define CANXL_PRIO_BITS CAN_SFF_ID_BITS
|
||||
|
||||
/*
|
||||
* Controller Area Network Error Message Frame Mask structure
|
||||
*
|
||||
* bit 0-28 : error class mask (see include/uapi/linux/can/error.h)
|
||||
* bit 29-31 : set to zero
|
||||
*/
|
||||
typedef __u32 can_err_mask_t;
|
||||
|
||||
/* CAN payload length and DLC definitions according to ISO 11898-1 */
|
||||
#define CAN_MAX_DLC 8
|
||||
#define CAN_MAX_RAW_DLC 15
|
||||
#define CAN_MAX_DLEN 8
|
||||
|
||||
/* CAN FD payload length and DLC definitions according to ISO 11898-7 */
|
||||
#define CANFD_MAX_DLC 15
|
||||
#define CANFD_MAX_DLEN 64
|
||||
|
||||
/*
|
||||
* CAN XL payload length and DLC definitions according to ISO 11898-1
|
||||
* CAN XL DLC ranges from 0 .. 2047 => data length from 1 .. 2048 byte
|
||||
*/
|
||||
#define CANXL_MIN_DLC 0
|
||||
#define CANXL_MAX_DLC 2047
|
||||
#define CANXL_MAX_DLC_MASK 0x07FF
|
||||
#define CANXL_MIN_DLEN 1
|
||||
#define CANXL_MAX_DLEN 2048
|
||||
|
||||
/**
|
||||
* struct can_frame - Classical CAN frame structure (aka CAN 2.0B)
|
||||
* @can_id: CAN ID of the frame and CAN_*_FLAG flags, see canid_t definition
|
||||
* @len: CAN frame payload length in byte (0 .. 8)
|
||||
* @can_dlc: deprecated name for CAN frame payload length in byte (0 .. 8)
|
||||
* @__pad: padding
|
||||
* @__res0: reserved / padding
|
||||
* @len8_dlc: optional DLC value (9 .. 15) at 8 byte payload length
|
||||
* len8_dlc contains values from 9 .. 15 when the payload length is
|
||||
* 8 bytes but the DLC value (see ISO 11898-1) is greater then 8.
|
||||
* CAN_CTRLMODE_CC_LEN8_DLC flag has to be enabled in CAN driver.
|
||||
* @data: CAN frame payload (up to 8 byte)
|
||||
*/
|
||||
struct can_frame {
|
||||
canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */
|
||||
union {
|
||||
/* CAN frame payload length in byte (0 .. CAN_MAX_DLEN)
|
||||
* was previously named can_dlc so we need to carry that
|
||||
* name for legacy support
|
||||
*/
|
||||
__u8 len;
|
||||
__u8 can_dlc; /* deprecated */
|
||||
} __attribute__((packed)); /* disable padding added in some ABIs */
|
||||
__u8 __pad; /* padding */
|
||||
__u8 __res0; /* reserved / padding */
|
||||
__u8 len8_dlc; /* optional DLC for 8 byte payload length (9 .. 15) */
|
||||
__u8 data[CAN_MAX_DLEN] __attribute__((aligned(8)));
|
||||
};
|
||||
|
||||
/*
|
||||
* defined bits for canfd_frame.flags
|
||||
*
|
||||
* The use of struct canfd_frame implies the FD Frame (FDF) bit to
|
||||
* be set in the CAN frame bitstream on the wire. The FDF bit switch turns
|
||||
* the CAN controllers bitstream processor into the CAN FD mode which creates
|
||||
* two new options within the CAN FD frame specification:
|
||||
*
|
||||
* Bit Rate Switch - to indicate a second bitrate is/was used for the payload
|
||||
* Error State Indicator - represents the error state of the transmitting node
|
||||
*
|
||||
* As the CANFD_ESI bit is internally generated by the transmitting CAN
|
||||
* controller only the CANFD_BRS bit is relevant for real CAN controllers when
|
||||
* building a CAN FD frame for transmission. Setting the CANFD_ESI bit can make
|
||||
* sense for virtual CAN interfaces to test applications with echoed frames.
|
||||
*
|
||||
* The struct can_frame and struct canfd_frame intentionally share the same
|
||||
* layout to be able to write CAN frame content into a CAN FD frame structure.
|
||||
* When this is done the former differentiation via CAN_MTU / CANFD_MTU gets
|
||||
* lost. CANFD_FDF allows programmers to mark CAN FD frames in the case of
|
||||
* using struct canfd_frame for mixed CAN / CAN FD content (dual use).
|
||||
* Since the introduction of CAN XL the CANFD_FDF flag is set in all CAN FD
|
||||
* frame structures provided by the CAN subsystem of the Linux kernel.
|
||||
*/
|
||||
#define CANFD_BRS 0x01 /* bit rate switch (second bitrate for payload data) */
|
||||
#define CANFD_ESI 0x02 /* error state indicator of the transmitting node */
|
||||
#define CANFD_FDF 0x04 /* mark CAN FD for dual use of struct canfd_frame */
|
||||
|
||||
/**
|
||||
* struct canfd_frame - CAN flexible data rate frame structure
|
||||
* @can_id: CAN ID of the frame and CAN_*_FLAG flags, see canid_t definition
|
||||
* @len: frame payload length in byte (0 .. CANFD_MAX_DLEN)
|
||||
* @flags: additional flags for CAN FD
|
||||
* @__res0: reserved / padding
|
||||
* @__res1: reserved / padding
|
||||
* @data: CAN FD frame payload (up to CANFD_MAX_DLEN byte)
|
||||
*/
|
||||
struct canfd_frame {
|
||||
canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */
|
||||
__u8 len; /* frame payload length in byte */
|
||||
__u8 flags; /* additional flags for CAN FD */
|
||||
__u8 __res0; /* reserved / padding */
|
||||
__u8 __res1; /* reserved / padding */
|
||||
__u8 data[CANFD_MAX_DLEN] __attribute__((aligned(8)));
|
||||
};
|
||||
|
||||
/*
|
||||
* defined bits for canxl_frame.flags
|
||||
*
|
||||
* The canxl_frame.flags element contains two bits CANXL_XLF and CANXL_SEC
|
||||
* and shares the relative position of the struct can[fd]_frame.len element.
|
||||
* The CANXL_XLF bit ALWAYS needs to be set to indicate a valid CAN XL frame.
|
||||
* As a side effect setting this bit intentionally breaks the length checks
|
||||
* for Classical CAN and CAN FD frames.
|
||||
*
|
||||
* Undefined bits in canxl_frame.flags are reserved and shall be set to zero.
|
||||
*/
|
||||
#define CANXL_XLF 0x80 /* mandatory CAN XL frame flag (must always be set!) */
|
||||
#define CANXL_SEC 0x01 /* Simple Extended Content (security/segmentation) */
|
||||
|
||||
/* the 8-bit VCID is optionally placed in the canxl_frame.prio element */
|
||||
#define CANXL_VCID_OFFSET 16 /* bit offset of VCID in prio element */
|
||||
#define CANXL_VCID_VAL_MASK 0xFFUL /* VCID is an 8-bit value */
|
||||
#define CANXL_VCID_MASK (CANXL_VCID_VAL_MASK << CANXL_VCID_OFFSET)
|
||||
|
||||
/**
|
||||
* struct canxl_frame - CAN with e'X'tended frame 'L'ength frame structure
|
||||
* @prio: 11 bit arbitration priority with zero'ed CAN_*_FLAG flags / VCID
|
||||
* @flags: additional flags for CAN XL
|
||||
* @sdt: SDU (service data unit) type
|
||||
* @len: frame payload length in byte (CANXL_MIN_DLEN .. CANXL_MAX_DLEN)
|
||||
* @af: acceptance field
|
||||
* @data: CAN XL frame payload (CANXL_MIN_DLEN .. CANXL_MAX_DLEN byte)
|
||||
*
|
||||
* @prio shares the same position as @can_id from struct can[fd]_frame.
|
||||
*/
|
||||
struct canxl_frame {
|
||||
canid_t prio; /* 11 bit priority for arbitration / 8 bit VCID */
|
||||
__u8 flags; /* additional flags for CAN XL */
|
||||
__u8 sdt; /* SDU (service data unit) type */
|
||||
__u16 len; /* frame payload length in byte */
|
||||
__u32 af; /* acceptance field */
|
||||
__u8 data[CANXL_MAX_DLEN];
|
||||
};
|
||||
|
||||
#define CAN_MTU (sizeof(struct can_frame))
|
||||
#define CANFD_MTU (sizeof(struct canfd_frame))
|
||||
#define CANXL_MTU (sizeof(struct canxl_frame))
|
||||
#define CANXL_HDR_SIZE (offsetof(struct canxl_frame, data))
|
||||
#define CANXL_MIN_MTU (CANXL_HDR_SIZE + 64)
|
||||
#define CANXL_MAX_MTU CANXL_MTU
|
||||
|
||||
/* particular protocols of the protocol family PF_CAN */
|
||||
#define CAN_RAW 1 /* RAW sockets */
|
||||
#define CAN_BCM 2 /* Broadcast Manager */
|
||||
#define CAN_TP16 3 /* VAG Transport Protocol v1.6 */
|
||||
#define CAN_TP20 4 /* VAG Transport Protocol v2.0 */
|
||||
#define CAN_MCNET 5 /* Bosch MCNet */
|
||||
#define CAN_ISOTP 6 /* ISO 15765-2 Transport Protocol */
|
||||
#define CAN_J1939 7 /* SAE J1939 */
|
||||
#define CAN_NPROTO 8
|
||||
|
||||
#define SOL_CAN_BASE 100
|
||||
|
||||
/*
|
||||
* This typedef was introduced in Linux v3.1-rc2
|
||||
* (commit 6602a4b net: Make userland include of netlink.h more sane)
|
||||
* in <linux/socket.h>. It must be duplicated here to make the CAN
|
||||
* headers self-contained.
|
||||
*/
|
||||
typedef unsigned short __kernel_sa_family_t;
|
||||
|
||||
/**
|
||||
* struct sockaddr_can - the sockaddr structure for CAN sockets
|
||||
* @can_family: address family number AF_CAN.
|
||||
* @can_ifindex: CAN network interface index.
|
||||
* @can_addr: protocol specific address information
|
||||
*/
|
||||
struct sockaddr_can {
|
||||
__kernel_sa_family_t can_family;
|
||||
int can_ifindex;
|
||||
union {
|
||||
/* transport protocol class address information (e.g. ISOTP) */
|
||||
struct { canid_t rx_id, tx_id; } tp;
|
||||
|
||||
/* J1939 address information */
|
||||
struct {
|
||||
/* 8 byte name when using dynamic addressing */
|
||||
__u64 name;
|
||||
|
||||
/* pgn:
|
||||
* 8 bit: PS in PDU2 case, else 0
|
||||
* 8 bit: PF
|
||||
* 1 bit: DP
|
||||
* 1 bit: reserved
|
||||
*/
|
||||
__u32 pgn;
|
||||
|
||||
/* 1 byte address */
|
||||
__u8 addr;
|
||||
} j1939;
|
||||
|
||||
/* reserved for future CAN protocols address information */
|
||||
} can_addr;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct can_filter - CAN ID based filter in can_register().
|
||||
* @can_id: relevant bits of CAN ID which are not masked out.
|
||||
* @can_mask: CAN mask (see description)
|
||||
*
|
||||
* Description:
|
||||
* A filter matches, when
|
||||
*
|
||||
* <received_can_id> & mask == can_id & mask
|
||||
*
|
||||
* The filter can be inverted (CAN_INV_FILTER bit set in can_id) or it can
|
||||
* filter for error message frames (CAN_ERR_FLAG bit set in mask).
|
||||
*/
|
||||
struct can_filter {
|
||||
canid_t can_id;
|
||||
canid_t can_mask;
|
||||
};
|
||||
|
||||
#define CAN_INV_FILTER 0x20000000U /* to be set in can_filter.can_id */
|
||||
|
||||
#endif /* !_UAPI_CAN_H */
|
||||
105
Devices/Libraries/Systems/can-utils/include/linux/can/bcm.h
Executable file
105
Devices/Libraries/Systems/can-utils/include/linux/can/bcm.h
Executable file
@@ -0,0 +1,105 @@
|
||||
/* SPDX-License-Identifier: ((GPL-2.0-only WITH Linux-syscall-note) OR BSD-3-Clause) */
|
||||
/*
|
||||
* linux/can/bcm.h
|
||||
*
|
||||
* Definitions for CAN Broadcast Manager (BCM)
|
||||
*
|
||||
* Author: Oliver Hartkopp <oliver.hartkopp@volkswagen.de>
|
||||
* Copyright (c) 2002-2007 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _UAPI_CAN_BCM_H
|
||||
#define _UAPI_CAN_BCM_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/can.h>
|
||||
|
||||
struct bcm_timeval {
|
||||
long tv_sec;
|
||||
long tv_usec;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct bcm_msg_head - head of messages to/from the broadcast manager
|
||||
* @opcode: opcode, see enum below.
|
||||
* @flags: special flags, see below.
|
||||
* @count: number of frames to send before changing interval.
|
||||
* @ival1: interval for the first @count frames.
|
||||
* @ival2: interval for the following frames.
|
||||
* @can_id: CAN ID of frames to be sent or received.
|
||||
* @nframes: number of frames appended to the message head.
|
||||
* @frames: array of CAN frames.
|
||||
*/
|
||||
struct bcm_msg_head {
|
||||
__u32 opcode;
|
||||
__u32 flags;
|
||||
__u32 count;
|
||||
struct bcm_timeval ival1, ival2;
|
||||
canid_t can_id;
|
||||
__u32 nframes;
|
||||
struct can_frame frames[];
|
||||
};
|
||||
|
||||
enum {
|
||||
TX_SETUP = 1, /* create (cyclic) transmission task */
|
||||
TX_DELETE, /* remove (cyclic) transmission task */
|
||||
TX_READ, /* read properties of (cyclic) transmission task */
|
||||
TX_SEND, /* send one CAN frame */
|
||||
RX_SETUP, /* create RX content filter subscription */
|
||||
RX_DELETE, /* remove RX content filter subscription */
|
||||
RX_READ, /* read properties of RX content filter subscription */
|
||||
TX_STATUS, /* reply to TX_READ request */
|
||||
TX_EXPIRED, /* notification on performed transmissions (count=0) */
|
||||
RX_STATUS, /* reply to RX_READ request */
|
||||
RX_TIMEOUT, /* cyclic message is absent */
|
||||
RX_CHANGED /* updated CAN frame (detected content change) */
|
||||
};
|
||||
|
||||
#define SETTIMER 0x0001
|
||||
#define STARTTIMER 0x0002
|
||||
#define TX_COUNTEVT 0x0004
|
||||
#define TX_ANNOUNCE 0x0008
|
||||
#define TX_CP_CAN_ID 0x0010
|
||||
#define RX_FILTER_ID 0x0020
|
||||
#define RX_CHECK_DLC 0x0040
|
||||
#define RX_NO_AUTOTIMER 0x0080
|
||||
#define RX_ANNOUNCE_RESUME 0x0100
|
||||
#define TX_RESET_MULTI_IDX 0x0200
|
||||
#define RX_RTR_FRAME 0x0400
|
||||
#define CAN_FD_FRAME 0x0800
|
||||
|
||||
#endif /* !_UAPI_CAN_BCM_H */
|
||||
143
Devices/Libraries/Systems/can-utils/include/linux/can/error.h
Executable file
143
Devices/Libraries/Systems/can-utils/include/linux/can/error.h
Executable file
@@ -0,0 +1,143 @@
|
||||
/* SPDX-License-Identifier: ((GPL-2.0-only WITH Linux-syscall-note) OR BSD-3-Clause) */
|
||||
/*
|
||||
* linux/can/error.h
|
||||
*
|
||||
* Definitions of the CAN error messages to be filtered and passed to the user.
|
||||
*
|
||||
* Author: Oliver Hartkopp <oliver.hartkopp@volkswagen.de>
|
||||
* Copyright (c) 2002-2007 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _UAPI_CAN_ERROR_H
|
||||
#define _UAPI_CAN_ERROR_H
|
||||
|
||||
#define CAN_ERR_DLC 8 /* dlc for error message frames */
|
||||
|
||||
/* error class (mask) in can_id */
|
||||
#define CAN_ERR_TX_TIMEOUT 0x00000001U /* TX timeout (by netdevice driver) */
|
||||
#define CAN_ERR_LOSTARB 0x00000002U /* lost arbitration / data[0] */
|
||||
#define CAN_ERR_CRTL 0x00000004U /* controller problems / data[1] */
|
||||
#define CAN_ERR_PROT 0x00000008U /* protocol violations / data[2..3] */
|
||||
#define CAN_ERR_TRX 0x00000010U /* transceiver status / data[4] */
|
||||
#define CAN_ERR_ACK 0x00000020U /* received no ACK on transmission */
|
||||
#define CAN_ERR_BUSOFF 0x00000040U /* bus off */
|
||||
#define CAN_ERR_BUSERROR 0x00000080U /* bus error (may flood!) */
|
||||
#define CAN_ERR_RESTARTED 0x00000100U /* controller restarted */
|
||||
#define CAN_ERR_CNT 0x00000200U /* TX error counter / data[6] */
|
||||
/* RX error counter / data[7] */
|
||||
|
||||
/* arbitration lost in bit ... / data[0] */
|
||||
#define CAN_ERR_LOSTARB_UNSPEC 0x00 /* unspecified */
|
||||
/* else bit number in bitstream */
|
||||
|
||||
/* error status of CAN-controller / data[1] */
|
||||
#define CAN_ERR_CRTL_UNSPEC 0x00 /* unspecified */
|
||||
#define CAN_ERR_CRTL_RX_OVERFLOW 0x01 /* RX buffer overflow */
|
||||
#define CAN_ERR_CRTL_TX_OVERFLOW 0x02 /* TX buffer overflow */
|
||||
#define CAN_ERR_CRTL_RX_WARNING 0x04 /* reached warning level for RX errors */
|
||||
#define CAN_ERR_CRTL_TX_WARNING 0x08 /* reached warning level for TX errors */
|
||||
#define CAN_ERR_CRTL_RX_PASSIVE 0x10 /* reached error passive status RX */
|
||||
#define CAN_ERR_CRTL_TX_PASSIVE 0x20 /* reached error passive status TX */
|
||||
/* (at least one error counter exceeds */
|
||||
/* the protocol-defined level of 127) */
|
||||
#define CAN_ERR_CRTL_ACTIVE 0x40 /* recovered to error active state */
|
||||
|
||||
/* error in CAN protocol (type) / data[2] */
|
||||
#define CAN_ERR_PROT_UNSPEC 0x00 /* unspecified */
|
||||
#define CAN_ERR_PROT_BIT 0x01 /* single bit error */
|
||||
#define CAN_ERR_PROT_FORM 0x02 /* frame format error */
|
||||
#define CAN_ERR_PROT_STUFF 0x04 /* bit stuffing error */
|
||||
#define CAN_ERR_PROT_BIT0 0x08 /* unable to send dominant bit */
|
||||
#define CAN_ERR_PROT_BIT1 0x10 /* unable to send recessive bit */
|
||||
#define CAN_ERR_PROT_OVERLOAD 0x20 /* bus overload */
|
||||
#define CAN_ERR_PROT_ACTIVE 0x40 /* active error announcement */
|
||||
#define CAN_ERR_PROT_TX 0x80 /* error occurred on transmission */
|
||||
|
||||
/* error in CAN protocol (location) / data[3] */
|
||||
#define CAN_ERR_PROT_LOC_UNSPEC 0x00 /* unspecified */
|
||||
#define CAN_ERR_PROT_LOC_SOF 0x03 /* start of frame */
|
||||
#define CAN_ERR_PROT_LOC_ID28_21 0x02 /* ID bits 28 - 21 (SFF: 10 - 3) */
|
||||
#define CAN_ERR_PROT_LOC_ID20_18 0x06 /* ID bits 20 - 18 (SFF: 2 - 0 )*/
|
||||
#define CAN_ERR_PROT_LOC_SRTR 0x04 /* substitute RTR (SFF: RTR) */
|
||||
#define CAN_ERR_PROT_LOC_IDE 0x05 /* identifier extension */
|
||||
#define CAN_ERR_PROT_LOC_ID17_13 0x07 /* ID bits 17-13 */
|
||||
#define CAN_ERR_PROT_LOC_ID12_05 0x0F /* ID bits 12-5 */
|
||||
#define CAN_ERR_PROT_LOC_ID04_00 0x0E /* ID bits 4-0 */
|
||||
#define CAN_ERR_PROT_LOC_RTR 0x0C /* RTR */
|
||||
#define CAN_ERR_PROT_LOC_RES1 0x0D /* reserved bit 1 */
|
||||
#define CAN_ERR_PROT_LOC_RES0 0x09 /* reserved bit 0 */
|
||||
#define CAN_ERR_PROT_LOC_DLC 0x0B /* data length code */
|
||||
#define CAN_ERR_PROT_LOC_DATA 0x0A /* data section */
|
||||
#define CAN_ERR_PROT_LOC_CRC_SEQ 0x08 /* CRC sequence */
|
||||
#define CAN_ERR_PROT_LOC_CRC_DEL 0x18 /* CRC delimiter */
|
||||
#define CAN_ERR_PROT_LOC_ACK 0x19 /* ACK slot */
|
||||
#define CAN_ERR_PROT_LOC_ACK_DEL 0x1B /* ACK delimiter */
|
||||
#define CAN_ERR_PROT_LOC_EOF 0x1A /* end of frame */
|
||||
#define CAN_ERR_PROT_LOC_INTERM 0x12 /* intermission */
|
||||
|
||||
/* error status of CAN-transceiver / data[4] */
|
||||
/* CANH CANL */
|
||||
#define CAN_ERR_TRX_UNSPEC 0x00 /* 0000 0000 */
|
||||
#define CAN_ERR_TRX_CANH_NO_WIRE 0x04 /* 0000 0100 */
|
||||
#define CAN_ERR_TRX_CANH_SHORT_TO_BAT 0x05 /* 0000 0101 */
|
||||
#define CAN_ERR_TRX_CANH_SHORT_TO_VCC 0x06 /* 0000 0110 */
|
||||
#define CAN_ERR_TRX_CANH_SHORT_TO_GND 0x07 /* 0000 0111 */
|
||||
#define CAN_ERR_TRX_CANL_NO_WIRE 0x40 /* 0100 0000 */
|
||||
#define CAN_ERR_TRX_CANL_SHORT_TO_BAT 0x50 /* 0101 0000 */
|
||||
#define CAN_ERR_TRX_CANL_SHORT_TO_VCC 0x60 /* 0110 0000 */
|
||||
#define CAN_ERR_TRX_CANL_SHORT_TO_GND 0x70 /* 0111 0000 */
|
||||
#define CAN_ERR_TRX_CANL_SHORT_TO_CANH 0x80 /* 1000 0000 */
|
||||
|
||||
/* data[5] is reserved (do not use) */
|
||||
|
||||
/* TX error counter / data[6] */
|
||||
/* RX error counter / data[7] */
|
||||
|
||||
/* CAN state thresholds
|
||||
*
|
||||
* Error counter Error state
|
||||
* -----------------------------------
|
||||
* 0 - 95 Error-active
|
||||
* 96 - 127 Error-warning
|
||||
* 128 - 255 Error-passive
|
||||
* 256 and greater Bus-off
|
||||
*/
|
||||
#define CAN_ERROR_WARNING_THRESHOLD 96
|
||||
#define CAN_ERROR_PASSIVE_THRESHOLD 128
|
||||
#define CAN_BUS_OFF_THRESHOLD 256
|
||||
|
||||
#endif /* _UAPI_CAN_ERROR_H */
|
||||
222
Devices/Libraries/Systems/can-utils/include/linux/can/gw.h
Executable file
222
Devices/Libraries/Systems/can-utils/include/linux/can/gw.h
Executable file
@@ -0,0 +1,222 @@
|
||||
/* SPDX-License-Identifier: ((GPL-2.0-only WITH Linux-syscall-note) OR BSD-3-Clause) */
|
||||
/*
|
||||
* linux/can/gw.h
|
||||
*
|
||||
* Definitions for CAN frame Gateway/Router/Bridge
|
||||
*
|
||||
* Author: Oliver Hartkopp <oliver.hartkopp@volkswagen.de>
|
||||
* Copyright (c) 2011 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _UAPI_CAN_GW_H
|
||||
#define _UAPI_CAN_GW_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/can.h>
|
||||
|
||||
struct rtcanmsg {
|
||||
__u8 can_family;
|
||||
__u8 gwtype;
|
||||
__u16 flags;
|
||||
};
|
||||
|
||||
/* CAN gateway types */
|
||||
enum {
|
||||
CGW_TYPE_UNSPEC,
|
||||
CGW_TYPE_CAN_CAN, /* CAN->CAN routing */
|
||||
__CGW_TYPE_MAX
|
||||
};
|
||||
|
||||
#define CGW_TYPE_MAX (__CGW_TYPE_MAX - 1)
|
||||
|
||||
/* CAN rtnetlink attribute definitions */
|
||||
enum {
|
||||
CGW_UNSPEC,
|
||||
CGW_MOD_AND, /* CAN frame modification binary AND */
|
||||
CGW_MOD_OR, /* CAN frame modification binary OR */
|
||||
CGW_MOD_XOR, /* CAN frame modification binary XOR */
|
||||
CGW_MOD_SET, /* CAN frame modification set alternate values */
|
||||
CGW_CS_XOR, /* set data[] XOR checksum into data[index] */
|
||||
CGW_CS_CRC8, /* set data[] CRC8 checksum into data[index] */
|
||||
CGW_HANDLED, /* number of handled CAN frames */
|
||||
CGW_DROPPED, /* number of dropped CAN frames */
|
||||
CGW_SRC_IF, /* ifindex of source network interface */
|
||||
CGW_DST_IF, /* ifindex of destination network interface */
|
||||
CGW_FILTER, /* specify struct can_filter on source CAN device */
|
||||
CGW_DELETED, /* number of deleted CAN frames (see max_hops param) */
|
||||
CGW_LIM_HOPS, /* limit the number of hops of this specific rule */
|
||||
CGW_MOD_UID, /* user defined identifier for modification updates */
|
||||
CGW_FDMOD_AND, /* CAN FD frame modification binary AND */
|
||||
CGW_FDMOD_OR, /* CAN FD frame modification binary OR */
|
||||
CGW_FDMOD_XOR, /* CAN FD frame modification binary XOR */
|
||||
CGW_FDMOD_SET, /* CAN FD frame modification set alternate values */
|
||||
__CGW_MAX
|
||||
};
|
||||
|
||||
#define CGW_MAX (__CGW_MAX - 1)
|
||||
|
||||
#define CGW_FLAGS_CAN_ECHO 0x01
|
||||
#define CGW_FLAGS_CAN_SRC_TSTAMP 0x02
|
||||
#define CGW_FLAGS_CAN_IIF_TX_OK 0x04
|
||||
#define CGW_FLAGS_CAN_FD 0x08
|
||||
|
||||
#define CGW_MOD_FUNCS 4 /* AND OR XOR SET */
|
||||
|
||||
/* CAN frame elements that are affected by curr. 3 CAN frame modifications */
|
||||
#define CGW_MOD_ID 0x01
|
||||
#define CGW_MOD_DLC 0x02 /* Classical CAN data length code */
|
||||
#define CGW_MOD_LEN CGW_MOD_DLC /* CAN FD (plain) data length */
|
||||
#define CGW_MOD_DATA 0x04
|
||||
#define CGW_MOD_FLAGS 0x08 /* CAN FD flags */
|
||||
|
||||
#define CGW_FRAME_MODS 4 /* ID DLC/LEN DATA FLAGS */
|
||||
|
||||
#define MAX_MODFUNCTIONS (CGW_MOD_FUNCS * CGW_FRAME_MODS)
|
||||
|
||||
struct cgw_frame_mod {
|
||||
struct can_frame cf;
|
||||
__u8 modtype;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct cgw_fdframe_mod {
|
||||
struct canfd_frame cf;
|
||||
__u8 modtype;
|
||||
} __attribute__((packed));
|
||||
|
||||
#define CGW_MODATTR_LEN sizeof(struct cgw_frame_mod)
|
||||
#define CGW_FDMODATTR_LEN sizeof(struct cgw_fdframe_mod)
|
||||
|
||||
struct cgw_csum_xor {
|
||||
__s8 from_idx;
|
||||
__s8 to_idx;
|
||||
__s8 result_idx;
|
||||
__u8 init_xor_val;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct cgw_csum_crc8 {
|
||||
__s8 from_idx;
|
||||
__s8 to_idx;
|
||||
__s8 result_idx;
|
||||
__u8 init_crc_val;
|
||||
__u8 final_xor_val;
|
||||
__u8 crctab[256];
|
||||
__u8 profile;
|
||||
__u8 profile_data[20];
|
||||
} __attribute__((packed));
|
||||
|
||||
/* length of checksum operation parameters. idx = index in CAN frame data[] */
|
||||
#define CGW_CS_XOR_LEN sizeof(struct cgw_csum_xor)
|
||||
#define CGW_CS_CRC8_LEN sizeof(struct cgw_csum_crc8)
|
||||
|
||||
/* CRC8 profiles (compute CRC for additional data elements - see below) */
|
||||
enum {
|
||||
CGW_CRC8PRF_UNSPEC,
|
||||
CGW_CRC8PRF_1U8, /* compute one additional u8 value */
|
||||
CGW_CRC8PRF_16U8, /* u8 value table indexed by data[1] & 0xF */
|
||||
CGW_CRC8PRF_SFFID_XOR, /* (can_id & 0xFF) ^ (can_id >> 8 & 0xFF) */
|
||||
__CGW_CRC8PRF_MAX
|
||||
};
|
||||
|
||||
#define CGW_CRC8PRF_MAX (__CGW_CRC8PRF_MAX - 1)
|
||||
|
||||
/*
|
||||
* CAN rtnetlink attribute contents in detail
|
||||
*
|
||||
* CGW_XXX_IF (length 4 bytes):
|
||||
* Sets an interface index for source/destination network interfaces.
|
||||
* For the CAN->CAN gwtype the indices of _two_ CAN interfaces are mandatory.
|
||||
*
|
||||
* CGW_FILTER (length 8 bytes):
|
||||
* Sets a CAN receive filter for the gateway job specified by the
|
||||
* struct can_filter described in include/linux/can.h
|
||||
*
|
||||
* CGW_MOD_(AND|OR|XOR|SET) (length 17 bytes):
|
||||
* Specifies a modification that's done to a received CAN frame before it is
|
||||
* send out to the destination interface.
|
||||
*
|
||||
* <struct can_frame> data used as operator
|
||||
* <u8> affected CAN frame elements
|
||||
*
|
||||
* CGW_LIM_HOPS (length 1 byte):
|
||||
* Limit the number of hops of this specific rule. Usually the received CAN
|
||||
* frame can be processed as much as 'max_hops' times (which is given at module
|
||||
* load time of the can-gw module). This value is used to reduce the number of
|
||||
* possible hops for this gateway rule to a value smaller then max_hops.
|
||||
*
|
||||
* CGW_MOD_UID (length 4 bytes):
|
||||
* Optional non-zero user defined routing job identifier to alter existing
|
||||
* modification settings at runtime.
|
||||
*
|
||||
* CGW_CS_XOR (length 4 bytes):
|
||||
* Set a simple XOR checksum starting with an initial value into
|
||||
* data[result-idx] using data[start-idx] .. data[end-idx]
|
||||
*
|
||||
* The XOR checksum is calculated like this:
|
||||
*
|
||||
* xor = init_xor_val
|
||||
*
|
||||
* for (i = from_idx .. to_idx)
|
||||
* xor ^= can_frame.data[i]
|
||||
*
|
||||
* can_frame.data[ result_idx ] = xor
|
||||
*
|
||||
* CGW_CS_CRC8 (length 282 bytes):
|
||||
* Set a CRC8 value into data[result-idx] using a given 256 byte CRC8 table,
|
||||
* a given initial value and a defined input data[start-idx] .. data[end-idx].
|
||||
* Finally the result value is XOR'ed with the final_xor_val.
|
||||
*
|
||||
* The CRC8 checksum is calculated like this:
|
||||
*
|
||||
* crc = init_crc_val
|
||||
*
|
||||
* for (i = from_idx .. to_idx)
|
||||
* crc = crctab[ crc ^ can_frame.data[i] ]
|
||||
*
|
||||
* can_frame.data[ result_idx ] = crc ^ final_xor_val
|
||||
*
|
||||
* The calculated CRC may contain additional source data elements that can be
|
||||
* defined in the handling of 'checksum profiles' e.g. shown in AUTOSAR specs
|
||||
* like http://www.autosar.org/download/R4.0/AUTOSAR_SWS_E2ELibrary.pdf
|
||||
* E.g. the profile_data[] may contain additional u8 values (called DATA_IDs)
|
||||
* that are used depending on counter values inside the CAN frame data[].
|
||||
* So far only three profiles have been implemented for illustration.
|
||||
*
|
||||
* Remark: In general the attribute data is a linear buffer.
|
||||
* Beware of sending unpacked or aligned structs!
|
||||
*/
|
||||
|
||||
#endif /* !_UAPI_CAN_GW_H */
|
||||
183
Devices/Libraries/Systems/can-utils/include/linux/can/isotp.h
Executable file
183
Devices/Libraries/Systems/can-utils/include/linux/can/isotp.h
Executable file
@@ -0,0 +1,183 @@
|
||||
/* SPDX-License-Identifier: ((GPL-2.0-only WITH Linux-syscall-note) OR BSD-3-Clause) */
|
||||
/*
|
||||
* linux/can/isotp.h
|
||||
*
|
||||
* Definitions for isotp CAN sockets (ISO 15765-2:2016)
|
||||
*
|
||||
* Copyright (c) 2020 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _UAPI_CAN_ISOTP_H
|
||||
#define _UAPI_CAN_ISOTP_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/can.h>
|
||||
|
||||
#define SOL_CAN_ISOTP (SOL_CAN_BASE + CAN_ISOTP)
|
||||
|
||||
/* for socket options affecting the socket (not the global system) */
|
||||
|
||||
#define CAN_ISOTP_OPTS 1 /* pass struct can_isotp_options */
|
||||
|
||||
#define CAN_ISOTP_RECV_FC 2 /* pass struct can_isotp_fc_options */
|
||||
|
||||
/* sockopts to force stmin timer values for protocol regression tests */
|
||||
|
||||
#define CAN_ISOTP_TX_STMIN 3 /* pass __u32 value in nano secs */
|
||||
/* use this time instead of value */
|
||||
/* provided in FC from the receiver */
|
||||
|
||||
#define CAN_ISOTP_RX_STMIN 4 /* pass __u32 value in nano secs */
|
||||
/* ignore received CF frames which */
|
||||
/* timestamps differ less than val */
|
||||
|
||||
#define CAN_ISOTP_LL_OPTS 5 /* pass struct can_isotp_ll_options */
|
||||
|
||||
struct can_isotp_options {
|
||||
|
||||
__u32 flags; /* set flags for isotp behaviour. */
|
||||
/* __u32 value : flags see below */
|
||||
|
||||
__u32 frame_txtime; /* frame transmission time (N_As/N_Ar) */
|
||||
/* __u32 value : time in nano secs */
|
||||
|
||||
__u8 ext_address; /* set address for extended addressing */
|
||||
/* __u8 value : extended address */
|
||||
|
||||
__u8 txpad_content; /* set content of padding byte (tx) */
|
||||
/* __u8 value : content on tx path */
|
||||
|
||||
__u8 rxpad_content; /* set content of padding byte (rx) */
|
||||
/* __u8 value : content on rx path */
|
||||
|
||||
__u8 rx_ext_address; /* set address for extended addressing */
|
||||
/* __u8 value : extended address (rx) */
|
||||
};
|
||||
|
||||
struct can_isotp_fc_options {
|
||||
|
||||
__u8 bs; /* blocksize provided in FC frame */
|
||||
/* __u8 value : blocksize. 0 = off */
|
||||
|
||||
__u8 stmin; /* separation time provided in FC frame */
|
||||
/* __u8 value : */
|
||||
/* 0x00 - 0x7F : 0 - 127 ms */
|
||||
/* 0x80 - 0xF0 : reserved */
|
||||
/* 0xF1 - 0xF9 : 100 us - 900 us */
|
||||
/* 0xFA - 0xFF : reserved */
|
||||
|
||||
__u8 wftmax; /* max. number of wait frame transmiss. */
|
||||
/* __u8 value : 0 = omit FC N_PDU WT */
|
||||
};
|
||||
|
||||
struct can_isotp_ll_options {
|
||||
|
||||
__u8 mtu; /* generated & accepted CAN frame type */
|
||||
/* __u8 value : */
|
||||
/* CAN_MTU (16) -> standard CAN 2.0 */
|
||||
/* CANFD_MTU (72) -> CAN FD frame */
|
||||
|
||||
__u8 tx_dl; /* tx link layer data length in bytes */
|
||||
/* (configured maximum payload length) */
|
||||
/* __u8 value : 8,12,16,20,24,32,48,64 */
|
||||
/* => rx path supports all LL_DL values */
|
||||
|
||||
__u8 tx_flags; /* set into struct canfd_frame.flags */
|
||||
/* at frame creation: e.g. CANFD_BRS */
|
||||
/* Obsolete when the BRS flag is fixed */
|
||||
/* by the CAN netdriver configuration */
|
||||
};
|
||||
|
||||
/* flags for isotp behaviour */
|
||||
|
||||
#define CAN_ISOTP_LISTEN_MODE 0x0001 /* listen only (do not send FC) */
|
||||
#define CAN_ISOTP_EXTEND_ADDR 0x0002 /* enable extended addressing */
|
||||
#define CAN_ISOTP_TX_PADDING 0x0004 /* enable CAN frame padding tx path */
|
||||
#define CAN_ISOTP_RX_PADDING 0x0008 /* enable CAN frame padding rx path */
|
||||
#define CAN_ISOTP_CHK_PAD_LEN 0x0010 /* check received CAN frame padding */
|
||||
#define CAN_ISOTP_CHK_PAD_DATA 0x0020 /* check received CAN frame padding */
|
||||
#define CAN_ISOTP_HALF_DUPLEX 0x0040 /* half duplex error state handling */
|
||||
#define CAN_ISOTP_FORCE_TXSTMIN 0x0080 /* ignore stmin from received FC */
|
||||
#define CAN_ISOTP_FORCE_RXSTMIN 0x0100 /* ignore CFs depending on rx stmin */
|
||||
#define CAN_ISOTP_RX_EXT_ADDR 0x0200 /* different rx extended addressing */
|
||||
#define CAN_ISOTP_WAIT_TX_DONE 0x0400 /* wait for tx completion */
|
||||
#define CAN_ISOTP_SF_BROADCAST 0x0800 /* 1-to-N functional addressing */
|
||||
#define CAN_ISOTP_CF_BROADCAST 0x1000 /* 1-to-N transmission w/o FC */
|
||||
#define CAN_ISOTP_DYN_FC_PARMS 0x2000 /* dynamic FC parameters BS/STmin */
|
||||
|
||||
/* protocol machine default values */
|
||||
|
||||
#define CAN_ISOTP_DEFAULT_FLAGS 0
|
||||
#define CAN_ISOTP_DEFAULT_EXT_ADDRESS 0x00
|
||||
#define CAN_ISOTP_DEFAULT_PAD_CONTENT 0xCC /* prevent bit-stuffing */
|
||||
#define CAN_ISOTP_DEFAULT_FRAME_TXTIME 50000 /* 50 micro seconds */
|
||||
#define CAN_ISOTP_DEFAULT_RECV_BS 0
|
||||
#define CAN_ISOTP_DEFAULT_RECV_STMIN 0x00
|
||||
#define CAN_ISOTP_DEFAULT_RECV_WFTMAX 0
|
||||
|
||||
/*
|
||||
* Remark on CAN_ISOTP_DEFAULT_RECV_* values:
|
||||
*
|
||||
* We can strongly assume, that the Linux Kernel implementation of
|
||||
* CAN_ISOTP is capable to run with BS=0, STmin=0 and WFTmax=0.
|
||||
* But as we like to be able to behave as a commonly available ECU,
|
||||
* these default settings can be changed via sockopts.
|
||||
* For that reason the STmin value is intentionally _not_ checked for
|
||||
* consistency and copied directly into the flow control (FC) frame.
|
||||
*/
|
||||
|
||||
/* link layer default values => make use of Classical CAN frames */
|
||||
|
||||
#define CAN_ISOTP_DEFAULT_LL_MTU CAN_MTU
|
||||
#define CAN_ISOTP_DEFAULT_LL_TX_DL CAN_MAX_DLEN
|
||||
#define CAN_ISOTP_DEFAULT_LL_TX_FLAGS 0
|
||||
|
||||
/*
|
||||
* The CAN_ISOTP_DEFAULT_FRAME_TXTIME has become a non-zero value as
|
||||
* it only makes sense for isotp implementation tests to run without
|
||||
* a N_As value. As user space applications usually do not set the
|
||||
* frame_txtime element of struct can_isotp_options the new in-kernel
|
||||
* default is very likely overwritten with zero when the sockopt()
|
||||
* CAN_ISOTP_OPTS is invoked.
|
||||
* To make sure that a N_As value of zero is only set intentional the
|
||||
* value '0' is now interpreted as 'do not change the current value'.
|
||||
* When a frame_txtime of zero is required for testing purposes this
|
||||
* CAN_ISOTP_FRAME_TXTIME_ZERO u32 value has to be set in frame_txtime.
|
||||
*/
|
||||
#define CAN_ISOTP_FRAME_TXTIME_ZERO 0xFFFFFFFF
|
||||
|
||||
#endif /* !_UAPI_CAN_ISOTP_H */
|
||||
108
Devices/Libraries/Systems/can-utils/include/linux/can/j1939.h
Executable file
108
Devices/Libraries/Systems/can-utils/include/linux/can/j1939.h
Executable file
@@ -0,0 +1,108 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
|
||||
/*
|
||||
* j1939.h
|
||||
*
|
||||
* Copyright (c) 2010-2011 EIA Electronics
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef _UAPI_CAN_J1939_H_
|
||||
#define _UAPI_CAN_J1939_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/socket.h>
|
||||
#include <linux/can.h>
|
||||
|
||||
#define J1939_MAX_UNICAST_ADDR 0xfd
|
||||
#define J1939_IDLE_ADDR 0xfe
|
||||
#define J1939_NO_ADDR 0xff /* == broadcast or no addr */
|
||||
#define J1939_NO_NAME 0
|
||||
#define J1939_PGN_REQUEST 0x0ea00 /* Request PG */
|
||||
#define J1939_PGN_ADDRESS_CLAIMED 0x0ee00 /* Address Claimed */
|
||||
#define J1939_PGN_ADDRESS_COMMANDED 0x0fed8 /* Commanded Address */
|
||||
#define J1939_PGN_PDU1_MAX 0x3ff00
|
||||
#define J1939_PGN_MAX 0x3ffff
|
||||
#define J1939_NO_PGN 0x40000
|
||||
|
||||
/* J1939 Parameter Group Number
|
||||
*
|
||||
* bit 0-7 : PDU Specific (PS)
|
||||
* bit 8-15 : PDU Format (PF)
|
||||
* bit 16 : Data Page (DP)
|
||||
* bit 17 : Reserved (R)
|
||||
* bit 19-31 : set to zero
|
||||
*/
|
||||
typedef __u32 pgn_t;
|
||||
|
||||
/* J1939 Priority
|
||||
*
|
||||
* bit 0-2 : Priority (P)
|
||||
* bit 3-7 : set to zero
|
||||
*/
|
||||
typedef __u8 priority_t;
|
||||
|
||||
/* J1939 NAME
|
||||
*
|
||||
* bit 0-20 : Identity Number
|
||||
* bit 21-31 : Manufacturer Code
|
||||
* bit 32-34 : ECU Instance
|
||||
* bit 35-39 : Function Instance
|
||||
* bit 40-47 : Function
|
||||
* bit 48 : Reserved
|
||||
* bit 49-55 : Vehicle System
|
||||
* bit 56-59 : Vehicle System Instance
|
||||
* bit 60-62 : Industry Group
|
||||
* bit 63 : Arbitrary Address Capable
|
||||
*/
|
||||
typedef __u64 name_t;
|
||||
|
||||
/* J1939 socket options */
|
||||
#define SOL_CAN_J1939 (SOL_CAN_BASE + CAN_J1939)
|
||||
enum {
|
||||
SO_J1939_FILTER = 1, /* set filters */
|
||||
SO_J1939_PROMISC = 2, /* set/clr promiscuous mode */
|
||||
SO_J1939_SEND_PRIO = 3,
|
||||
SO_J1939_ERRQUEUE = 4,
|
||||
};
|
||||
|
||||
enum {
|
||||
SCM_J1939_DEST_ADDR = 1,
|
||||
SCM_J1939_DEST_NAME = 2,
|
||||
SCM_J1939_PRIO = 3,
|
||||
SCM_J1939_ERRQUEUE = 4,
|
||||
};
|
||||
|
||||
enum {
|
||||
J1939_NLA_PAD,
|
||||
J1939_NLA_BYTES_ACKED,
|
||||
J1939_NLA_TOTAL_SIZE,
|
||||
J1939_NLA_PGN,
|
||||
J1939_NLA_SRC_NAME,
|
||||
J1939_NLA_DEST_NAME,
|
||||
J1939_NLA_SRC_ADDR,
|
||||
J1939_NLA_DEST_ADDR,
|
||||
};
|
||||
|
||||
enum {
|
||||
J1939_EE_INFO_NONE,
|
||||
J1939_EE_INFO_TX_ABORT,
|
||||
J1939_EE_INFO_RX_RTS,
|
||||
J1939_EE_INFO_RX_DPO,
|
||||
J1939_EE_INFO_RX_ABORT,
|
||||
};
|
||||
|
||||
struct j1939_filter {
|
||||
name_t name;
|
||||
name_t name_mask;
|
||||
pgn_t pgn;
|
||||
pgn_t pgn_mask;
|
||||
__u8 addr;
|
||||
__u8 addr_mask;
|
||||
};
|
||||
|
||||
#define J1939_FILTER_MAX 512 /* maximum number of j1939_filter set via setsockopt() */
|
||||
|
||||
#endif /* !_UAPI_CAN_J1939_H_ */
|
||||
185
Devices/Libraries/Systems/can-utils/include/linux/can/netlink.h
Executable file
185
Devices/Libraries/Systems/can-utils/include/linux/can/netlink.h
Executable file
@@ -0,0 +1,185 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
|
||||
/*
|
||||
* linux/can/netlink.h
|
||||
*
|
||||
* Definitions for the CAN netlink interface
|
||||
*
|
||||
* Copyright (c) 2009 Wolfgang Grandegger <wg@grandegger.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the version 2 of the GNU General Public License
|
||||
* as published by the Free Software Foundation
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#ifndef _UAPI_CAN_NETLINK_H
|
||||
#define _UAPI_CAN_NETLINK_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
/*
|
||||
* CAN bit-timing parameters
|
||||
*
|
||||
* For further information, please read chapter "8 BIT TIMING
|
||||
* REQUIREMENTS" of the "Bosch CAN Specification version 2.0"
|
||||
* at http://www.semiconductors.bosch.de/pdf/can2spec.pdf.
|
||||
*/
|
||||
struct can_bittiming {
|
||||
__u32 bitrate; /* Bit-rate in bits/second */
|
||||
__u32 sample_point; /* Sample point in one-tenth of a percent */
|
||||
__u32 tq; /* Time quanta (TQ) in nanoseconds */
|
||||
__u32 prop_seg; /* Propagation segment in TQs */
|
||||
__u32 phase_seg1; /* Phase buffer segment 1 in TQs */
|
||||
__u32 phase_seg2; /* Phase buffer segment 2 in TQs */
|
||||
__u32 sjw; /* Synchronisation jump width in TQs */
|
||||
__u32 brp; /* Bit-rate prescaler */
|
||||
};
|
||||
|
||||
/*
|
||||
* CAN hardware-dependent bit-timing constant
|
||||
*
|
||||
* Used for calculating and checking bit-timing parameters
|
||||
*/
|
||||
struct can_bittiming_const {
|
||||
char name[16]; /* Name of the CAN controller hardware */
|
||||
__u32 tseg1_min; /* Time segment 1 = prop_seg + phase_seg1 */
|
||||
__u32 tseg1_max;
|
||||
__u32 tseg2_min; /* Time segment 2 = phase_seg2 */
|
||||
__u32 tseg2_max;
|
||||
__u32 sjw_max; /* Synchronisation jump width */
|
||||
__u32 brp_min; /* Bit-rate prescaler */
|
||||
__u32 brp_max;
|
||||
__u32 brp_inc;
|
||||
};
|
||||
|
||||
/*
|
||||
* CAN clock parameters
|
||||
*/
|
||||
struct can_clock {
|
||||
__u32 freq; /* CAN system clock frequency in Hz */
|
||||
};
|
||||
|
||||
/*
|
||||
* CAN operational and error states
|
||||
*/
|
||||
enum can_state {
|
||||
CAN_STATE_ERROR_ACTIVE = 0, /* RX/TX error count < 96 */
|
||||
CAN_STATE_ERROR_WARNING, /* RX/TX error count < 128 */
|
||||
CAN_STATE_ERROR_PASSIVE, /* RX/TX error count < 256 */
|
||||
CAN_STATE_BUS_OFF, /* RX/TX error count >= 256 */
|
||||
CAN_STATE_STOPPED, /* Device is stopped */
|
||||
CAN_STATE_SLEEPING, /* Device is sleeping */
|
||||
CAN_STATE_MAX
|
||||
};
|
||||
|
||||
/*
|
||||
* CAN bus error counters
|
||||
*/
|
||||
struct can_berr_counter {
|
||||
__u16 txerr;
|
||||
__u16 rxerr;
|
||||
};
|
||||
|
||||
/*
|
||||
* CAN controller mode
|
||||
*/
|
||||
struct can_ctrlmode {
|
||||
__u32 mask;
|
||||
__u32 flags;
|
||||
};
|
||||
|
||||
#define CAN_CTRLMODE_LOOPBACK 0x01 /* Loopback mode */
|
||||
#define CAN_CTRLMODE_LISTENONLY 0x02 /* Listen-only mode */
|
||||
#define CAN_CTRLMODE_3_SAMPLES 0x04 /* Triple sampling mode */
|
||||
#define CAN_CTRLMODE_ONE_SHOT 0x08 /* One-Shot mode */
|
||||
#define CAN_CTRLMODE_BERR_REPORTING 0x10 /* Bus-error reporting */
|
||||
#define CAN_CTRLMODE_FD 0x20 /* CAN FD mode */
|
||||
#define CAN_CTRLMODE_PRESUME_ACK 0x40 /* Ignore missing CAN ACKs */
|
||||
#define CAN_CTRLMODE_FD_NON_ISO 0x80 /* CAN FD in non-ISO mode */
|
||||
#define CAN_CTRLMODE_CC_LEN8_DLC 0x100 /* Classic CAN DLC option */
|
||||
#define CAN_CTRLMODE_TDC_AUTO 0x200 /* CAN transiver automatically calculates TDCV */
|
||||
#define CAN_CTRLMODE_TDC_MANUAL 0x400 /* TDCV is manually set up by user */
|
||||
|
||||
/*
|
||||
* CAN device statistics
|
||||
*/
|
||||
struct can_device_stats {
|
||||
__u32 bus_error; /* Bus errors */
|
||||
__u32 error_warning; /* Changes to error warning state */
|
||||
__u32 error_passive; /* Changes to error passive state */
|
||||
__u32 bus_off; /* Changes to bus off state */
|
||||
__u32 arbitration_lost; /* Arbitration lost errors */
|
||||
__u32 restarts; /* CAN controller re-starts */
|
||||
};
|
||||
|
||||
/*
|
||||
* CAN netlink interface
|
||||
*/
|
||||
enum {
|
||||
IFLA_CAN_UNSPEC,
|
||||
IFLA_CAN_BITTIMING,
|
||||
IFLA_CAN_BITTIMING_CONST,
|
||||
IFLA_CAN_CLOCK,
|
||||
IFLA_CAN_STATE,
|
||||
IFLA_CAN_CTRLMODE,
|
||||
IFLA_CAN_RESTART_MS,
|
||||
IFLA_CAN_RESTART,
|
||||
IFLA_CAN_BERR_COUNTER,
|
||||
IFLA_CAN_DATA_BITTIMING,
|
||||
IFLA_CAN_DATA_BITTIMING_CONST,
|
||||
IFLA_CAN_TERMINATION,
|
||||
IFLA_CAN_TERMINATION_CONST,
|
||||
IFLA_CAN_BITRATE_CONST,
|
||||
IFLA_CAN_DATA_BITRATE_CONST,
|
||||
IFLA_CAN_BITRATE_MAX,
|
||||
IFLA_CAN_TDC,
|
||||
IFLA_CAN_CTRLMODE_EXT,
|
||||
|
||||
/* add new constants above here */
|
||||
__IFLA_CAN_MAX,
|
||||
IFLA_CAN_MAX = __IFLA_CAN_MAX - 1
|
||||
};
|
||||
|
||||
/*
|
||||
* CAN FD Transmitter Delay Compensation (TDC)
|
||||
*
|
||||
* Please refer to struct can_tdc_const and can_tdc in
|
||||
* include/linux/can/bittiming.h for further details.
|
||||
*/
|
||||
enum {
|
||||
IFLA_CAN_TDC_UNSPEC,
|
||||
IFLA_CAN_TDC_TDCV_MIN, /* u32 */
|
||||
IFLA_CAN_TDC_TDCV_MAX, /* u32 */
|
||||
IFLA_CAN_TDC_TDCO_MIN, /* u32 */
|
||||
IFLA_CAN_TDC_TDCO_MAX, /* u32 */
|
||||
IFLA_CAN_TDC_TDCF_MIN, /* u32 */
|
||||
IFLA_CAN_TDC_TDCF_MAX, /* u32 */
|
||||
IFLA_CAN_TDC_TDCV, /* u32 */
|
||||
IFLA_CAN_TDC_TDCO, /* u32 */
|
||||
IFLA_CAN_TDC_TDCF, /* u32 */
|
||||
|
||||
/* add new constants above here */
|
||||
__IFLA_CAN_TDC,
|
||||
IFLA_CAN_TDC_MAX = __IFLA_CAN_TDC - 1
|
||||
};
|
||||
|
||||
/*
|
||||
* IFLA_CAN_CTRLMODE_EXT nest: controller mode extended parameters
|
||||
*/
|
||||
enum {
|
||||
IFLA_CAN_CTRLMODE_UNSPEC,
|
||||
IFLA_CAN_CTRLMODE_SUPPORTED, /* u32 */
|
||||
|
||||
/* add new constants above here */
|
||||
__IFLA_CAN_CTRLMODE,
|
||||
IFLA_CAN_CTRLMODE_MAX = __IFLA_CAN_CTRLMODE - 1
|
||||
};
|
||||
|
||||
/* u16 termination range: 1..65535 Ohms */
|
||||
#define CAN_TERMINATION_DISABLED 0
|
||||
|
||||
#endif /* !_UAPI_CAN_NETLINK_H */
|
||||
86
Devices/Libraries/Systems/can-utils/include/linux/can/raw.h
Executable file
86
Devices/Libraries/Systems/can-utils/include/linux/can/raw.h
Executable file
@@ -0,0 +1,86 @@
|
||||
/* SPDX-License-Identifier: ((GPL-2.0-only WITH Linux-syscall-note) OR BSD-3-Clause) */
|
||||
/*
|
||||
* linux/can/raw.h
|
||||
*
|
||||
* Definitions for raw CAN sockets
|
||||
*
|
||||
* Authors: Oliver Hartkopp <oliver.hartkopp@volkswagen.de>
|
||||
* Urs Thuermann <urs.thuermann@volkswagen.de>
|
||||
* Copyright (c) 2002-2007 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _UAPI_CAN_RAW_H
|
||||
#define _UAPI_CAN_RAW_H
|
||||
|
||||
#include <linux/can.h>
|
||||
|
||||
#define SOL_CAN_RAW (SOL_CAN_BASE + CAN_RAW)
|
||||
#define CAN_RAW_FILTER_MAX 512 /* maximum number of can_filter set via setsockopt() */
|
||||
|
||||
enum {
|
||||
SCM_CAN_RAW_ERRQUEUE = 1,
|
||||
};
|
||||
|
||||
/* for socket options affecting the socket (not the global system) */
|
||||
|
||||
enum {
|
||||
CAN_RAW_FILTER = 1, /* set 0 .. n can_filter(s) */
|
||||
CAN_RAW_ERR_FILTER, /* set filter for error frames */
|
||||
CAN_RAW_LOOPBACK, /* local loopback (default:on) */
|
||||
CAN_RAW_RECV_OWN_MSGS, /* receive my own msgs (default:off) */
|
||||
CAN_RAW_FD_FRAMES, /* allow CAN FD frames (default:off) */
|
||||
CAN_RAW_JOIN_FILTERS, /* all filters must match to trigger */
|
||||
CAN_RAW_XL_FRAMES, /* allow CAN XL frames (default:off) */
|
||||
CAN_RAW_XL_VCID_OPTS, /* CAN XL VCID configuration options */
|
||||
};
|
||||
|
||||
/* configuration for CAN XL virtual CAN identifier (VCID) handling */
|
||||
struct can_raw_vcid_options {
|
||||
|
||||
__u8 flags; /* flags for vcid (filter) behaviour */
|
||||
__u8 tx_vcid; /* VCID value set into canxl_frame.prio */
|
||||
__u8 rx_vcid; /* VCID value for VCID filter */
|
||||
__u8 rx_vcid_mask; /* VCID mask for VCID filter */
|
||||
|
||||
};
|
||||
|
||||
/* can_raw_vcid_options.flags for CAN XL virtual CAN identifier handling */
|
||||
#define CAN_RAW_XL_VCID_TX_SET 0x01
|
||||
#define CAN_RAW_XL_VCID_TX_PASS 0x02
|
||||
#define CAN_RAW_XL_VCID_RX_FILTER 0x04
|
||||
|
||||
#endif /* !_UAPI_CAN_RAW_H */
|
||||
13
Devices/Libraries/Systems/can-utils/include/linux/can/vxcan.h
Executable file
13
Devices/Libraries/Systems/can-utils/include/linux/can/vxcan.h
Executable file
@@ -0,0 +1,13 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
|
||||
#ifndef _UAPI_CAN_VXCAN_H
|
||||
#define _UAPI_CAN_VXCAN_H
|
||||
|
||||
enum {
|
||||
VXCAN_INFO_UNSPEC,
|
||||
VXCAN_INFO_PEER,
|
||||
|
||||
__VXCAN_INFO_MAX
|
||||
#define VXCAN_INFO_MAX (__VXCAN_INFO_MAX - 1)
|
||||
};
|
||||
|
||||
#endif
|
||||
54
Devices/Libraries/Systems/can-utils/include/linux/errqueue.h
Executable file
54
Devices/Libraries/Systems/can-utils/include/linux/errqueue.h
Executable file
@@ -0,0 +1,54 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
||||
#ifndef _UAPI_LINUX_ERRQUEUE_H
|
||||
#define _UAPI_LINUX_ERRQUEUE_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
struct sock_extended_err {
|
||||
__u32 ee_errno;
|
||||
__u8 ee_origin;
|
||||
__u8 ee_type;
|
||||
__u8 ee_code;
|
||||
__u8 ee_pad;
|
||||
__u32 ee_info;
|
||||
__u32 ee_data;
|
||||
};
|
||||
|
||||
#define SO_EE_ORIGIN_NONE 0
|
||||
#define SO_EE_ORIGIN_LOCAL 1
|
||||
#define SO_EE_ORIGIN_ICMP 2
|
||||
#define SO_EE_ORIGIN_ICMP6 3
|
||||
#define SO_EE_ORIGIN_TXSTATUS 4
|
||||
#define SO_EE_ORIGIN_ZEROCOPY 5
|
||||
#define SO_EE_ORIGIN_TXTIME 6
|
||||
#define SO_EE_ORIGIN_TIMESTAMPING SO_EE_ORIGIN_TXSTATUS
|
||||
|
||||
#define SO_EE_OFFENDER(ee) ((struct sockaddr*)((ee)+1))
|
||||
|
||||
#define SO_EE_CODE_ZEROCOPY_COPIED 1
|
||||
|
||||
#define SO_EE_CODE_TXTIME_INVALID_PARAM 1
|
||||
#define SO_EE_CODE_TXTIME_MISSED 2
|
||||
|
||||
/**
|
||||
* struct scm_timestamping - timestamps exposed through cmsg
|
||||
*
|
||||
* The timestamping interfaces SO_TIMESTAMPING, MSG_TSTAMP_*
|
||||
* communicate network timestamps by passing this struct in a cmsg with
|
||||
* recvmsg(). See Documentation/networking/timestamping.txt for details.
|
||||
*/
|
||||
struct scm_timestamping {
|
||||
struct timespec ts[3];
|
||||
};
|
||||
|
||||
/* The type of scm_timestamping, passed in sock_extended_err ee_info.
|
||||
* This defines the type of ts[0]. For SCM_TSTAMP_SND only, if ts[0]
|
||||
* is zero, then this is a hardware timestamp and recorded in ts[2].
|
||||
*/
|
||||
enum {
|
||||
SCM_TSTAMP_SND, /* driver passed skb to NIC, or HW */
|
||||
SCM_TSTAMP_SCHED, /* data entered the packet scheduler */
|
||||
SCM_TSTAMP_ACK, /* data acknowledged by peer */
|
||||
};
|
||||
|
||||
#endif /* _UAPI_LINUX_ERRQUEUE_H */
|
||||
98
Devices/Libraries/Systems/can-utils/include/linux/kernel.h
Executable file
98
Devices/Libraries/Systems/can-utils/include/linux/kernel.h
Executable file
@@ -0,0 +1,98 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
#ifndef _LINUX_KERNEL_H
|
||||
#define _LINUX_KERNEL_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint32_t __le32;
|
||||
|
||||
struct mcp251xfd_mem;
|
||||
|
||||
struct regmap {
|
||||
struct mcp251xfd_mem *mem;
|
||||
};
|
||||
|
||||
#define pr_info(...) fprintf(stdout, ## __VA_ARGS__)
|
||||
#define pr_err(...) fprintf(stderr, ## __VA_ARGS__)
|
||||
#define pr_warn(...) fprintf(stderr, ## __VA_ARGS__)
|
||||
#define pr_cont(...) fprintf(stdout, ## __VA_ARGS__)
|
||||
#define netdev_info(ndev, ...) fprintf(stdout, ## __VA_ARGS__)
|
||||
#define BUILD_BUG_ON(...)
|
||||
|
||||
#define BITS_PER_LONG (sizeof(long) * 8)
|
||||
|
||||
#define ____cacheline_aligned
|
||||
|
||||
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
|
||||
|
||||
int regmap_bulk_read(struct regmap *map, unsigned int reg,
|
||||
void *val, size_t val_count);
|
||||
|
||||
#define SZ_2K 0x00000800
|
||||
|
||||
#define __packed __attribute__((__packed__))
|
||||
|
||||
#define sizeof_field(TYPE, MEMBER) sizeof((((TYPE *)0)->MEMBER))
|
||||
|
||||
#define BIT(nr) (UL(1) << (nr))
|
||||
|
||||
#define __stringify_1(x...) #x
|
||||
#define __stringify(x...) __stringify_1(x)
|
||||
|
||||
#ifdef __ASSEMBLY__
|
||||
#define _AC(X,Y) X
|
||||
#define _AT(T,X) X
|
||||
#else
|
||||
#define __AC(X,Y) (X##Y)
|
||||
#define _AC(X,Y) __AC(X,Y)
|
||||
#define _AT(T,X) ((T)(X))
|
||||
#endif
|
||||
|
||||
#define _UL(x) (_AC(x, UL))
|
||||
#define _ULL(x) (_AC(x, ULL))
|
||||
|
||||
#define UL(x) (_UL(x))
|
||||
#define ULL(x) (_ULL(x))
|
||||
|
||||
#define GENMASK(h, l) \
|
||||
(((~UL(0)) - (UL(1) << (l)) + 1) & \
|
||||
(~UL(0) >> (BITS_PER_LONG - 1 - (h))))
|
||||
|
||||
#define __bf_shf(x) (__builtin_ffsll(x) - 1)
|
||||
|
||||
#define FIELD_PREP(_mask, _val) \
|
||||
({ \
|
||||
((typeof(_mask))(_val) << __bf_shf(_mask)) & (_mask); \
|
||||
})
|
||||
|
||||
#define FIELD_GET(_mask, _reg) \
|
||||
({ \
|
||||
(typeof(_mask))(((_reg) & (_mask)) >> __bf_shf(_mask)); \
|
||||
})
|
||||
|
||||
#define min_t(type, x, y) ({ \
|
||||
type __min1 = (x); \
|
||||
type __min2 = (y); \
|
||||
__min1 < __min2 ? __min1 : __min2; })
|
||||
|
||||
#define get_canfd_dlc(i) (min_t(__u8, (i), CANFD_MAX_DLC))
|
||||
|
||||
static const u8 dlc2len[] = {0, 1, 2, 3, 4, 5, 6, 7,
|
||||
8, 12, 16, 20, 24, 32, 48, 64};
|
||||
|
||||
/* get data length from can_dlc with sanitized can_dlc */
|
||||
static inline u8 can_dlc2len(u8 can_dlc)
|
||||
{
|
||||
return dlc2len[can_dlc & 0x0F];
|
||||
}
|
||||
|
||||
#endif /* _LINUX_KERNEL_H */
|
||||
162
Devices/Libraries/Systems/can-utils/include/linux/net_tstamp.h
Executable file
162
Devices/Libraries/Systems/can-utils/include/linux/net_tstamp.h
Executable file
@@ -0,0 +1,162 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
||||
/*
|
||||
* Userspace API for hardware time stamping of network packets
|
||||
*
|
||||
* Copyright (C) 2008,2009 Intel Corporation
|
||||
* Author: Patrick Ohly <patrick.ohly@intel.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _NET_TIMESTAMPING_H
|
||||
#define _NET_TIMESTAMPING_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/socket.h> /* for SO_TIMESTAMPING */
|
||||
|
||||
/* SO_TIMESTAMPING gets an integer bit field comprised of these values */
|
||||
enum {
|
||||
SOF_TIMESTAMPING_TX_HARDWARE = (1<<0),
|
||||
SOF_TIMESTAMPING_TX_SOFTWARE = (1<<1),
|
||||
SOF_TIMESTAMPING_RX_HARDWARE = (1<<2),
|
||||
SOF_TIMESTAMPING_RX_SOFTWARE = (1<<3),
|
||||
SOF_TIMESTAMPING_SOFTWARE = (1<<4),
|
||||
SOF_TIMESTAMPING_SYS_HARDWARE = (1<<5),
|
||||
SOF_TIMESTAMPING_RAW_HARDWARE = (1<<6),
|
||||
SOF_TIMESTAMPING_OPT_ID = (1<<7),
|
||||
SOF_TIMESTAMPING_TX_SCHED = (1<<8),
|
||||
SOF_TIMESTAMPING_TX_ACK = (1<<9),
|
||||
SOF_TIMESTAMPING_OPT_CMSG = (1<<10),
|
||||
SOF_TIMESTAMPING_OPT_TSONLY = (1<<11),
|
||||
SOF_TIMESTAMPING_OPT_STATS = (1<<12),
|
||||
SOF_TIMESTAMPING_OPT_PKTINFO = (1<<13),
|
||||
SOF_TIMESTAMPING_OPT_TX_SWHW = (1<<14),
|
||||
|
||||
SOF_TIMESTAMPING_LAST = SOF_TIMESTAMPING_OPT_TX_SWHW,
|
||||
SOF_TIMESTAMPING_MASK = (SOF_TIMESTAMPING_LAST - 1) |
|
||||
SOF_TIMESTAMPING_LAST
|
||||
};
|
||||
|
||||
/*
|
||||
* SO_TIMESTAMPING flags are either for recording a packet timestamp or for
|
||||
* reporting the timestamp to user space.
|
||||
* Recording flags can be set both via socket options and control messages.
|
||||
*/
|
||||
#define SOF_TIMESTAMPING_TX_RECORD_MASK (SOF_TIMESTAMPING_TX_HARDWARE | \
|
||||
SOF_TIMESTAMPING_TX_SOFTWARE | \
|
||||
SOF_TIMESTAMPING_TX_SCHED | \
|
||||
SOF_TIMESTAMPING_TX_ACK)
|
||||
|
||||
/**
|
||||
* struct hwtstamp_config - %SIOCGHWTSTAMP and %SIOCSHWTSTAMP parameter
|
||||
*
|
||||
* @flags: no flags defined right now, must be zero for %SIOCSHWTSTAMP
|
||||
* @tx_type: one of HWTSTAMP_TX_*
|
||||
* @rx_filter: one of HWTSTAMP_FILTER_*
|
||||
*
|
||||
* %SIOCGHWTSTAMP and %SIOCSHWTSTAMP expect a &struct ifreq with a
|
||||
* ifr_data pointer to this structure. For %SIOCSHWTSTAMP, if the
|
||||
* driver or hardware does not support the requested @rx_filter value,
|
||||
* the driver may use a more general filter mode. In this case
|
||||
* @rx_filter will indicate the actual mode on return.
|
||||
*/
|
||||
struct hwtstamp_config {
|
||||
int flags;
|
||||
int tx_type;
|
||||
int rx_filter;
|
||||
};
|
||||
|
||||
/* possible values for hwtstamp_config->tx_type */
|
||||
enum hwtstamp_tx_types {
|
||||
/*
|
||||
* No outgoing packet will need hardware time stamping;
|
||||
* should a packet arrive which asks for it, no hardware
|
||||
* time stamping will be done.
|
||||
*/
|
||||
HWTSTAMP_TX_OFF,
|
||||
|
||||
/*
|
||||
* Enables hardware time stamping for outgoing packets;
|
||||
* the sender of the packet decides which are to be
|
||||
* time stamped by setting %SOF_TIMESTAMPING_TX_SOFTWARE
|
||||
* before sending the packet.
|
||||
*/
|
||||
HWTSTAMP_TX_ON,
|
||||
|
||||
/*
|
||||
* Enables time stamping for outgoing packets just as
|
||||
* HWTSTAMP_TX_ON does, but also enables time stamp insertion
|
||||
* directly into Sync packets. In this case, transmitted Sync
|
||||
* packets will not received a time stamp via the socket error
|
||||
* queue.
|
||||
*/
|
||||
HWTSTAMP_TX_ONESTEP_SYNC,
|
||||
};
|
||||
|
||||
/* possible values for hwtstamp_config->rx_filter */
|
||||
enum hwtstamp_rx_filters {
|
||||
/* time stamp no incoming packet at all */
|
||||
HWTSTAMP_FILTER_NONE,
|
||||
|
||||
/* time stamp any incoming packet */
|
||||
HWTSTAMP_FILTER_ALL,
|
||||
|
||||
/* return value: time stamp all packets requested plus some others */
|
||||
HWTSTAMP_FILTER_SOME,
|
||||
|
||||
/* PTP v1, UDP, any kind of event packet */
|
||||
HWTSTAMP_FILTER_PTP_V1_L4_EVENT,
|
||||
/* PTP v1, UDP, Sync packet */
|
||||
HWTSTAMP_FILTER_PTP_V1_L4_SYNC,
|
||||
/* PTP v1, UDP, Delay_req packet */
|
||||
HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ,
|
||||
/* PTP v2, UDP, any kind of event packet */
|
||||
HWTSTAMP_FILTER_PTP_V2_L4_EVENT,
|
||||
/* PTP v2, UDP, Sync packet */
|
||||
HWTSTAMP_FILTER_PTP_V2_L4_SYNC,
|
||||
/* PTP v2, UDP, Delay_req packet */
|
||||
HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ,
|
||||
|
||||
/* 802.AS1, Ethernet, any kind of event packet */
|
||||
HWTSTAMP_FILTER_PTP_V2_L2_EVENT,
|
||||
/* 802.AS1, Ethernet, Sync packet */
|
||||
HWTSTAMP_FILTER_PTP_V2_L2_SYNC,
|
||||
/* 802.AS1, Ethernet, Delay_req packet */
|
||||
HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ,
|
||||
|
||||
/* PTP v2/802.AS1, any layer, any kind of event packet */
|
||||
HWTSTAMP_FILTER_PTP_V2_EVENT,
|
||||
/* PTP v2/802.AS1, any layer, Sync packet */
|
||||
HWTSTAMP_FILTER_PTP_V2_SYNC,
|
||||
/* PTP v2/802.AS1, any layer, Delay_req packet */
|
||||
HWTSTAMP_FILTER_PTP_V2_DELAY_REQ,
|
||||
|
||||
/* NTP, UDP, all versions and packet modes */
|
||||
HWTSTAMP_FILTER_NTP_ALL,
|
||||
};
|
||||
|
||||
/* SCM_TIMESTAMPING_PKTINFO control message */
|
||||
struct scm_ts_pktinfo {
|
||||
__u32 if_index;
|
||||
__u32 pkt_length;
|
||||
__u32 reserved[2];
|
||||
};
|
||||
|
||||
/*
|
||||
* SO_TXTIME gets a struct sock_txtime with flags being an integer bit
|
||||
* field comprised of these values.
|
||||
*/
|
||||
enum txtime_flags {
|
||||
SOF_TXTIME_DEADLINE_MODE = (1 << 0),
|
||||
SOF_TXTIME_REPORT_ERRORS = (1 << 1),
|
||||
|
||||
SOF_TXTIME_FLAGS_LAST = SOF_TXTIME_REPORT_ERRORS,
|
||||
SOF_TXTIME_FLAGS_MASK = (SOF_TXTIME_FLAGS_LAST - 1) |
|
||||
SOF_TXTIME_FLAGS_LAST
|
||||
};
|
||||
|
||||
struct sock_txtime {
|
||||
__kernel_clockid_t clockid;/* reference clockid */
|
||||
__u32 flags; /* as defined by enum txtime_flags */
|
||||
};
|
||||
|
||||
#endif /* _NET_TIMESTAMPING_H */
|
||||
252
Devices/Libraries/Systems/can-utils/include/linux/netlink.h
Executable file
252
Devices/Libraries/Systems/can-utils/include/linux/netlink.h
Executable file
@@ -0,0 +1,252 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
||||
#ifndef _UAPI__LINUX_NETLINK_H
|
||||
#define _UAPI__LINUX_NETLINK_H
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/socket.h> /* for __kernel_sa_family_t */
|
||||
#include <linux/types.h>
|
||||
|
||||
#define NETLINK_ROUTE 0 /* Routing/device hook */
|
||||
#define NETLINK_UNUSED 1 /* Unused number */
|
||||
#define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */
|
||||
#define NETLINK_FIREWALL 3 /* Unused number, formerly ip_queue */
|
||||
#define NETLINK_SOCK_DIAG 4 /* socket monitoring */
|
||||
#define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */
|
||||
#define NETLINK_XFRM 6 /* ipsec */
|
||||
#define NETLINK_SELINUX 7 /* SELinux event notifications */
|
||||
#define NETLINK_ISCSI 8 /* Open-iSCSI */
|
||||
#define NETLINK_AUDIT 9 /* auditing */
|
||||
#define NETLINK_FIB_LOOKUP 10
|
||||
#define NETLINK_CONNECTOR 11
|
||||
#define NETLINK_NETFILTER 12 /* netfilter subsystem */
|
||||
#define NETLINK_IP6_FW 13
|
||||
#define NETLINK_DNRTMSG 14 /* DECnet routing messages */
|
||||
#define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */
|
||||
#define NETLINK_GENERIC 16
|
||||
/* leave room for NETLINK_DM (DM Events) */
|
||||
#define NETLINK_SCSITRANSPORT 18 /* SCSI Transports */
|
||||
#define NETLINK_ECRYPTFS 19
|
||||
#define NETLINK_RDMA 20
|
||||
#define NETLINK_CRYPTO 21 /* Crypto layer */
|
||||
#define NETLINK_SMC 22 /* SMC monitoring */
|
||||
|
||||
#define NETLINK_INET_DIAG NETLINK_SOCK_DIAG
|
||||
|
||||
#define MAX_LINKS 32
|
||||
|
||||
struct sockaddr_nl {
|
||||
__kernel_sa_family_t nl_family; /* AF_NETLINK */
|
||||
unsigned short nl_pad; /* zero */
|
||||
__u32 nl_pid; /* port ID */
|
||||
__u32 nl_groups; /* multicast groups mask */
|
||||
};
|
||||
|
||||
struct nlmsghdr {
|
||||
__u32 nlmsg_len; /* Length of message including header */
|
||||
__u16 nlmsg_type; /* Message content */
|
||||
__u16 nlmsg_flags; /* Additional flags */
|
||||
__u32 nlmsg_seq; /* Sequence number */
|
||||
__u32 nlmsg_pid; /* Sending process port ID */
|
||||
};
|
||||
|
||||
/* Flags values */
|
||||
|
||||
#define NLM_F_REQUEST 0x01 /* It is request message. */
|
||||
#define NLM_F_MULTI 0x02 /* Multipart message, terminated by NLMSG_DONE */
|
||||
#define NLM_F_ACK 0x04 /* Reply with ack, with zero or error code */
|
||||
#define NLM_F_ECHO 0x08 /* Echo this request */
|
||||
#define NLM_F_DUMP_INTR 0x10 /* Dump was inconsistent due to sequence change */
|
||||
#define NLM_F_DUMP_FILTERED 0x20 /* Dump was filtered as requested */
|
||||
|
||||
/* Modifiers to GET request */
|
||||
#define NLM_F_ROOT 0x100 /* specify tree root */
|
||||
#define NLM_F_MATCH 0x200 /* return all matching */
|
||||
#define NLM_F_ATOMIC 0x400 /* atomic GET */
|
||||
#define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH)
|
||||
|
||||
/* Modifiers to NEW request */
|
||||
#define NLM_F_REPLACE 0x100 /* Override existing */
|
||||
#define NLM_F_EXCL 0x200 /* Do not touch, if it exists */
|
||||
#define NLM_F_CREATE 0x400 /* Create, if it does not exist */
|
||||
#define NLM_F_APPEND 0x800 /* Add to end of list */
|
||||
|
||||
/* Modifiers to DELETE request */
|
||||
#define NLM_F_NONREC 0x100 /* Do not delete recursively */
|
||||
|
||||
/* Flags for ACK message */
|
||||
#define NLM_F_CAPPED 0x100 /* request was capped */
|
||||
#define NLM_F_ACK_TLVS 0x200 /* extended ACK TVLs were included */
|
||||
|
||||
/*
|
||||
4.4BSD ADD NLM_F_CREATE|NLM_F_EXCL
|
||||
4.4BSD CHANGE NLM_F_REPLACE
|
||||
|
||||
True CHANGE NLM_F_CREATE|NLM_F_REPLACE
|
||||
Append NLM_F_CREATE
|
||||
Check NLM_F_EXCL
|
||||
*/
|
||||
|
||||
#define NLMSG_ALIGNTO 4U
|
||||
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
|
||||
#define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
|
||||
#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
|
||||
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
|
||||
#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
|
||||
#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
|
||||
(struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
|
||||
#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
|
||||
(nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
|
||||
(int)((nlh)->nlmsg_len) <= (len))
|
||||
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
|
||||
|
||||
#define NLMSG_NOOP 0x1 /* Nothing. */
|
||||
#define NLMSG_ERROR 0x2 /* Error */
|
||||
#define NLMSG_DONE 0x3 /* End of a dump */
|
||||
#define NLMSG_OVERRUN 0x4 /* Data lost */
|
||||
|
||||
#define NLMSG_MIN_TYPE 0x10 /* < 0x10: reserved control messages */
|
||||
|
||||
struct nlmsgerr {
|
||||
int error;
|
||||
struct nlmsghdr msg;
|
||||
/*
|
||||
* followed by the message contents unless NETLINK_CAP_ACK was set
|
||||
* or the ACK indicates success (error == 0)
|
||||
* message length is aligned with NLMSG_ALIGN()
|
||||
*/
|
||||
/*
|
||||
* followed by TLVs defined in enum nlmsgerr_attrs
|
||||
* if NETLINK_EXT_ACK was set
|
||||
*/
|
||||
};
|
||||
|
||||
/**
|
||||
* enum nlmsgerr_attrs - nlmsgerr attributes
|
||||
* @NLMSGERR_ATTR_UNUSED: unused
|
||||
* @NLMSGERR_ATTR_MSG: error message string (string)
|
||||
* @NLMSGERR_ATTR_OFFS: offset of the invalid attribute in the original
|
||||
* message, counting from the beginning of the header (u32)
|
||||
* @NLMSGERR_ATTR_COOKIE: arbitrary subsystem specific cookie to
|
||||
* be used - in the success case - to identify a created
|
||||
* object or operation or similar (binary)
|
||||
* @__NLMSGERR_ATTR_MAX: number of attributes
|
||||
* @NLMSGERR_ATTR_MAX: highest attribute number
|
||||
*/
|
||||
enum nlmsgerr_attrs {
|
||||
NLMSGERR_ATTR_UNUSED,
|
||||
NLMSGERR_ATTR_MSG,
|
||||
NLMSGERR_ATTR_OFFS,
|
||||
NLMSGERR_ATTR_COOKIE,
|
||||
|
||||
__NLMSGERR_ATTR_MAX,
|
||||
NLMSGERR_ATTR_MAX = __NLMSGERR_ATTR_MAX - 1
|
||||
};
|
||||
|
||||
#define NETLINK_ADD_MEMBERSHIP 1
|
||||
#define NETLINK_DROP_MEMBERSHIP 2
|
||||
#define NETLINK_PKTINFO 3
|
||||
#define NETLINK_BROADCAST_ERROR 4
|
||||
#define NETLINK_NO_ENOBUFS 5
|
||||
#ifndef __KERNEL__
|
||||
#define NETLINK_RX_RING 6
|
||||
#define NETLINK_TX_RING 7
|
||||
#endif
|
||||
#define NETLINK_LISTEN_ALL_NSID 8
|
||||
#define NETLINK_LIST_MEMBERSHIPS 9
|
||||
#define NETLINK_CAP_ACK 10
|
||||
#define NETLINK_EXT_ACK 11
|
||||
#define NETLINK_GET_STRICT_CHK 12
|
||||
|
||||
struct nl_pktinfo {
|
||||
__u32 group;
|
||||
};
|
||||
|
||||
struct nl_mmap_req {
|
||||
unsigned int nm_block_size;
|
||||
unsigned int nm_block_nr;
|
||||
unsigned int nm_frame_size;
|
||||
unsigned int nm_frame_nr;
|
||||
};
|
||||
|
||||
struct nl_mmap_hdr {
|
||||
unsigned int nm_status;
|
||||
unsigned int nm_len;
|
||||
__u32 nm_group;
|
||||
/* credentials */
|
||||
__u32 nm_pid;
|
||||
__u32 nm_uid;
|
||||
__u32 nm_gid;
|
||||
};
|
||||
|
||||
#ifndef __KERNEL__
|
||||
enum nl_mmap_status {
|
||||
NL_MMAP_STATUS_UNUSED,
|
||||
NL_MMAP_STATUS_RESERVED,
|
||||
NL_MMAP_STATUS_VALID,
|
||||
NL_MMAP_STATUS_COPY,
|
||||
NL_MMAP_STATUS_SKIP,
|
||||
};
|
||||
|
||||
#define NL_MMAP_MSG_ALIGNMENT NLMSG_ALIGNTO
|
||||
#define NL_MMAP_MSG_ALIGN(sz) __ALIGN_KERNEL(sz, NL_MMAP_MSG_ALIGNMENT)
|
||||
#define NL_MMAP_HDRLEN NL_MMAP_MSG_ALIGN(sizeof(struct nl_mmap_hdr))
|
||||
#endif
|
||||
|
||||
#define NET_MAJOR 36 /* Major 36 is reserved for networking */
|
||||
|
||||
enum {
|
||||
NETLINK_UNCONNECTED = 0,
|
||||
NETLINK_CONNECTED,
|
||||
};
|
||||
|
||||
/*
|
||||
* <------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)-->
|
||||
* +---------------------+- - -+- - - - - - - - - -+- - -+
|
||||
* | Header | Pad | Payload | Pad |
|
||||
* | (struct nlattr) | ing | | ing |
|
||||
* +---------------------+- - -+- - - - - - - - - -+- - -+
|
||||
* <-------------- nlattr->nla_len -------------->
|
||||
*/
|
||||
|
||||
struct nlattr {
|
||||
__u16 nla_len;
|
||||
__u16 nla_type;
|
||||
};
|
||||
|
||||
/*
|
||||
* nla_type (16 bits)
|
||||
* +---+---+-------------------------------+
|
||||
* | N | O | Attribute Type |
|
||||
* +---+---+-------------------------------+
|
||||
* N := Carries nested attributes
|
||||
* O := Payload stored in network byte order
|
||||
*
|
||||
* Note: The N and O flag are mutually exclusive.
|
||||
*/
|
||||
#define NLA_F_NESTED (1 << 15)
|
||||
#define NLA_F_NET_BYTEORDER (1 << 14)
|
||||
#define NLA_TYPE_MASK ~(NLA_F_NESTED | NLA_F_NET_BYTEORDER)
|
||||
|
||||
#define NLA_ALIGNTO 4
|
||||
#define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1))
|
||||
#define NLA_HDRLEN ((int) NLA_ALIGN(sizeof(struct nlattr)))
|
||||
|
||||
/* Generic 32 bitflags attribute content sent to the kernel.
|
||||
*
|
||||
* The value is a bitmap that defines the values being set
|
||||
* The selector is a bitmask that defines which value is legit
|
||||
*
|
||||
* Examples:
|
||||
* value = 0x0, and selector = 0x1
|
||||
* implies we are selecting bit 1 and we want to set its value to 0.
|
||||
*
|
||||
* value = 0x2, and selector = 0x2
|
||||
* implies we are selecting bit 2 and we want to set its value to 1.
|
||||
*
|
||||
*/
|
||||
struct nla_bitfield32 {
|
||||
__u32 value;
|
||||
__u32 selector;
|
||||
};
|
||||
|
||||
#endif /* _UAPI__LINUX_NETLINK_H */
|
||||
688
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cli.c
Executable file
688
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cli.c
Executable file
@@ -0,0 +1,688 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <net/if.h>
|
||||
#include <poll.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "isobusfs_cli.h"
|
||||
|
||||
/* Maximal number of events that can be registered. The number is
|
||||
* based on feeling not on any real data.
|
||||
*/
|
||||
#define ISOBUSFS_CLI_MAX_EVENTS 10
|
||||
|
||||
int isobusfs_cli_register_event(struct isobusfs_priv *priv,
|
||||
const struct isobusfs_event *new_event)
|
||||
{
|
||||
if (!priv->events) {
|
||||
priv->events = malloc(sizeof(*new_event) *
|
||||
ISOBUSFS_CLI_MAX_EVENTS);
|
||||
if (!priv->events)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->max_events = ISOBUSFS_CLI_MAX_EVENTS;
|
||||
} else if (priv->num_events >= priv->max_events) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(&priv->events[priv->num_events], new_event,
|
||||
sizeof(*new_event));
|
||||
priv->num_events++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int isobusfs_cli_remove_event(struct isobusfs_priv *priv,
|
||||
struct isobusfs_event *event_to_remove)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < priv->num_events; ++i) {
|
||||
if (&priv->events[i] == event_to_remove) {
|
||||
memmove(&priv->events[i], &priv->events[i + 1],
|
||||
(priv->num_events - i - 1) *
|
||||
sizeof(*priv->events));
|
||||
priv->num_events--;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
struct timespec ms_to_timespec(unsigned int timeout_ms)
|
||||
{
|
||||
struct timespec ts;
|
||||
|
||||
ts.tv_sec = timeout_ms / 1000;
|
||||
ts.tv_nsec = (timeout_ms % 1000) * 1000000;
|
||||
|
||||
return ts;
|
||||
}
|
||||
|
||||
void isobusfs_cli_prepare_response_event(struct isobusfs_event *event, int sock,
|
||||
uint8_t fs_function)
|
||||
{
|
||||
struct timespec current_time, timeout_timespec;
|
||||
|
||||
event->fd = sock;
|
||||
event->fs_function = fs_function;
|
||||
|
||||
/* Calculate the timeout */
|
||||
clock_gettime(CLOCK_REALTIME, ¤t_time);
|
||||
timeout_timespec = ms_to_timespec(ISOBUSFS_CLI_DEFAULT_WAIT_TIMEOUT_MS);
|
||||
event->timeout.tv_sec = current_time.tv_sec + timeout_timespec.tv_sec;
|
||||
event->timeout.tv_nsec = current_time.tv_nsec + timeout_timespec.tv_nsec;
|
||||
|
||||
/* Adjust for nanosecond overflow */
|
||||
if (event->timeout.tv_nsec >= 1000000000) {
|
||||
event->timeout.tv_nsec -= 1000000000;
|
||||
event->timeout.tv_sec++;
|
||||
}
|
||||
|
||||
event->one_shot = true;
|
||||
}
|
||||
|
||||
static bool isobusfs_cli_has_event_expired(const struct timespec *timeout)
|
||||
{
|
||||
struct timespec now;
|
||||
|
||||
clock_gettime(CLOCK_REALTIME, &now);
|
||||
return now.tv_sec > timeout->tv_sec ||
|
||||
(now.tv_sec == timeout->tv_sec &&
|
||||
now.tv_nsec > timeout->tv_nsec);
|
||||
}
|
||||
|
||||
static void isobusfs_cli_process_expired_events(struct isobusfs_priv *priv)
|
||||
{
|
||||
for (size_t i = 0; i < priv->num_events; ++i) {
|
||||
struct isobusfs_event *event = &priv->events[i];
|
||||
|
||||
if (isobusfs_cli_has_event_expired(&event->timeout)) {
|
||||
event->cb(priv, NULL, event->ctx, -ETIME);
|
||||
|
||||
isobusfs_cli_remove_event(priv, event);
|
||||
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int isobusfs_cli_rx_event(struct isobusfs_priv *priv, int sock,
|
||||
struct isobusfs_msg *msg, bool *found)
|
||||
{
|
||||
struct isobusfs_event *event = NULL;
|
||||
int ret = 0;
|
||||
|
||||
*found = false;
|
||||
|
||||
for (size_t i = 0; i < priv->num_events; ++i) {
|
||||
event = &priv->events[i];
|
||||
|
||||
if (event->fd == sock && event->fs_function == msg->buf[0]) {
|
||||
if (event->cb)
|
||||
ret = event->cb(priv, msg, event->ctx, 0);
|
||||
|
||||
*found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (*found && event->one_shot)
|
||||
isobusfs_cli_remove_event(priv, event);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int isobusfs_cli_rx(struct isobusfs_priv *priv, struct isobusfs_msg *msg)
|
||||
{
|
||||
int cmd = isobusfs_buf_to_cmd(msg->buf);
|
||||
int ret = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case ISOBUSFS_CG_CONNECTION_MANAGMENT:
|
||||
ret = isobusfs_cli_rx_cg_cm(priv, msg);
|
||||
break;
|
||||
case ISOBUSFS_CG_DIRECTORY_HANDLING:
|
||||
ret = isobusfs_cli_rx_cg_dh(priv, msg);
|
||||
break;
|
||||
case ISOBUSFS_CG_FILE_ACCESS: /* fall through */
|
||||
ret = isobusfs_cli_rx_cg_fa(priv, msg);
|
||||
break;
|
||||
case ISOBUSFS_CG_FILE_HANDLING: /* fall through */
|
||||
case ISOBUSFS_CG_VOLUME_HANDLING: /* fall through */
|
||||
default:
|
||||
isobusfs_send_nack(priv->sock_nack, msg);
|
||||
pr_warn("unsupported command group: %i", cmd);
|
||||
/* Not a critical error */
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int isobusfs_cli_rx_ack(struct isobusfs_priv *priv, struct isobusfs_msg *msg)
|
||||
{
|
||||
enum isobusfs_ack_ctrl ctrl = msg->buf[0];
|
||||
|
||||
switch (ctrl) {
|
||||
case ISOBUS_ACK_CTRL_ACK:
|
||||
/* received ACK unexpectedly an ACK, no idea what to do */
|
||||
pr_debug("< rx: ACK?????");
|
||||
break;
|
||||
case ISOBUS_ACK_CTRL_NACK:
|
||||
/* we did something wrong */
|
||||
pr_debug("< rx: NACK!!!!!!");
|
||||
/* try to provide some usable information with a trace of
|
||||
* the TX history
|
||||
*/
|
||||
isobusfs_dump_tx_data(&priv->tx_buf_log);
|
||||
priv->state = ISOBUSFS_CLI_STATE_IDLE;
|
||||
break;
|
||||
default:
|
||||
pr_warn("%s: unsupported ACK control: %i", __func__, ctrl);
|
||||
}
|
||||
|
||||
/* Not a critical error */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isobusfs_cli_rx_buf(struct isobusfs_priv *priv, struct isobusfs_msg *msg)
|
||||
{
|
||||
pgn_t pgn = msg->peername.can_addr.j1939.pgn;
|
||||
int ret;
|
||||
|
||||
switch (pgn) {
|
||||
case ISOBUSFS_PGN_FS_TO_CL:
|
||||
ret = isobusfs_cli_rx(priv, msg);
|
||||
break;
|
||||
case ISOBUS_PGN_ACK:
|
||||
ret = isobusfs_cli_rx_ack(priv, msg);
|
||||
break;
|
||||
default:
|
||||
pr_warn("%s: unsupported PGN: %x", __func__, pgn);
|
||||
/* Not a critical error */
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int isobusfs_cli_rx_one(struct isobusfs_priv *priv, int sock)
|
||||
{
|
||||
struct isobusfs_msg *msg;
|
||||
int flags = 0;
|
||||
bool found;
|
||||
int ret;
|
||||
|
||||
msg = malloc(sizeof(*msg));
|
||||
if (!msg) {
|
||||
pr_err("can't allocate rx msg struct\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
msg->buf_size = ISOBUSFS_MAX_TRANSFER_LENGH;
|
||||
msg->peer_addr_len = sizeof(msg->peername);
|
||||
msg->sock = sock;
|
||||
|
||||
ret = recvfrom(sock, &msg->buf[0], msg->buf_size, flags,
|
||||
(struct sockaddr *)&msg->peername, &msg->peer_addr_len);
|
||||
|
||||
if (ret < 0) {
|
||||
ret = -errno;
|
||||
pr_warn("recvfrom() failed: %i %s", ret, strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ret < ISOBUSFS_MIN_TRANSFER_LENGH) {
|
||||
pr_warn("buf is less then min transfer: %i\n", ret);
|
||||
isobusfs_send_nack(priv->sock_nack, msg);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
msg->len = ret;
|
||||
|
||||
ret = isobusfs_cli_rx_event(priv, sock, msg, &found);
|
||||
if (ret < 0) {
|
||||
pr_warn("failed to process rx event: %i (%s)\n", ret, strerror(ret));
|
||||
return ret;
|
||||
} else if (found)
|
||||
return 0;
|
||||
|
||||
ret = isobusfs_cli_rx_buf(priv, msg);
|
||||
if (ret < 0) {
|
||||
pr_warn("failed to process rx buf: %i (%s)\n", ret, strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isobusfs_cli_handle_events(struct isobusfs_priv *priv, unsigned int nfds)
|
||||
{
|
||||
int ret;
|
||||
unsigned int n;
|
||||
|
||||
for (n = 0; n < nfds && n < priv->cmn.epoll_events_size; ++n) {
|
||||
struct epoll_event *ev = &priv->cmn.epoll_events[n];
|
||||
|
||||
if (!ev->events) {
|
||||
warn("no events");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ev->data.fd == priv->sock_ccm) {
|
||||
if (ev->events & POLLERR) {
|
||||
struct isobusfs_err_msg emsg = {
|
||||
.stats = &priv->stats,
|
||||
};
|
||||
|
||||
ret = isobusfs_recv_err(priv->sock_ccm, &emsg);
|
||||
if (ret)
|
||||
pr_warn("error queue reported error: %i", ret);
|
||||
}
|
||||
} else if (ev->data.fd == STDIN_FILENO) {
|
||||
if (!priv->interactive) {
|
||||
warn("got POLLIN on stdin, but interactive mode is disabled");
|
||||
continue;
|
||||
}
|
||||
if (ev->events & POLLIN) {
|
||||
ret = isobusfs_cli_interactive(priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else
|
||||
warn("got not POLLIN on stdin");
|
||||
} else if (ev->events & POLLIN) {
|
||||
ret = isobusfs_cli_rx_one(priv, ev->data.fd);
|
||||
if (ret) {
|
||||
warn("recv one");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isobusfs_cli_handle_periodic_tasks(struct isobusfs_priv *priv)
|
||||
{
|
||||
/* detect FS timeout */
|
||||
isobusfs_cli_fs_detect_timeout(priv);
|
||||
|
||||
isobusfs_cli_run_self_tests(priv);
|
||||
|
||||
isobusfs_cli_process_expired_events(priv);
|
||||
|
||||
/* this function will send status only if it is proper time to do so */
|
||||
return isobusfs_cli_ccm_send(priv);
|
||||
}
|
||||
|
||||
int isobusfs_cli_process_events_and_tasks(struct isobusfs_priv *priv)
|
||||
{
|
||||
bool dont_wait = false;
|
||||
int nfds = 0;
|
||||
int ret;
|
||||
|
||||
if (priv->state == ISOBUSFS_CLI_STATE_SELFTEST)
|
||||
dont_wait = true;
|
||||
|
||||
ret = libj1939_prepare_for_events(&priv->cmn, &nfds, dont_wait);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (nfds > 0) {
|
||||
ret = isobusfs_cli_handle_events(priv, (unsigned int)nfds);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return isobusfs_cli_handle_periodic_tasks(priv);
|
||||
}
|
||||
|
||||
static int isobusfs_cli_sock_main_prepare(struct isobusfs_priv *priv)
|
||||
{
|
||||
struct sockaddr_can addr = priv->sockname;
|
||||
int ret;
|
||||
|
||||
ret = libj1939_open_socket();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
priv->sock_main = ret;
|
||||
|
||||
/* TODO: this is TX only socket */
|
||||
addr.can_addr.j1939.pgn = ISOBUSFS_PGN_FS_TO_CL;
|
||||
ret = libj1939_bind_socket(priv->sock_main, &addr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = isobusfs_cmn_set_linger(priv->sock_main);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = libj1939_socket_prio(priv->sock_main, ISOBUSFS_PRIO_DEFAULT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = isobusfs_cmn_connect_socket(priv->sock_main, &priv->peername);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return libj1939_add_socket_to_epoll(priv->cmn.epoll_fd,
|
||||
priv->sock_main, EPOLLIN);
|
||||
}
|
||||
|
||||
/* isobusfs_cli_sock_int_prepare() is used to prepare stdin for interactive
|
||||
* mode.
|
||||
*/
|
||||
static int isobusfs_cli_sock_int_prepare(struct isobusfs_priv *priv)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!priv->interactive)
|
||||
return 0;
|
||||
|
||||
isobusfs_set_interactive(true);
|
||||
|
||||
ret = fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return libj1939_add_socket_to_epoll(priv->cmn.epoll_fd,
|
||||
STDIN_FILENO, EPOLLIN);
|
||||
}
|
||||
|
||||
static int isobusfs_cli_sock_ccm_prepare(struct isobusfs_priv *priv)
|
||||
{
|
||||
struct sockaddr_can addr = priv->sockname;
|
||||
int ret;
|
||||
|
||||
ret = libj1939_open_socket();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
priv->sock_ccm = ret;
|
||||
|
||||
ret = isobusfs_cmn_configure_error_queue(priv->sock_ccm);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* TODO: this is TX only socket */
|
||||
addr.can_addr.j1939.pgn = J1939_NO_PGN;
|
||||
ret = libj1939_bind_socket(priv->sock_ccm, &addr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = isobusfs_cmn_set_linger(priv->sock_ccm);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = libj1939_socket_prio(priv->sock_ccm, ISOBUSFS_PRIO_DEFAULT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = isobusfs_cmn_connect_socket(priv->sock_ccm, &priv->peername);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* poll for errors to get confirmation if our packets are send */
|
||||
return libj1939_add_socket_to_epoll(priv->cmn.epoll_fd, priv->sock_ccm,
|
||||
EPOLLERR);
|
||||
}
|
||||
|
||||
static int isobusfs_cli_sock_nack_prepare(struct isobusfs_priv *priv)
|
||||
{
|
||||
struct sockaddr_can addr = priv->sockname;
|
||||
int ret;
|
||||
|
||||
ret = libj1939_open_socket();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
priv->sock_nack = ret;
|
||||
|
||||
addr.can_addr.j1939.pgn = ISOBUS_PGN_ACK;
|
||||
ret = libj1939_bind_socket(priv->sock_nack, &addr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = libj1939_socket_prio(priv->sock_nack, ISOBUSFS_PRIO_ACK);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* poll for errors to get confirmation if our packets are send */
|
||||
return libj1939_add_socket_to_epoll(priv->cmn.epoll_fd,
|
||||
priv->sock_nack, EPOLLIN);
|
||||
}
|
||||
|
||||
/* rx socket for fss and volume status announcements */
|
||||
static int isobusfs_cli_sock_bcast_prepare(struct isobusfs_priv *priv)
|
||||
{
|
||||
struct sockaddr_can addr = priv->sockname;
|
||||
int ret;
|
||||
|
||||
ret = libj1939_open_socket();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
priv->sock_bcast_rx = ret;
|
||||
|
||||
/* keep address and name and overwrite PGN */
|
||||
addr.can_addr.j1939.name = J1939_NO_NAME;
|
||||
addr.can_addr.j1939.addr = J1939_NO_ADDR;
|
||||
addr.can_addr.j1939.pgn = ISOBUSFS_PGN_FS_TO_CL;
|
||||
ret = libj1939_bind_socket(priv->sock_bcast_rx, &addr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = libj1939_set_broadcast(priv->sock_bcast_rx);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = isobusfs_cmn_connect_socket(priv->sock_bcast_rx, &priv->peername);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return libj1939_add_socket_to_epoll(priv->cmn.epoll_fd, priv->sock_bcast_rx,
|
||||
EPOLLIN);
|
||||
}
|
||||
|
||||
static int isobusfs_cli_sock_prepare(struct isobusfs_priv *priv)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = libj1939_create_epoll();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
priv->cmn.epoll_fd = ret;
|
||||
|
||||
priv->cmn.epoll_events = calloc(ISOBUSFS_CLI_MAX_EPOLL_EVENTS,
|
||||
sizeof(struct epoll_event));
|
||||
if (!priv->cmn.epoll_events)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->cmn.epoll_events_size = ISOBUSFS_CLI_MAX_EPOLL_EVENTS;
|
||||
|
||||
ret = isobusfs_cli_sock_int_prepare(priv);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = isobusfs_cli_sock_ccm_prepare(priv);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = isobusfs_cli_sock_bcast_prepare(priv);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = isobusfs_cli_sock_main_prepare(priv);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return isobusfs_cli_sock_nack_prepare(priv);
|
||||
}
|
||||
|
||||
static void isobusfs_cli_print_help(void)
|
||||
{
|
||||
printf("Usage: isobusfs-cli [options]\n");
|
||||
printf("Options:\n");
|
||||
printf(" --interactive or -I (Default)\n");
|
||||
printf(" --interface <interface_name> or -i <interface_name>\n");
|
||||
printf(" --local-address <local_address_hex> or -a <local_address_hex>\n");
|
||||
printf(" --local-name <local_name_hex> or -n <local_name_hex>\n");
|
||||
printf(" --log-level <logging_level> or -l <logging_level> (Default %d)\n",
|
||||
LOG_LEVEL_INFO);
|
||||
printf(" --remote-address <remote_address_hex> or -r <remote_address_hex>\n");
|
||||
printf(" --remote-name <remote_name_hex> or -m <remote_name_hex>\n");
|
||||
printf("Note: Local address and local name are mutually exclusive\n");
|
||||
printf("Note: Remote address and remote name are mutually exclusive\n");
|
||||
}
|
||||
|
||||
static int isobusfs_cli_parse_args(struct isobusfs_priv *priv, int argc, char *argv[])
|
||||
{
|
||||
struct sockaddr_can *remote = &priv->peername;
|
||||
struct sockaddr_can *local = &priv->sockname;
|
||||
bool local_address_set = false;
|
||||
bool local_name_set = false;
|
||||
bool remote_address_set = false;
|
||||
bool remote_name_set = false;
|
||||
bool interface_set = false;
|
||||
int long_index = 0;
|
||||
int level;
|
||||
int opt;
|
||||
|
||||
static struct option long_options[] = {
|
||||
{"interface", required_argument, 0, 'i'},
|
||||
{"interactive", no_argument, 0, 'I'},
|
||||
{"local-address", required_argument, 0, 'a'},
|
||||
{"local-name", required_argument, 0, 'n'},
|
||||
{"log-level", required_argument, 0, 'l'},
|
||||
{"remote-address", required_argument, 0, 'r'},
|
||||
{"remote-name", required_argument, 0, 'm'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
/* active by default */
|
||||
priv->interactive = true;
|
||||
|
||||
while ((opt = getopt_long(argc, argv, "a:n:r:m:Ii:l:", long_options, &long_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 'a':
|
||||
local->can_addr.j1939.addr = strtoul(optarg, NULL, 16);
|
||||
local_address_set = true;
|
||||
break;
|
||||
case 'n':
|
||||
local->can_addr.j1939.name = strtoull(optarg, NULL, 16);
|
||||
local_name_set = true;
|
||||
break;
|
||||
case 'r':
|
||||
remote->can_addr.j1939.addr = strtoul(optarg, NULL, 16);
|
||||
remote_address_set = true;
|
||||
break;
|
||||
case 'm':
|
||||
remote->can_addr.j1939.name = strtoull(optarg, NULL, 16);
|
||||
remote_name_set = true;
|
||||
break;
|
||||
case 'i':
|
||||
local->can_ifindex = if_nametoindex(optarg);
|
||||
if (!local->can_ifindex) {
|
||||
pr_err("Interface %s not found. Error: %d (%s)\n",
|
||||
optarg, errno, strerror(errno));
|
||||
return -EINVAL;
|
||||
}
|
||||
remote->can_ifindex = local->can_ifindex;
|
||||
interface_set = true;
|
||||
break;
|
||||
case 'I':
|
||||
priv->interactive = true;
|
||||
break;
|
||||
case 'l':
|
||||
level = strtoul(optarg, NULL, 0);
|
||||
if (level < LOG_LEVEL_ERROR || level > LOG_LEVEL_DEBUG)
|
||||
pr_err("invalid debug level %d", level);
|
||||
isobusfs_log_level_set(level);
|
||||
break;
|
||||
default:
|
||||
isobusfs_cli_print_help();
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!interface_set) {
|
||||
pr_err("interface not specified");
|
||||
isobusfs_cli_print_help();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((local_address_set && local_name_set) ||
|
||||
(remote_address_set && remote_name_set)) {
|
||||
pr_err("local address and local name or remote address and remote name are mutually exclusive");
|
||||
isobusfs_cli_print_help();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct isobusfs_priv *priv;
|
||||
struct timespec ts;
|
||||
int ret;
|
||||
|
||||
priv = malloc(sizeof(*priv));
|
||||
if (!priv)
|
||||
err(EXIT_FAILURE, "can't allocate priv");
|
||||
|
||||
bzero(priv, sizeof(*priv));
|
||||
|
||||
libj1939_init_sockaddr_can(&priv->sockname, J1939_NO_PGN);
|
||||
libj1939_init_sockaddr_can(&priv->peername, ISOBUSFS_PGN_CL_TO_FS);
|
||||
|
||||
ret = isobusfs_cli_parse_args(priv, argc, argv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = isobusfs_cli_sock_prepare(priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
isobusfs_cli_ccm_init(priv);
|
||||
|
||||
/* Init next st_next_send_time value to avoid warnings */
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
priv->cmn.next_send_time = ts;
|
||||
|
||||
if (priv->interactive)
|
||||
isobusfs_cli_int_start(priv);
|
||||
else
|
||||
pr_debug("starting client\n");
|
||||
|
||||
while (1) {
|
||||
ret = isobusfs_cli_process_events_and_tasks(priv);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
close(priv->cmn.epoll_fd);
|
||||
free(priv->cmn.epoll_events);
|
||||
|
||||
close(priv->sock_main);
|
||||
close(priv->sock_nack);
|
||||
close(priv->sock_ccm);
|
||||
close(priv->sock_bcast_rx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
215
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cli.h
Executable file
215
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cli.h
Executable file
@@ -0,0 +1,215 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#ifndef ISOBUSFS_CLI_H
|
||||
#define ISOBUSFS_CLI_H
|
||||
|
||||
#include <sys/epoll.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "isobusfs_cmn.h"
|
||||
#include "isobusfs_cmn_cm.h"
|
||||
|
||||
#define ISOBUSFS_CLI_MAX_EPOLL_EVENTS 10
|
||||
#define ISOBUSFS_CLI_DEFAULT_WAIT_TIMEOUT_MS 1000 /* ms */
|
||||
|
||||
enum isobusfs_cli_state {
|
||||
ISOBUSFS_CLI_STATE_CONNECTING,
|
||||
ISOBUSFS_CLI_STATE_IDLE,
|
||||
ISOBUSFS_CLI_STATE_NACKED, /* here is NACKed and not what you think */
|
||||
ISOBUSFS_CLI_STATE_SELFTEST,
|
||||
ISOBUSFS_CLI_STATE_WAIT_FS_PROPERTIES,
|
||||
ISOBUSFS_CLI_STATE_WAIT_CURRENT_DIR,
|
||||
ISOBUSFS_CLI_STATE_WAIT_CCD_RESP,
|
||||
ISOBUSFS_CLI_STATE_WAIT_OF_RESP,
|
||||
ISOBUSFS_CLI_STATE_WAIT_FILE_SIZE,
|
||||
ISOBUSFS_CLI_STATE_WAIT_FILE,
|
||||
ISOBUSFS_CLI_STATE_WAIT_VOLUME_STATUS,
|
||||
ISOBUSFS_CLI_STATE_WAIT_CF_RESP,
|
||||
ISOBUSFS_CLI_STATE_WAIT_SF_RESP, /* wait for seek file response */
|
||||
ISOBUSFS_CLI_STATE_WAIT_RF_RESP, /* wait for read file response */
|
||||
ISOBUSFS_CLI_STATE_MAX_WAITING,
|
||||
|
||||
ISOBUSFS_CLI_STATE_CONNECTING_DONE,
|
||||
ISOBUSFS_CLI_STATE_GET_FS_PROPERTIES_DONE,
|
||||
ISOBUSFS_CLI_STATE_GET_CURRENT_DIR_DONE,
|
||||
ISOBUSFS_CLI_STATE_GET_CURRENT_DIR_FAIL,
|
||||
ISOBUSFS_CLI_STATE_GET_FILE_SIZE_DONE,
|
||||
ISOBUSFS_CLI_STATE_GET_FILE_DONE,
|
||||
ISOBUSFS_CLI_STATE_VOLUME_STATUS_DONE,
|
||||
ISOBUSFS_CLI_STATE_CCD_DONE,
|
||||
ISOBUSFS_CLI_STATE_CCD_FAIL,
|
||||
ISOBUSFS_CLI_STATE_OF_DONE,
|
||||
ISOBUSFS_CLI_STATE_OF_FAIL,
|
||||
ISOBUSFS_CLI_STATE_CF_DONE,
|
||||
ISOBUSFS_CLI_STATE_CF_FAIL,
|
||||
ISOBUSFS_CLI_STATE_SF_DONE,
|
||||
ISOBUSFS_CLI_STATE_SF_FAIL,
|
||||
ISOBUSFS_CLI_STATE_RF_CONT,
|
||||
ISOBUSFS_CLI_STATE_RF_DONE,
|
||||
ISOBUSFS_CLI_STATE_RF_FAIL,
|
||||
ISOBUSFS_CLI_STATE_MAX_DONE,
|
||||
|
||||
ISOBUSFS_CLI_STATE_GET_FS_PROPERTIES,
|
||||
ISOBUSFS_CLI_STATE_GET_CURRENT_DIR,
|
||||
ISOBUSFS_CLI_STATE_GET_FILE_SIZE,
|
||||
ISOBUSFS_CLI_STATE_GET_FILE,
|
||||
ISOBUSFS_CLI_STATE_VOLUME_STATUS,
|
||||
ISOBUSFS_CLI_STATE_TEST_CLEANUP,
|
||||
ISOBUSFS_CLI_STATE_TEST_DONE,
|
||||
ISOBUSFS_CLI_STATE_MAX_ACTIVE,
|
||||
};
|
||||
|
||||
struct isobusfs_priv;
|
||||
|
||||
typedef int (*isobusfs_event_callback)(struct isobusfs_priv *priv,
|
||||
struct isobusfs_msg *msg, void *ctx,
|
||||
int error);
|
||||
|
||||
struct isobusfs_event {
|
||||
isobusfs_event_callback cb;
|
||||
struct timespec timeout;
|
||||
/* fs_function is needed to identify package type for event
|
||||
* subscription
|
||||
*/
|
||||
uint8_t fs_function;
|
||||
int fd;
|
||||
bool one_shot;
|
||||
void *ctx;
|
||||
};
|
||||
|
||||
struct isobusfs_priv {
|
||||
int sock_ccm;
|
||||
int sock_nack;
|
||||
int sock_main;
|
||||
int sock_bcast_rx;
|
||||
struct isobusfs_cm_ccm ccm; /* file server status message */
|
||||
|
||||
bool run_selftest;
|
||||
|
||||
struct sockaddr_can sockname;
|
||||
struct sockaddr_can peername;
|
||||
|
||||
struct isobusfs_stats stats;
|
||||
|
||||
uint8_t next_tan;
|
||||
uint8_t cl_buf[1];
|
||||
|
||||
bool fs_is_active;
|
||||
struct timespec fs_last_seen;
|
||||
uint8_t fs_version;
|
||||
uint8_t fs_max_open_files;
|
||||
uint8_t fs_caps;
|
||||
struct isobusfs_buf_log tx_buf_log;
|
||||
enum isobusfs_cli_state state;
|
||||
|
||||
struct libj1939_cmn cmn;
|
||||
uint8_t handle;
|
||||
|
||||
uint32_t read_offset;
|
||||
uint8_t *read_data;
|
||||
size_t read_data_len;
|
||||
|
||||
bool interactive;
|
||||
bool int_busy;
|
||||
|
||||
struct isobusfs_event *events;
|
||||
uint num_events;
|
||||
uint max_events;
|
||||
|
||||
enum isobusfs_error error_code;
|
||||
};
|
||||
|
||||
/* isobusfs_cli_cm.c */
|
||||
void isobusfs_cli_ccm_init(struct isobusfs_priv *priv);
|
||||
int isobusfs_cli_ccm_send(struct isobusfs_priv *priv);
|
||||
void isobusfs_cli_fs_detect_timeout(struct isobusfs_priv *priv);
|
||||
int isobusfs_cli_rx_cg_cm(struct isobusfs_priv *priv, struct isobusfs_msg *msg);
|
||||
int isobusfs_cli_property_req(struct isobusfs_priv *priv);
|
||||
int isobusfs_cli_volume_status_req(struct isobusfs_priv *priv,
|
||||
uint8_t volume_mode,
|
||||
uint16_t path_name_length,
|
||||
const char *volume_name);
|
||||
|
||||
/* isobusfs_cli_dh.c */
|
||||
int isobusfs_cli_ccd_req(struct isobusfs_priv *priv, const char *name,
|
||||
size_t name_len);
|
||||
int isobusfs_cli_get_current_dir_req(struct isobusfs_priv *priv);
|
||||
int isobusfs_cli_rx_cg_dh(struct isobusfs_priv *priv,
|
||||
struct isobusfs_msg *msg);
|
||||
int isobusfs_cli_send_and_register_ccd_event(struct isobusfs_priv *priv,
|
||||
const char *name,
|
||||
size_t name_len,
|
||||
isobusfs_event_callback cb,
|
||||
void *ctx);
|
||||
int isobusfs_cli_send_and_register_gcd_event(struct isobusfs_priv *priv,
|
||||
isobusfs_event_callback cb,
|
||||
void *ctx);
|
||||
|
||||
/* isobusfs_cli_fa.c */
|
||||
int isobusfs_cli_rx_cg_fa(struct isobusfs_priv *priv,
|
||||
struct isobusfs_msg *msg);
|
||||
int isobusfs_cli_fa_of_req(struct isobusfs_priv *priv, const char *name,
|
||||
size_t name_len, uint8_t flags);
|
||||
int isobusfs_cli_fa_cf_req(struct isobusfs_priv *priv, uint8_t handle);
|
||||
int isobusfs_cli_fa_rf_req(struct isobusfs_priv *priv, uint8_t handle,
|
||||
uint16_t count);
|
||||
int isobusfs_cli_fa_sf_req(struct isobusfs_priv *priv, uint8_t handle,
|
||||
uint8_t position_mode, int32_t offset);
|
||||
|
||||
int isobusfs_cli_send_and_register_fa_of_event(struct isobusfs_priv *priv,
|
||||
const char *name,
|
||||
size_t name_len,
|
||||
uint8_t flags,
|
||||
isobusfs_event_callback cb,
|
||||
void *ctx);
|
||||
int isobusfs_cli_send_and_register_fa_sf_event(struct isobusfs_priv *priv,
|
||||
uint8_t handle,
|
||||
uint8_t position_mode,
|
||||
int32_t offset,
|
||||
isobusfs_event_callback cb,
|
||||
void *ctx);
|
||||
int isobusfs_cli_send_and_register_fa_rf_event(struct isobusfs_priv *priv,
|
||||
uint8_t handle,
|
||||
uint16_t count,
|
||||
isobusfs_event_callback cb,
|
||||
void *ctx);
|
||||
int isobusfs_cli_send_and_register_fa_cf_event(struct isobusfs_priv *priv,
|
||||
uint8_t handle,
|
||||
isobusfs_event_callback cb,
|
||||
void *ctx);
|
||||
|
||||
/* isobusfs_cli_selftests.c */
|
||||
void isobusfs_cli_run_self_tests(struct isobusfs_priv *priv);
|
||||
|
||||
/* isobusfs_cli_int.c */
|
||||
void isobusfs_cli_int_start(struct isobusfs_priv *priv);
|
||||
int isobusfs_cli_interactive(struct isobusfs_priv *priv);
|
||||
|
||||
/* isobusfs_cli.c */
|
||||
int isobusfs_cli_process_events_and_tasks(struct isobusfs_priv *priv);
|
||||
void isobusfs_cli_prepare_response_event(struct isobusfs_event *event, int sock,
|
||||
uint8_t fs_function);
|
||||
int isobusfs_cli_register_event(struct isobusfs_priv *priv,
|
||||
const struct isobusfs_event *new_event);
|
||||
|
||||
static inline uint8_t isobusfs_cli_get_next_tan(struct isobusfs_priv *priv)
|
||||
{
|
||||
return priv->next_tan++;
|
||||
}
|
||||
|
||||
static inline bool isobusfs_cli_tan_is_valid(uint8_t tan,
|
||||
struct isobusfs_priv *priv)
|
||||
{
|
||||
uint8_t expected_tan = priv->next_tan == 0 ? 255 : priv->next_tan - 1;
|
||||
|
||||
if (tan != expected_tan) {
|
||||
pr_err("%s: tan %d is not valid, expected tan %d\n", __func__,
|
||||
tan, expected_tan);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif /* ISOBUSFS_CLI_H */
|
||||
241
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cli_cm.c
Executable file
241
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cli_cm.c
Executable file
@@ -0,0 +1,241 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "isobusfs_cli.h"
|
||||
#include "isobusfs_cmn_cm.h"
|
||||
|
||||
|
||||
int isobusfs_cli_volume_status_req(struct isobusfs_priv *priv,
|
||||
uint8_t volume_mode,
|
||||
uint16_t path_name_length,
|
||||
const char *volume_name)
|
||||
{
|
||||
struct isobusfs_cm_vol_stat_req req;
|
||||
size_t req_size;
|
||||
int ret;
|
||||
|
||||
req.fs_function =
|
||||
isobusfs_cg_function_to_buf(ISOBUSFS_CG_CONNECTION_MANAGMENT,
|
||||
ISOBUSFS_CM_VOLUME_STATUS_REQ);
|
||||
|
||||
req.volume_mode = volume_mode;
|
||||
req.name_len = htole16(path_name_length);
|
||||
req_size = sizeof(req) - sizeof(req.name) + path_name_length;
|
||||
|
||||
memcpy(req.name, volume_name, path_name_length);
|
||||
|
||||
ret = isobusfs_send(priv->sock_main, &req, req_size, &priv->tx_buf_log);
|
||||
if (ret < 0) {
|
||||
ret = -errno;
|
||||
pr_warn("failed to send volume status request: %d (%s)", ret, strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
priv->state = ISOBUSFS_CLI_STATE_WAIT_VOLUME_STATUS;
|
||||
|
||||
pr_debug("> tx: volume status request");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int isobusfs_cli_property_req(struct isobusfs_priv *priv)
|
||||
{
|
||||
uint8_t buf[ISOBUSFS_MIN_TRANSFER_LENGH];
|
||||
int ret;
|
||||
|
||||
/* not used space should be filled with 0xff */
|
||||
memset(buf, 0xff, ARRAY_SIZE(buf));
|
||||
buf[0] = isobusfs_cg_function_to_buf(ISOBUSFS_CG_CONNECTION_MANAGMENT,
|
||||
ISOBUSFS_CM_GET_FS_PROPERTIES);
|
||||
|
||||
/* send property request */
|
||||
ret = isobusfs_send(priv->sock_main, buf, sizeof(buf), &priv->tx_buf_log);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
priv->state = ISOBUSFS_CLI_STATE_WAIT_FS_PROPERTIES;
|
||||
|
||||
pr_debug("> tx: FS property request");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ccm section */
|
||||
void isobusfs_cli_ccm_init(struct isobusfs_priv *priv)
|
||||
{
|
||||
struct isobusfs_cm_ccm *ccm = &priv->ccm;
|
||||
|
||||
ccm->fs_function =
|
||||
isobusfs_cg_function_to_buf(ISOBUSFS_CG_CONNECTION_MANAGMENT,
|
||||
ISOBUSFS_CM_F_FS_STATUS);
|
||||
ccm->version = 2;
|
||||
memset(ccm->reserved, 0xFF, sizeof(ccm->reserved));
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_cli_ccm_send - send periodic file server status messages
|
||||
* @priv: pointer to the isobusfs_priv structure
|
||||
*
|
||||
* Returns 0 on success, -1 on errors.
|
||||
*/
|
||||
int isobusfs_cli_ccm_send(struct isobusfs_priv *priv)
|
||||
{
|
||||
int64_t time_diff;
|
||||
int ret;
|
||||
|
||||
/* Test if it is proper time to send next status message. */
|
||||
time_diff = timespec_diff_ms(&priv->cmn.next_send_time,
|
||||
&priv->cmn.last_time);
|
||||
if (time_diff > ISOBUSFS_CM_F_FS_STATUS_RATE_JITTER) {
|
||||
/* too early to send next message */
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (time_diff < -ISOBUSFS_CM_F_FS_STATUS_RATE_JITTER) {
|
||||
pr_warn("too late to send next fs status message: %ld ms",
|
||||
time_diff);
|
||||
}
|
||||
|
||||
/* Make sure we send the message with the latest stats */
|
||||
if (priv->stats.tskey_sch != priv->stats.tskey_ack)
|
||||
pr_warn("previous message was not acked");
|
||||
|
||||
/* send periodic file servers status messages. */
|
||||
ret = isobusfs_send(priv->sock_ccm, &priv->ccm, sizeof(priv->ccm),
|
||||
&priv->tx_buf_log);
|
||||
if (ret < 0) {
|
||||
pr_err("sendto() failed: %d (%s)", ret, strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
pr_debug("> tx: ccm version: %d", priv->ccm.version);
|
||||
|
||||
priv->cmn.next_send_time = priv->cmn.last_time;
|
||||
timespec_add_ms(&priv->cmn.next_send_time, 2000);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* detect if FS is timeout */
|
||||
void isobusfs_cli_fs_detect_timeout(struct isobusfs_priv *priv)
|
||||
{
|
||||
int64_t time_diff;
|
||||
|
||||
if (!priv->fs_is_active)
|
||||
return;
|
||||
|
||||
time_diff = timespec_diff_ms(&priv->cmn.last_time,
|
||||
&priv->fs_last_seen);
|
||||
if (time_diff > ISOBUSFS_FS_TIMEOUT) {
|
||||
pr_debug("file server timeout");
|
||||
priv->fs_is_active = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* activate FS status if was not active till now */
|
||||
static void isobusfs_cli_fs_activate(struct isobusfs_priv *priv)
|
||||
{
|
||||
if (priv->fs_is_active)
|
||||
return;
|
||||
|
||||
pr_debug("file server detectet");
|
||||
priv->fs_is_active = true;
|
||||
}
|
||||
|
||||
static int isobusfs_cli_rx_fs_status(struct isobusfs_priv *priv,
|
||||
struct isobusfs_msg *msg)
|
||||
{
|
||||
struct isobusfs_cm_fss *fs_status = (void *)msg->buf;
|
||||
int ret = 0;
|
||||
|
||||
if (msg->len != sizeof(*fs_status)) {
|
||||
pr_warn("wrong message length: %d", msg->len);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
isobusfs_cli_fs_activate(priv);
|
||||
|
||||
priv->fs_last_seen = priv->cmn.last_time;
|
||||
pr_debug("< rx: fs status: %x, opened files: %d",
|
||||
fs_status->status, fs_status->num_open_files);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* process FS properties response */
|
||||
static int isobusfs_cli_rx_fs_property_res(struct isobusfs_priv *priv,
|
||||
struct isobusfs_msg *msg)
|
||||
{
|
||||
struct isobusfs_cm_get_fs_props_resp *fs_prop = (void *)msg->buf;
|
||||
int ret = 0;
|
||||
|
||||
if (priv->state != ISOBUSFS_CLI_STATE_WAIT_FS_PROPERTIES) {
|
||||
pr_warn("unexpected fs properties response");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (msg->len != sizeof(*fs_prop)) {
|
||||
pr_warn("wrong message length: %d", msg->len);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
priv->fs_version = fs_prop->version_number;
|
||||
priv->fs_max_open_files = fs_prop->max_open_files;
|
||||
priv->fs_caps = fs_prop->fs_capabilities;
|
||||
|
||||
|
||||
pr_debug("< rx: fs properties: version: %d, max open files: %d, caps: %x",
|
||||
priv->fs_version, priv->fs_max_open_files, priv->fs_caps);
|
||||
|
||||
priv->state = ISOBUSFS_CLI_STATE_GET_FS_PROPERTIES_DONE;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* function to handle ISOBUSFS_CM_VOLUME_STATUS_RES */
|
||||
static int isobusfs_cli_rx_volume_status_res(struct isobusfs_priv *priv,
|
||||
struct isobusfs_msg *msg)
|
||||
{
|
||||
struct isobusfs_cm_vol_stat_res *vol_status = (void *)msg->buf;
|
||||
int ret = 0;
|
||||
|
||||
if (priv->state != ISOBUSFS_CLI_STATE_WAIT_VOLUME_STATUS) {
|
||||
pr_warn("unexpected volume status response");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
pr_debug("< rx: volume status: %x, max time before remove %d, error code %d, path name length %d, name %s",
|
||||
vol_status->volume_status,
|
||||
vol_status->max_time_before_removal,
|
||||
vol_status->error_code,
|
||||
vol_status->name_len,
|
||||
vol_status->name);
|
||||
|
||||
priv->state = ISOBUSFS_CLI_STATE_VOLUME_STATUS_DONE;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Command group: connection management */
|
||||
int isobusfs_cli_rx_cg_cm(struct isobusfs_priv *priv, struct isobusfs_msg *msg)
|
||||
{
|
||||
int func = isobusfs_buf_to_function(msg->buf);
|
||||
int ret = 0;
|
||||
|
||||
switch (func) {
|
||||
case ISOBUSFS_CM_F_FS_STATUS:
|
||||
return isobusfs_cli_rx_fs_status(priv, msg);
|
||||
case ISOBUSFS_CM_GET_FS_PROPERTIES_RES:
|
||||
return isobusfs_cli_rx_fs_property_res(priv, msg);
|
||||
case ISOBUSFS_CM_VOLUME_STATUS_RES:
|
||||
return isobusfs_cli_rx_volume_status_res(priv, msg);
|
||||
default:
|
||||
pr_warn("unsupported function: %i", func);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
232
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cli_dh.c
Executable file
232
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cli_dh.c
Executable file
@@ -0,0 +1,232 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "isobusfs_cli.h"
|
||||
#include "isobusfs_cmn_dh.h"
|
||||
|
||||
/*
|
||||
* C.2.3.2 Change Current Directory Request
|
||||
*/
|
||||
int isobusfs_cli_ccd_req(struct isobusfs_priv *priv, const char *name,
|
||||
size_t name_len)
|
||||
{
|
||||
struct isobusfs_dh_ccd_req *req;
|
||||
size_t req_len = sizeof(*req) + name_len;
|
||||
size_t padding_size = 0;
|
||||
int ret;
|
||||
|
||||
if (name_len > ISOBUSFS_MAX_PATH_NAME_LENGTH) {
|
||||
pr_warn("path name too long: %i, max is %i", name_len,
|
||||
ISOBUSFS_MAX_PATH_NAME_LENGTH);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (req_len < ISOBUSFS_MIN_TRANSFER_LENGH) {
|
||||
/* Update the buffer size accordingly */
|
||||
padding_size = ISOBUSFS_MIN_TRANSFER_LENGH - req_len;
|
||||
req_len = ISOBUSFS_MIN_TRANSFER_LENGH;
|
||||
}
|
||||
|
||||
req = malloc(req_len);
|
||||
if (!req) {
|
||||
pr_err("failed to allocate memory for ccd request");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
req->fs_function = isobusfs_cg_function_to_buf(ISOBUSFS_CG_DIRECTORY_HANDLING,
|
||||
ISOBUSFS_DH_F_CHANGE_CURRENT_DIR_REQ);
|
||||
req->tan = isobusfs_cli_get_next_tan(priv);
|
||||
|
||||
memcpy(&req->name[0], name, name_len);
|
||||
req->name_len = name_len;
|
||||
|
||||
if (padding_size) {
|
||||
/* Fill the rest of the res structure with 0xff */
|
||||
memset(((uint8_t *)req) + req_len - padding_size, 0xff,
|
||||
padding_size);
|
||||
}
|
||||
|
||||
priv->state = ISOBUSFS_CLI_STATE_WAIT_CCD_RESP;
|
||||
ret = isobusfs_send(priv->sock_main, req, req_len, &priv->tx_buf_log);
|
||||
if (ret < 0) {
|
||||
ret = -errno;
|
||||
pr_warn("failed to send ccd request: %d (%s)",
|
||||
ret, strerror(ret));
|
||||
goto free_req;
|
||||
}
|
||||
|
||||
pr_debug("> tx: ccd request for %s", name);
|
||||
|
||||
free_req:
|
||||
free(req);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int isobusfs_cli_dh_ccd_res_log(struct isobusfs_priv *priv,
|
||||
struct isobusfs_msg *msg, void *ctx,
|
||||
int error)
|
||||
{
|
||||
struct isobusfs_dh_ccd_res *res =
|
||||
(struct isobusfs_dh_ccd_res *)msg->buf;
|
||||
|
||||
if (priv->state != ISOBUSFS_CLI_STATE_WAIT_CCD_RESP) {
|
||||
pr_warn("invalid state: %i (expected %i)", priv->state,
|
||||
ISOBUSFS_CLI_STATE_WAIT_CCD_RESP);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!isobusfs_cli_tan_is_valid(res->tan, priv)) {
|
||||
priv->state = ISOBUSFS_CLI_STATE_CCD_FAIL;
|
||||
} else if (res->error_code != 0) {
|
||||
pr_warn("ccd failed with error code: %i", res->error_code);
|
||||
priv->state = ISOBUSFS_CLI_STATE_CCD_FAIL;
|
||||
} else {
|
||||
priv->state = ISOBUSFS_CLI_STATE_CCD_DONE;
|
||||
}
|
||||
|
||||
priv->error_code = res->error_code;
|
||||
if (!error)
|
||||
pr_debug("< rx: change current directory response. Error code: %i",
|
||||
res->error_code);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int isobusfs_cli_send_and_register_ccd_event(struct isobusfs_priv *priv,
|
||||
const char *name,
|
||||
size_t name_len,
|
||||
isobusfs_event_callback cb,
|
||||
void *ctx)
|
||||
{
|
||||
struct isobusfs_event event;
|
||||
uint8_t fs_function;
|
||||
int ret;
|
||||
|
||||
ret = isobusfs_cli_ccd_req(priv, name, name_len);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
fs_function =
|
||||
isobusfs_cg_function_to_buf(ISOBUSFS_CG_DIRECTORY_HANDLING,
|
||||
ISOBUSFS_DH_F_CHANGE_CURRENT_DIR_RES);
|
||||
|
||||
if (cb)
|
||||
event.cb = cb;
|
||||
else
|
||||
event.cb = isobusfs_cli_dh_ccd_res_log;
|
||||
|
||||
event.ctx = ctx;
|
||||
|
||||
isobusfs_cli_prepare_response_event(&event, priv->sock_main,
|
||||
fs_function);
|
||||
|
||||
return isobusfs_cli_register_event(priv, &event);
|
||||
}
|
||||
|
||||
/* function to send current directory request */
|
||||
int isobusfs_cli_get_current_dir_req(struct isobusfs_priv *priv)
|
||||
{
|
||||
struct isobusfs_dh_get_cd_req req;
|
||||
int ret;
|
||||
|
||||
req.fs_function = isobusfs_cg_function_to_buf(ISOBUSFS_CG_DIRECTORY_HANDLING,
|
||||
ISOBUSFS_DH_F_GET_CURRENT_DIR_REQ);
|
||||
req.tan = isobusfs_cli_get_next_tan(priv);
|
||||
|
||||
ret = isobusfs_send(priv->sock_main, &req, sizeof(req), &priv->tx_buf_log);
|
||||
if (ret < 0) {
|
||||
ret = -errno;
|
||||
pr_warn("failed to send current directory request: %d (%s)",
|
||||
ret, strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
priv->state = ISOBUSFS_CLI_STATE_WAIT_CURRENT_DIR;
|
||||
|
||||
pr_debug("> tx: current directory request");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isobusfs_cli_dh_current_dir_res_log(struct isobusfs_priv *priv,
|
||||
struct isobusfs_msg *msg,
|
||||
void *ctx, int error)
|
||||
{
|
||||
struct isobusfs_dh_get_cd_res *res =
|
||||
(struct isobusfs_dh_get_cd_res *)msg->buf;
|
||||
char str[ISOBUSFS_MAX_PATH_NAME_LENGTH];
|
||||
uint16_t total_space, free_space, str_len;
|
||||
|
||||
if (!isobusfs_cli_tan_is_valid(res->tan, priv))
|
||||
pr_warn("invalid tan: %i", res->tan);
|
||||
|
||||
total_space = le16toh(res->total_space);
|
||||
free_space = le16toh(res->free_space);
|
||||
str_len = le16toh(res->name_len);
|
||||
if (str_len > ISOBUSFS_MAX_PATH_NAME_LENGTH) {
|
||||
pr_warn("path name too long: %i, max is %i", str_len,
|
||||
ISOBUSFS_MAX_PATH_NAME_LENGTH);
|
||||
str_len = ISOBUSFS_MAX_PATH_NAME_LENGTH;
|
||||
}
|
||||
strncpy(str, (const char *)&res->name[0], str_len);
|
||||
|
||||
priv->state = ISOBUSFS_CLI_STATE_GET_CURRENT_DIR_DONE;
|
||||
|
||||
pr_debug("< rx: current directory response: %s, total space: %i, free space: %i",
|
||||
str, total_space, free_space);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int isobusfs_cli_send_and_register_gcd_event(struct isobusfs_priv *priv,
|
||||
isobusfs_event_callback cb,
|
||||
void *ctx)
|
||||
{
|
||||
struct isobusfs_event event;
|
||||
uint8_t fs_function;
|
||||
int ret;
|
||||
|
||||
ret = isobusfs_cli_get_current_dir_req(priv);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
fs_function =
|
||||
isobusfs_cg_function_to_buf(ISOBUSFS_CG_DIRECTORY_HANDLING,
|
||||
ISOBUSFS_DH_F_GET_CURRENT_DIR_RES);
|
||||
if (cb)
|
||||
event.cb = cb;
|
||||
else
|
||||
event.cb = isobusfs_cli_dh_ccd_res_log;
|
||||
|
||||
event.ctx = ctx;
|
||||
|
||||
isobusfs_cli_prepare_response_event(&event, priv->sock_main,
|
||||
fs_function);
|
||||
|
||||
return isobusfs_cli_register_event(priv, &event);
|
||||
}
|
||||
|
||||
/* Command group: directory handling */
|
||||
int isobusfs_cli_rx_cg_dh(struct isobusfs_priv *priv,
|
||||
struct isobusfs_msg *msg)
|
||||
{
|
||||
int func = isobusfs_buf_to_function(msg->buf);
|
||||
int ret = 0;
|
||||
|
||||
switch (func) {
|
||||
case ISOBUSFS_DH_F_GET_CURRENT_DIR_RES:
|
||||
return isobusfs_cli_dh_current_dir_res_log(priv, msg, NULL, 0);
|
||||
case ISOBUSFS_DH_F_CHANGE_CURRENT_DIR_RES:
|
||||
return isobusfs_cli_dh_ccd_res_log(priv, msg, NULL, 0);
|
||||
default:
|
||||
pr_warn("%s: unsupported function: %i", __func__, func);
|
||||
/* Not a critical error */
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
430
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cli_fa.c
Executable file
430
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cli_fa.c
Executable file
@@ -0,0 +1,430 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "isobusfs_cli.h"
|
||||
#include "isobusfs_cmn_fa.h"
|
||||
|
||||
int isobusfs_cli_fa_sf_req(struct isobusfs_priv *priv, uint8_t handle,
|
||||
uint8_t position_mode, int32_t offset)
|
||||
{
|
||||
struct isobusfs_fa_seekf_req req;
|
||||
int ret;
|
||||
|
||||
req.fs_function = isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
|
||||
ISOBUSFS_FA_F_SEEK_FILE_REQ);
|
||||
req.tan = isobusfs_cli_get_next_tan(priv);
|
||||
req.handle = handle;
|
||||
req.position_mode = position_mode;
|
||||
req.offset = htole32(offset);
|
||||
|
||||
priv->state = ISOBUSFS_CLI_STATE_WAIT_SF_RESP;
|
||||
|
||||
ret = isobusfs_send(priv->sock_main, &req, sizeof(req),
|
||||
&priv->tx_buf_log);
|
||||
if (ret < 0) {
|
||||
pr_warn("failed to send Seek File Request: %d (%s)", ret,
|
||||
strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
pr_debug("> tx: Seek File Request for handle: %x, position mode: %d, offset: %d",
|
||||
req.handle, req.position_mode, req.offset);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int isobusfs_cli_fa_sf_res_log(struct isobusfs_priv *priv,
|
||||
struct isobusfs_msg *msg,
|
||||
void *ctx, int error)
|
||||
{
|
||||
struct isobusfs_fa_seekf_res *res =
|
||||
(struct isobusfs_fa_seekf_res *)msg;
|
||||
|
||||
if (!isobusfs_cli_tan_is_valid(res->tan, priv)) {
|
||||
priv->state = ISOBUSFS_CLI_STATE_SF_FAIL;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (res->error_code) {
|
||||
priv->state = ISOBUSFS_CLI_STATE_SF_FAIL;
|
||||
pr_warn("< rx: Seek File Error - Error code: %d",
|
||||
res->error_code);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
priv->read_offset = le32toh(res->position);
|
||||
priv->state = ISOBUSFS_CLI_STATE_SF_DONE;
|
||||
pr_debug("< rx: Seek File Success, position: %d", res->position);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int isobusfs_cli_send_and_register_fa_sf_event(struct isobusfs_priv *priv,
|
||||
uint8_t handle,
|
||||
uint8_t position_mode,
|
||||
int32_t offset,
|
||||
isobusfs_event_callback cb,
|
||||
void *ctx)
|
||||
{
|
||||
struct isobusfs_event event;
|
||||
uint8_t fs_function;
|
||||
int ret;
|
||||
|
||||
ret = isobusfs_cli_fa_sf_req(priv, handle, position_mode, offset);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
fs_function =
|
||||
isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
|
||||
ISOBUSFS_FA_F_SEEK_FILE_RES);
|
||||
|
||||
if (cb)
|
||||
event.cb = cb;
|
||||
else
|
||||
event.cb = isobusfs_cli_fa_sf_res_log;
|
||||
|
||||
event.ctx = ctx;
|
||||
|
||||
isobusfs_cli_prepare_response_event(&event, priv->sock_main,
|
||||
fs_function);
|
||||
|
||||
return isobusfs_cli_register_event(priv, &event);
|
||||
}
|
||||
|
||||
int isobusfs_cli_fa_rf_req(struct isobusfs_priv *priv, uint8_t handle,
|
||||
uint16_t count)
|
||||
{
|
||||
struct isobusfs_fa_readf_req req;
|
||||
int ret;
|
||||
|
||||
req.fs_function = isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
|
||||
ISOBUSFS_FA_F_READ_FILE_REQ);
|
||||
req.tan = isobusfs_cli_get_next_tan(priv);
|
||||
req.handle = handle;
|
||||
req.count = htole16(count);
|
||||
memset(req.reserved, 0xff, sizeof(req.reserved));
|
||||
|
||||
priv->state = ISOBUSFS_CLI_STATE_WAIT_RF_RESP;
|
||||
|
||||
ret = isobusfs_send(priv->sock_main, &req, sizeof(req), &priv->tx_buf_log);
|
||||
if (ret < 0) {
|
||||
ret = -errno;
|
||||
pr_warn("failed to send Read File Request: %d (%s)", ret, strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
pr_debug("> tx: Read File Request for handle: %x, size: %d", req.handle, count);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int isobusfs_cli_fa_rf_res_log(struct isobusfs_priv *priv,
|
||||
struct isobusfs_msg *msg,
|
||||
void *ctx, int error)
|
||||
{
|
||||
struct isobusfs_read_file_response *res =
|
||||
(struct isobusfs_read_file_response *)msg->buf;
|
||||
|
||||
if (priv->state != ISOBUSFS_CLI_STATE_WAIT_RF_RESP) {
|
||||
pr_warn("invalid state: %i (expected %i)", priv->state,
|
||||
ISOBUSFS_CLI_STATE_WAIT_RF_RESP);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!isobusfs_cli_tan_is_valid(res->tan, priv)) {
|
||||
priv->state = ISOBUSFS_CLI_STATE_RF_FAIL;
|
||||
} else if (res->error_code && res->error_code != ISOBUSFS_ERR_END_OF_FILE) {
|
||||
pr_warn("read file failed with error code: %i", res->error_code);
|
||||
priv->state = ISOBUSFS_CLI_STATE_RF_FAIL;
|
||||
} else {
|
||||
if (priv->read_data) {
|
||||
pr_err("read data buffer not empty");
|
||||
free(priv->read_data);
|
||||
}
|
||||
|
||||
priv->read_data_len = le16toh(res->count);
|
||||
priv->read_data = malloc(priv->read_data_len);
|
||||
if (!priv->read_data) {
|
||||
pr_err("failed to allocate memory for data");
|
||||
priv->state = ISOBUSFS_CLI_STATE_RF_FAIL;
|
||||
} else {
|
||||
memcpy(priv->read_data, res->data, priv->read_data_len);
|
||||
priv->state = ISOBUSFS_CLI_STATE_RF_DONE;
|
||||
}
|
||||
}
|
||||
|
||||
pr_debug("< rx: Read File Response. Error code: %i", res->error_code);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int isobusfs_cli_send_and_register_fa_rf_event(struct isobusfs_priv *priv,
|
||||
uint8_t handle,
|
||||
uint16_t count,
|
||||
isobusfs_event_callback cb,
|
||||
void *ctx)
|
||||
{
|
||||
struct isobusfs_event event;
|
||||
uint8_t fs_function;
|
||||
int ret;
|
||||
|
||||
ret = isobusfs_cli_fa_rf_req(priv, handle, count);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
fs_function =
|
||||
isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
|
||||
ISOBUSFS_FA_F_READ_FILE_RES);
|
||||
|
||||
if (cb)
|
||||
event.cb = cb;
|
||||
else
|
||||
event.cb = isobusfs_cli_fa_rf_res_log;
|
||||
|
||||
event.ctx = ctx;
|
||||
|
||||
isobusfs_cli_prepare_response_event(&event, priv->sock_main,
|
||||
fs_function);
|
||||
|
||||
return isobusfs_cli_register_event(priv, &event);
|
||||
}
|
||||
|
||||
int isobusfs_cli_fa_cf_req(struct isobusfs_priv *priv, uint8_t handle)
|
||||
{
|
||||
struct isobusfs_close_file_request req;
|
||||
int ret;
|
||||
|
||||
req.fs_function = isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
|
||||
ISOBUSFS_FA_F_CLOSE_FILE_REQ);
|
||||
req.tan = isobusfs_cli_get_next_tan(priv);
|
||||
req.handle = handle;
|
||||
memset(req.reserved, 0xff, sizeof(req.reserved));
|
||||
|
||||
priv->state = ISOBUSFS_CLI_STATE_WAIT_CF_RESP;
|
||||
|
||||
ret = isobusfs_send(priv->sock_main, &req, sizeof(req), &priv->tx_buf_log);
|
||||
if (ret < 0) {
|
||||
ret = -errno;
|
||||
pr_warn("failed to send Close File request: %d (%s)", ret, strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
pr_debug("> tx: Close File Request for handle: %x", req.handle);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int isobusfs_cli_fa_cf_res_log(struct isobusfs_priv *priv,
|
||||
struct isobusfs_msg *msg,
|
||||
void *ctx, int error)
|
||||
{
|
||||
struct isobusfs_close_file_res *res =
|
||||
(struct isobusfs_close_file_res *)msg->buf;
|
||||
|
||||
if (priv->state != ISOBUSFS_CLI_STATE_WAIT_CF_RESP) {
|
||||
pr_warn("invalid state: %i (expected %i)", priv->state,
|
||||
ISOBUSFS_CLI_STATE_WAIT_CF_RESP);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!isobusfs_cli_tan_is_valid(res->tan, priv)) {
|
||||
priv->state = ISOBUSFS_CLI_STATE_CF_FAIL;
|
||||
} else if (res->error_code != 0) {
|
||||
pr_warn("ccd failed with error code: %i", res->error_code);
|
||||
priv->state = ISOBUSFS_CLI_STATE_CF_FAIL;
|
||||
} else {
|
||||
priv->state = ISOBUSFS_CLI_STATE_CF_DONE;
|
||||
}
|
||||
|
||||
pr_debug("< rx: Close File Response. Error code: %i",
|
||||
res->error_code);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int isobusfs_cli_send_and_register_fa_cf_event(struct isobusfs_priv *priv,
|
||||
uint8_t handle,
|
||||
isobusfs_event_callback cb,
|
||||
void *ctx)
|
||||
{
|
||||
struct isobusfs_event event;
|
||||
uint8_t fs_function;
|
||||
int ret;
|
||||
|
||||
ret = isobusfs_cli_fa_cf_req(priv, handle);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
fs_function =
|
||||
isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
|
||||
ISOBUSFS_FA_F_CLOSE_FILE_RES);
|
||||
|
||||
if (cb)
|
||||
event.cb = cb;
|
||||
else
|
||||
event.cb = isobusfs_cli_fa_cf_res_log;
|
||||
|
||||
event.ctx = ctx;
|
||||
|
||||
isobusfs_cli_prepare_response_event(&event, priv->sock_main,
|
||||
fs_function);
|
||||
|
||||
return isobusfs_cli_register_event(priv, &event);
|
||||
}
|
||||
|
||||
int isobusfs_cli_fa_of_req(struct isobusfs_priv *priv, const char *name,
|
||||
size_t name_len, uint8_t flags)
|
||||
{
|
||||
struct isobusfs_fa_openf_req *req;
|
||||
size_t req_len = sizeof(*req) + name_len;
|
||||
size_t padding_size = 0;
|
||||
int ret;
|
||||
|
||||
if (name_len > ISOBUSFS_MAX_PATH_NAME_LENGTH) {
|
||||
pr_warn("path name too long: %i, max is %i", name_len,
|
||||
ISOBUSFS_MAX_PATH_NAME_LENGTH);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (req_len < ISOBUSFS_MIN_TRANSFER_LENGH) {
|
||||
/* Update the buffer size accordingly */
|
||||
padding_size = ISOBUSFS_MIN_TRANSFER_LENGH - req_len;
|
||||
req_len = ISOBUSFS_MIN_TRANSFER_LENGH;
|
||||
}
|
||||
|
||||
req = malloc(req_len);
|
||||
if (!req) {
|
||||
pr_err("failed to allocate memory for ccd request");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
req->fs_function = isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
|
||||
ISOBUSFS_FA_F_OPEN_FILE_REQ);
|
||||
req->tan = isobusfs_cli_get_next_tan(priv);
|
||||
req->flags = flags;
|
||||
memcpy(&req->name[0], name, name_len);
|
||||
req->name_len = name_len;
|
||||
|
||||
if (padding_size) {
|
||||
/* Fill the rest of the res structure with 0xff */
|
||||
memset(((uint8_t *)req) + req_len - padding_size, 0xff,
|
||||
padding_size);
|
||||
}
|
||||
|
||||
priv->handle = ISOBUSFS_FILE_HANDLE_ERROR;
|
||||
priv->state = ISOBUSFS_CLI_STATE_WAIT_OF_RESP;
|
||||
ret = isobusfs_send(priv->sock_main, req, req_len, &priv->tx_buf_log);
|
||||
if (ret < 0) {
|
||||
ret = -errno;
|
||||
pr_warn("failed to send ccd request: %d (%s)",
|
||||
ret, strerror(ret));
|
||||
goto free_req;
|
||||
}
|
||||
|
||||
pr_debug("> tx: Open File Request for %s, with flags: %x", name,
|
||||
req->flags);
|
||||
|
||||
free_req:
|
||||
free(req);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int isobusfs_cli_fa_open_file_res_log(struct isobusfs_priv *priv,
|
||||
struct isobusfs_msg *msg,
|
||||
void *ctx, int error)
|
||||
|
||||
{
|
||||
struct isobusfs_fa_openf_res *res =
|
||||
(struct isobusfs_fa_openf_res *)msg->buf;
|
||||
|
||||
if (priv->state != ISOBUSFS_CLI_STATE_WAIT_OF_RESP) {
|
||||
pr_warn("invalid state: %i (expected %i)", priv->state,
|
||||
ISOBUSFS_CLI_STATE_WAIT_OF_RESP);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!isobusfs_cli_tan_is_valid(res->tan, priv)) {
|
||||
priv->state = ISOBUSFS_CLI_STATE_OF_FAIL;
|
||||
} else if (res->error_code != 0) {
|
||||
pr_warn("open file request failed with error code: %i", res->error_code);
|
||||
priv->state = ISOBUSFS_CLI_STATE_OF_FAIL;
|
||||
} else if (res->handle == ISOBUSFS_FILE_HANDLE_ERROR) {
|
||||
pr_warn("open file request didn't failed with error code, but with handle");
|
||||
priv->state = ISOBUSFS_CLI_STATE_OF_FAIL;
|
||||
} else {
|
||||
priv->state = ISOBUSFS_CLI_STATE_OF_DONE;
|
||||
priv->handle = res->handle;
|
||||
}
|
||||
|
||||
pr_debug("< rx: Open File Response. Error code: %i",
|
||||
res->error_code);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int isobusfs_cli_send_and_register_fa_of_event(struct isobusfs_priv *priv,
|
||||
const char *name,
|
||||
size_t name_len,
|
||||
uint8_t flags,
|
||||
isobusfs_event_callback cb,
|
||||
void *ctx)
|
||||
{
|
||||
struct isobusfs_event event;
|
||||
uint8_t fs_function;
|
||||
int ret;
|
||||
|
||||
ret = isobusfs_cli_fa_of_req(priv, name, name_len, flags);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
fs_function =
|
||||
isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
|
||||
ISOBUSFS_FA_F_OPEN_FILE_RES);
|
||||
|
||||
if (cb)
|
||||
event.cb = cb;
|
||||
else
|
||||
event.cb = isobusfs_cli_fa_open_file_res_log;
|
||||
|
||||
event.ctx = ctx;
|
||||
|
||||
isobusfs_cli_prepare_response_event(&event, priv->sock_main,
|
||||
fs_function);
|
||||
|
||||
return isobusfs_cli_register_event(priv, &event);
|
||||
}
|
||||
|
||||
/* Command group: directory handling */
|
||||
int isobusfs_cli_rx_cg_fa(struct isobusfs_priv *priv,
|
||||
struct isobusfs_msg *msg)
|
||||
{
|
||||
int func = isobusfs_buf_to_function(msg->buf);
|
||||
int ret = 0;
|
||||
|
||||
switch (func) {
|
||||
case ISOBUSFS_FA_F_OPEN_FILE_RES:
|
||||
ret = isobusfs_cli_fa_open_file_res_log(priv, msg, NULL, 0);
|
||||
break;
|
||||
case ISOBUSFS_FA_F_CLOSE_FILE_RES:
|
||||
ret = isobusfs_cli_fa_cf_res_log(priv, msg, NULL, 0);
|
||||
break;
|
||||
case ISOBUSFS_FA_F_READ_FILE_RES:
|
||||
ret = isobusfs_cli_fa_rf_res_log(priv, msg, NULL, 0);
|
||||
break;
|
||||
case ISOBUSFS_FA_F_SEEK_FILE_RES:
|
||||
ret = isobusfs_cli_fa_sf_res_log(priv, msg, NULL, 0);
|
||||
break;
|
||||
case ISOBUSFS_FA_F_WRITE_FILE_RES:
|
||||
default:
|
||||
pr_warn("%s: unsupported function: %i", __func__, func);
|
||||
/* Not a critical error */
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
1088
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cli_int.c
Executable file
1088
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cli_int.c
Executable file
File diff suppressed because it is too large
Load Diff
920
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cli_selftests.c
Executable file
920
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cli_selftests.c
Executable file
@@ -0,0 +1,920 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "isobusfs_cli.h"
|
||||
#include "isobusfs_cmn.h"
|
||||
#include "isobusfs_cmn_fa.h"
|
||||
|
||||
size_t current_test;
|
||||
bool test_running;
|
||||
struct timespec test_start_time;
|
||||
|
||||
struct isobusfs_cli_test_case {
|
||||
int (*test_func)(struct isobusfs_priv *priv, bool *complete);
|
||||
const char *test_description;
|
||||
};
|
||||
|
||||
static int isobusfs_cli_test_connect(struct isobusfs_priv *priv, bool *complete)
|
||||
{
|
||||
struct timespec current_time;
|
||||
int ret;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, ¤t_time);
|
||||
|
||||
switch (priv->state) {
|
||||
case ISOBUSFS_CLI_STATE_SELFTEST:
|
||||
test_start_time = current_time;
|
||||
priv->state = ISOBUSFS_CLI_STATE_CONNECTING;
|
||||
/* fall through */
|
||||
case ISOBUSFS_CLI_STATE_CONNECTING:
|
||||
if (priv->fs_is_active) {
|
||||
*complete = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
|
||||
ret = -ETIMEDOUT;
|
||||
goto test_fail;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
pr_err("%s:%i: unknown state: %d", __func__, __LINE__, priv->state);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
test_fail:
|
||||
/* without server all other tests make no sense */
|
||||
priv->run_selftest = false;
|
||||
*complete = true;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int isobusfs_cli_test_property_req(struct isobusfs_priv *priv, bool *complete)
|
||||
{
|
||||
struct timespec current_time;
|
||||
int ret;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, ¤t_time);
|
||||
|
||||
switch (priv->state) {
|
||||
case ISOBUSFS_CLI_STATE_SELFTEST:
|
||||
test_start_time = current_time;
|
||||
|
||||
ret = isobusfs_cli_property_req(priv);
|
||||
if (ret)
|
||||
goto test_fail;
|
||||
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_WAIT_FS_PROPERTIES:
|
||||
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
|
||||
ret = -ETIMEDOUT;
|
||||
goto test_fail;
|
||||
}
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_GET_FS_PROPERTIES_DONE:
|
||||
*complete = true;
|
||||
break;
|
||||
default:
|
||||
pr_err("%s:%i: unknown state: %d", __func__, __LINE__, priv->state);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
test_fail:
|
||||
*complete = true;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int isobusfs_cli_test_volume_status_req(struct isobusfs_priv *priv, bool *complete)
|
||||
{
|
||||
static const char volume_name[] = "\\\\vol1";
|
||||
struct timespec current_time;
|
||||
int ret;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, ¤t_time);
|
||||
|
||||
switch (priv->state) {
|
||||
case ISOBUSFS_CLI_STATE_SELFTEST:
|
||||
test_start_time = current_time;
|
||||
|
||||
ret = isobusfs_cli_volume_status_req(priv, 0,
|
||||
sizeof(volume_name) - 1, volume_name);
|
||||
if (ret)
|
||||
goto test_fail;
|
||||
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_WAIT_VOLUME_STATUS:
|
||||
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
|
||||
ret = -ETIMEDOUT;
|
||||
goto test_fail;
|
||||
}
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_VOLUME_STATUS_DONE:
|
||||
*complete = true;
|
||||
break;
|
||||
default:
|
||||
pr_err("%s:%i: unknown state: %d", __func__, __LINE__, priv->state);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
test_fail:
|
||||
*complete = true;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int isobusfs_cli_test_current_dir_req(struct isobusfs_priv *priv,
|
||||
bool *complete)
|
||||
{
|
||||
struct timespec current_time;
|
||||
int ret;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, ¤t_time);
|
||||
|
||||
switch (priv->state) {
|
||||
case ISOBUSFS_CLI_STATE_SELFTEST:
|
||||
test_start_time = current_time;
|
||||
|
||||
ret = isobusfs_cli_get_current_dir_req(priv);
|
||||
if (ret)
|
||||
goto test_fail;
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_WAIT_CURRENT_DIR:
|
||||
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
|
||||
ret = -ETIMEDOUT;
|
||||
goto test_fail;
|
||||
}
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_GET_CURRENT_DIR_DONE:
|
||||
*complete = true;
|
||||
break;
|
||||
default:
|
||||
pr_err("%s:%i: unknown state: %d", __func__, __LINE__, priv->state);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
test_fail:
|
||||
*complete = true;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct isobusfs_cli_test_dir_path {
|
||||
const char *dir_name;
|
||||
bool expect_pass;
|
||||
};
|
||||
|
||||
static struct isobusfs_cli_test_dir_path test_dir_patterns[] = {
|
||||
/* expected result \\vol1\dir1\ */
|
||||
{ "\\\\vol1\\dir1", true },
|
||||
/* expected result \\vol1\dir1\dir2\ */
|
||||
{ "\\\\vol1\\dir1\\dir2", true },
|
||||
/* expected result \\vol1\dir1\dir2\dir3\dir4\ */
|
||||
{ ".\\dir3\\dir4", true },
|
||||
/* expected result \\vol1\dir1\dir2\dir3\dir5\ */
|
||||
{ "..\\dir5", true },
|
||||
/* expected result \\vol1\ */
|
||||
{ "..\\..\\..\\..\\..\\..\\vol1", true },
|
||||
/* expected result \\vol1\~\ */
|
||||
{ "~\\", true },
|
||||
/* expected result \\vol1\~\msd_dir1\msd_dir2\ */
|
||||
{ "~\\msd_dir1\\msd_dir2", true },
|
||||
/* expected result \\vol1\~\ */
|
||||
{ "\\\\vol1\\~\\", true },
|
||||
/* expected result \\vol1\~\msd_dir1\msd_dir2\ */
|
||||
{ "\\\\vol1\\~\\msd_dir1\\msd_dir2", true },
|
||||
/* expected result \\vol1\~\msd_dir1\msd_dir2\~\ */
|
||||
{ ".\\~\\", true },
|
||||
/* expected result \\vol1\~\msd_dir1\msd_dir2\~\~tilde_dir */
|
||||
{ "~tilde_dir", true },
|
||||
/* expected result \\vol1\dir1\~\ */
|
||||
{ "\\\\vol1\\dir1\\~", true },
|
||||
/* expected result \\vol1\~\ not clear if it is manufacture speficic dir */
|
||||
{ "\\~\\", true },
|
||||
/* expected result \\~\ */
|
||||
{ "\\\\~\\", false },
|
||||
/* expected result: should fail */
|
||||
{ "\\\\\\\\\\\\\\\\", false },
|
||||
/* Set back to dir1 for other test. Expected result \\vol1\dir1\ */
|
||||
{ "\\\\vol1\\dir1", true },
|
||||
|
||||
/* Initialize server path to root: Expected initial state: root */
|
||||
{ "\\\\vol1\\dir1", true }, /* Set server path to \\vol1\dir1\ */
|
||||
|
||||
/* Test absolute paths: Expected state: \\vol1\dir1\ */
|
||||
{ "\\\\vol1\\dir1\\dir2", true }, /* Changes to \\vol1\dir1\dir2\ */
|
||||
{ "\\\\vol1\\dir1", true }, /* Changes back to \\vol1\dir1\ */
|
||||
|
||||
/* Test relative path .\ : Expected state: \\vol1\dir1\ */
|
||||
{ ".\\dir2\\dir3\\dir4", true }, /* Changes to \\vol1\dir1\dir2\dir3\dir4\ */
|
||||
{ "..\\dir5", true }, /* Changes to \\vol1\dir1\dir2\dir3\dir5\ */
|
||||
{ "..\\..\\..\\..\\..\\..\\vol1", true }, /* Changes to \\vol1\ */
|
||||
{ ".\\dir1\\dir2", true }, /* Changes back to \\vol1\dir1\dir2 */
|
||||
|
||||
/* Test relative path ..\ with multiple backslashes:
|
||||
* Expected state: \\vol1\dir1\dir2\
|
||||
*/
|
||||
{ "..\\\\\\", true }, /* Changes to \\vol1\dir1\ */
|
||||
{ ".\\dir2", true }, /* Changes back to \\vol1\dir1\dir2\ */
|
||||
{ "..\\\\\\\\\\\\\\", true }, /* Changes to \\vol1\dir1\ */
|
||||
{ ".\\dir2", true }, /* Changes back to \\vol1\dir1\dir2 */
|
||||
{ "\\\\vol1\\dir1", true }, /* Changes back to \\vol1\dir1\ */
|
||||
|
||||
/* Test relative path .\ with multiple backslashes: Expected state:
|
||||
* \\vol1\dir1\
|
||||
*/
|
||||
{ ".\\\\\\", true }, /* Remains at \\vol1\dir1\ */
|
||||
{ ".\\dir2", true }, /* Changes back to \\vol1\dir1\dir2 */
|
||||
{ "..\\", true }, /* Changes to \\vol1\dir1\ */
|
||||
{ ".\\\\\\\\\\\\\\", true }, /* Remains at \\vol1\dir1\ */
|
||||
{ ".\\dir2", true }, /* Changes back to \\vol1\dir1\dir2 */
|
||||
{ "..\\", true }, /* Changes to \\vol1\dir1\ */
|
||||
|
||||
/* Test navigating up and down: Expected state: \\vol1\dir1\ */
|
||||
{ "..\\..\\..\\..\\..\\..\\vol1", true }, /* Changes to \\vol1\ */
|
||||
|
||||
/* prepare for tilde tests */
|
||||
{ "\\\\vol1\\", true }, /* Set server path to \\vol1\ */
|
||||
/* Tilde used correctly at the beginning of a path */
|
||||
{ "~\\", true }, /* Replace with the manufacturer-specific directory on
|
||||
* the current volume
|
||||
*/
|
||||
/* Tilde used correctly after a volume name */
|
||||
{ "\\\\vol1\\~\\", true }, /* Replace ~ with the manufacturer-specific
|
||||
* directory on vol1
|
||||
*/
|
||||
|
||||
/* Tilde used in non-root locations, treated as a regular directory */
|
||||
{ "\\\\vol1\\dir1\\~", true }, /* Treated as a regular directory named
|
||||
* '~' under \\vol1\dir1\
|
||||
*/
|
||||
{ ".\\~\\", true }, /* Treated as a regular directory named '~' in the
|
||||
* current directory: \\vol1\dir1\~\
|
||||
*/
|
||||
|
||||
/* Tilde used with a specific manufacturer directory at the root */
|
||||
{ "~\\msd_dir1\\msd_dir2", true }, /* Replace ~ and append rest of the
|
||||
* path on the current volume
|
||||
*/
|
||||
{ "\\\\vol1\\~\\msd_dir1\\msd_dir2", true },
|
||||
/* Replace ~ and append rest of the
|
||||
* path on vol1
|
||||
*/
|
||||
{ ".\\~\\", true }, /* Treated as a regular directory named '~' in the
|
||||
* current directory: \\vol1\~\msd_dir1\msd_dir2\~\
|
||||
*/
|
||||
{ "~tilde_dir", true }, /* Treated as a regular directory named
|
||||
* '~tilde_dir' in the current directory:
|
||||
* \\vol1\~\msd_dir1\msd_dir2\~\~tilde_dir\
|
||||
*/
|
||||
/* Invalid usage of tilde at non-root locations (as a
|
||||
* manufacturer-specific directory)
|
||||
*/
|
||||
{ "\\~\\", false }, /* Incorrect usage of tilde at non-root, expected
|
||||
* to fail
|
||||
*/
|
||||
{ "\\\\~\\", false }, /* Incorrect usage of tilde at non-root, expected
|
||||
* to fail
|
||||
*/
|
||||
|
||||
/* Test invalid or ambiguous paths: Expected state: \\vol1\dir1\ */
|
||||
{ "\\\\\\\\\\\\\\\\", false }, /* Invalid path, should fail */
|
||||
|
||||
/* Set back to dir1 for other tests: Expected state: \\vol1\dir1\ */
|
||||
{ "\\\\vol1\\dir1", true }, /* Ensure server path is set to \\vol1\dir1\ */
|
||||
};
|
||||
|
||||
size_t current_dir_pattern_test;
|
||||
|
||||
static int isobusfs_cli_test_ccd_req(struct isobusfs_priv *priv, bool *complete)
|
||||
{
|
||||
size_t num_patterns = ARRAY_SIZE(test_dir_patterns);
|
||||
struct isobusfs_cli_test_dir_path *tp =
|
||||
&test_dir_patterns[current_dir_pattern_test];
|
||||
struct timespec current_time;
|
||||
bool fail = false;
|
||||
int ret;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, ¤t_time);
|
||||
|
||||
switch (priv->state) {
|
||||
case ISOBUSFS_CLI_STATE_SELFTEST:
|
||||
test_start_time = current_time;
|
||||
pr_info("Start pattern test: %s", tp->dir_name);
|
||||
ret = isobusfs_cli_ccd_req(priv, tp->dir_name,
|
||||
strlen(tp->dir_name));
|
||||
if (ret)
|
||||
goto test_fail;
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_WAIT_CCD_RESP:
|
||||
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
|
||||
ret = -ETIMEDOUT;
|
||||
goto test_fail;
|
||||
}
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_CCD_FAIL:
|
||||
fail = true;
|
||||
/* fallthrough */
|
||||
case ISOBUSFS_CLI_STATE_CCD_DONE:
|
||||
if (tp->expect_pass && fail) {
|
||||
pr_err("pattern test failed: %s", tp->dir_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
} else if (!tp->expect_pass && !fail) {
|
||||
pr_err("pattern test failed: %s", tp->dir_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
current_dir_pattern_test++;
|
||||
|
||||
if (current_dir_pattern_test >= num_patterns) {
|
||||
*complete = true;
|
||||
break;
|
||||
}
|
||||
|
||||
priv->state = ISOBUSFS_CLI_STATE_SELFTEST;
|
||||
break;
|
||||
default:
|
||||
pr_err("%s:%i: unknown state: %d", __func__, __LINE__, priv->state);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
test_fail:
|
||||
*complete = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct isobusfs_cli_test_of_path {
|
||||
const char *path_name;
|
||||
uint8_t flags;
|
||||
bool expect_pass;
|
||||
};
|
||||
|
||||
static struct isobusfs_cli_test_of_path test_of_patterns[] = {
|
||||
/* expected result \\vol1\dir1\dir2\ */
|
||||
{ "\\\\vol1\\dir1\\dir2", 0, false },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file0", 0, true },
|
||||
};
|
||||
|
||||
size_t current_of_pattern_test;
|
||||
|
||||
static int isobusfs_cli_test_of_req(struct isobusfs_priv *priv, bool *complete)
|
||||
{
|
||||
size_t num_patterns = ARRAY_SIZE(test_of_patterns);
|
||||
struct isobusfs_cli_test_of_path *tp =
|
||||
&test_of_patterns[current_of_pattern_test];
|
||||
struct timespec current_time;
|
||||
int ret;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, ¤t_time);
|
||||
|
||||
switch (priv->state) {
|
||||
case ISOBUSFS_CLI_STATE_SELFTEST:
|
||||
test_start_time = current_time;
|
||||
pr_info("Start pattern test: %s", tp->path_name);
|
||||
ret = isobusfs_cli_fa_of_req(priv, tp->path_name,
|
||||
strlen(tp->path_name), tp->flags);
|
||||
if (ret)
|
||||
goto test_fail;
|
||||
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_WAIT_OF_RESP:
|
||||
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
|
||||
ret = -ETIMEDOUT;
|
||||
goto test_fail;
|
||||
}
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_OF_FAIL:
|
||||
if (tp->expect_pass) {
|
||||
pr_err("pattern test failed: %s", tp->path_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_OF_DONE:
|
||||
if (!tp->expect_pass) {
|
||||
pr_err("pattern test failed: %s", tp->path_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
if (priv->handle != ISOBUSFS_FILE_HANDLE_ERROR)
|
||||
isobusfs_cli_fa_cf_req(priv, priv->handle);
|
||||
else
|
||||
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
|
||||
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_CF_FAIL:
|
||||
pr_err("failed to close file: %s", tp->path_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
case ISOBUSFS_CLI_STATE_CF_DONE:
|
||||
case ISOBUSFS_CLI_STATE_TEST_DONE:
|
||||
current_of_pattern_test++;
|
||||
|
||||
if (current_of_pattern_test >= num_patterns)
|
||||
*complete = true;
|
||||
else
|
||||
priv->state = ISOBUSFS_CLI_STATE_SELFTEST;
|
||||
|
||||
break;
|
||||
default:
|
||||
pr_err("%s:%i: unknown state: %d", __func__, __LINE__, priv->state);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
test_fail:
|
||||
*complete = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct isobusfs_cli_test_sf_path {
|
||||
const char *path_name;
|
||||
uint8_t flags;
|
||||
uint32_t offset;
|
||||
uint32_t read_size;
|
||||
bool expect_pass;
|
||||
};
|
||||
|
||||
static struct isobusfs_cli_test_sf_path test_sf_patterns[] = {
|
||||
/* expected result \\vol1\dir1\dir2\ */
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 0, 0, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 10, 0, true },
|
||||
};
|
||||
|
||||
size_t current_sf_pattern_test;
|
||||
|
||||
static int isobusfs_cli_test_sf_req(struct isobusfs_priv *priv, bool *complete)
|
||||
{
|
||||
size_t num_patterns = ARRAY_SIZE(test_sf_patterns);
|
||||
struct isobusfs_cli_test_sf_path *tp =
|
||||
&test_sf_patterns[current_sf_pattern_test];
|
||||
struct timespec current_time;
|
||||
int ret;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, ¤t_time);
|
||||
|
||||
switch (priv->state) {
|
||||
case ISOBUSFS_CLI_STATE_SELFTEST:
|
||||
test_start_time = current_time;
|
||||
pr_info("Start pattern test: %s", tp->path_name);
|
||||
ret = isobusfs_cli_fa_of_req(priv, tp->path_name,
|
||||
strlen(tp->path_name), tp->flags);
|
||||
if (ret)
|
||||
goto test_fail;
|
||||
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_WAIT_OF_RESP:
|
||||
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
|
||||
ret = -ETIMEDOUT;
|
||||
goto test_fail;
|
||||
}
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_OF_FAIL:
|
||||
if (tp->expect_pass) {
|
||||
pr_err("pattern test failed: %s", tp->path_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_OF_DONE:
|
||||
if (!tp->expect_pass) {
|
||||
pr_err("pattern test failed: %s", tp->path_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
ret = isobusfs_cli_fa_sf_req(priv, priv->handle,
|
||||
ISOBUSFS_FA_SEEK_SET, tp->offset);
|
||||
if (ret)
|
||||
goto test_fail;
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_SF_FAIL:
|
||||
if (tp->expect_pass) {
|
||||
pr_err("pattern test failed: %s", tp->path_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_SF_DONE:
|
||||
if (!tp->expect_pass) {
|
||||
pr_err("pattern test failed: %s", tp->path_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
if (priv->read_offset != tp->offset) {
|
||||
pr_err("Not expected read offset: %s", tp->path_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
ret = isobusfs_cli_fa_rf_req(priv, priv->handle,
|
||||
tp->read_size);
|
||||
if (ret)
|
||||
goto test_fail;
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_RF_FAIL:
|
||||
if (tp->expect_pass) {
|
||||
pr_err("pattern test failed: %s", tp->path_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
priv->state = ISOBUSFS_CLI_STATE_TEST_CLEANUP;
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_RF_DONE:
|
||||
if (!tp->expect_pass) {
|
||||
pr_err("pattern test failed: %s", tp->path_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
/* fall troth */
|
||||
case ISOBUSFS_CLI_STATE_TEST_CLEANUP:
|
||||
if (priv->handle != ISOBUSFS_FILE_HANDLE_ERROR)
|
||||
isobusfs_cli_fa_cf_req(priv, priv->handle);
|
||||
else
|
||||
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
|
||||
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_CF_FAIL:
|
||||
pr_err("failed to close file: %s", tp->path_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
case ISOBUSFS_CLI_STATE_CF_DONE:
|
||||
case ISOBUSFS_CLI_STATE_TEST_DONE:
|
||||
current_of_pattern_test++;
|
||||
|
||||
if (current_of_pattern_test >= num_patterns)
|
||||
*complete = true;
|
||||
else
|
||||
priv->state = ISOBUSFS_CLI_STATE_SELFTEST;
|
||||
break;
|
||||
default:
|
||||
pr_err("%s:%i: unknown state: %d", __func__, __LINE__, priv->state);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
test_fail:
|
||||
*complete = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct isobusfs_cli_test_rf_path {
|
||||
const char *path_name;
|
||||
uint8_t flags;
|
||||
uint32_t offset;
|
||||
uint32_t read_size;
|
||||
bool expect_pass;
|
||||
};
|
||||
|
||||
static struct isobusfs_cli_test_rf_path test_rf_patterns[] = {
|
||||
/* expected result \\vol1\dir1\dir2\ */
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 0, 0, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 0, 1, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 1, 1, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 2, 1, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 3, 1, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, 8, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, 8 * 100, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 100, 8 * 100, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, ISOBUSFS_MAX_DATA_LENGH, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, (ISOBUSFS_MAX_DATA_LENGH & ~3) + 16, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, ISOBUSFS_MAX_DATA_LENGH + 1, true },
|
||||
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, -1, true },
|
||||
};
|
||||
|
||||
size_t current_rf_pattern_test;
|
||||
|
||||
static uint32_t isobusfs_cli_calculate_sum(uint8_t *data, size_t size,
|
||||
uint32_t offset)
|
||||
{
|
||||
const uint8_t xor_pattern[] = {0xde, 0xad, 0xbe, 0xef};
|
||||
uint32_t actual_sum = 0;
|
||||
uint32_t current_value = 0;
|
||||
uint8_t byte_offset = 0;
|
||||
size_t idx;
|
||||
|
||||
byte_offset = offset % 4;
|
||||
|
||||
for (idx = 0; idx < size; idx++) {
|
||||
uint8_t byte;
|
||||
|
||||
if (data) {
|
||||
byte = data[idx] ^ xor_pattern[byte_offset];
|
||||
current_value |= (byte << ((3 - byte_offset) * 8));
|
||||
} else {
|
||||
uint32_t value_at_offset;
|
||||
|
||||
/* if no data is provided, generate the data based on
|
||||
* offset
|
||||
*/
|
||||
|
||||
value_at_offset = (offset + idx) / 4;
|
||||
byte_offset = (offset + idx) % 4;
|
||||
byte = (value_at_offset >> ((3 - byte_offset) * 8)) & 0xff;
|
||||
current_value |= (byte << ((3 - byte_offset) * 8));
|
||||
}
|
||||
|
||||
if (byte_offset == 3) {
|
||||
actual_sum += current_value;
|
||||
current_value = 0;
|
||||
}
|
||||
|
||||
byte_offset = (byte_offset + 1) % 4;
|
||||
|
||||
/* if this is the last byte in the buffer but it's not aligned,
|
||||
* add the partial uint32_t to the sum
|
||||
*/
|
||||
if (idx == size - 1 && byte_offset != 0)
|
||||
actual_sum += current_value;
|
||||
}
|
||||
|
||||
return actual_sum;
|
||||
}
|
||||
|
||||
static int isobusfs_cli_test_rf_req(struct isobusfs_priv *priv, bool *complete)
|
||||
{
|
||||
size_t num_patterns = ARRAY_SIZE(test_rf_patterns);
|
||||
struct isobusfs_cli_test_rf_path *tp =
|
||||
&test_rf_patterns[current_rf_pattern_test];
|
||||
uint32_t actual_sum, expected_sum;
|
||||
struct timespec current_time;
|
||||
ssize_t remaining_size, read_size;
|
||||
int ret;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, ¤t_time);
|
||||
|
||||
switch (priv->state) {
|
||||
case ISOBUSFS_CLI_STATE_SELFTEST:
|
||||
test_start_time = current_time;
|
||||
pr_info("Start read test. Path: %s, size: %d, offset: %d, flags: %x",
|
||||
tp->path_name, tp->read_size, tp->offset, tp->flags);
|
||||
ret = isobusfs_cli_fa_of_req(priv, tp->path_name,
|
||||
strlen(tp->path_name), tp->flags);
|
||||
if (ret)
|
||||
goto test_fail;
|
||||
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_WAIT_OF_RESP:
|
||||
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
|
||||
ret = -ETIMEDOUT;
|
||||
goto test_fail;
|
||||
}
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_OF_FAIL:
|
||||
if (tp->expect_pass) {
|
||||
pr_err("pattern test failed: %s", tp->path_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_OF_DONE:
|
||||
if (!tp->expect_pass) {
|
||||
pr_err("pattern test failed: %s", tp->path_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
ret = isobusfs_cli_fa_sf_req(priv, priv->handle,
|
||||
ISOBUSFS_FA_SEEK_SET, tp->offset);
|
||||
if (ret)
|
||||
goto test_fail;
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_SF_FAIL:
|
||||
if (tp->expect_pass) {
|
||||
pr_err("pattern test failed: %s", tp->path_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_SF_DONE:
|
||||
if (!tp->expect_pass) {
|
||||
pr_err("pattern test failed: %s", tp->path_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
if (priv->read_offset != tp->offset) {
|
||||
pr_err("Not expected read offset: %s", tp->path_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
if (tp->read_size > 0xffff)
|
||||
read_size = 0xffff;
|
||||
else
|
||||
read_size = tp->read_size;
|
||||
|
||||
ret = isobusfs_cli_fa_rf_req(priv, priv->handle,
|
||||
read_size);
|
||||
if (ret)
|
||||
goto test_fail;
|
||||
test_start_time = current_time;
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_WAIT_RF_RESP:
|
||||
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
|
||||
ret = -ETIMEDOUT;
|
||||
goto test_fail;
|
||||
}
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_RF_FAIL:
|
||||
if (tp->expect_pass) {
|
||||
pr_err("pattern test failed: %s", tp->path_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
priv->state = ISOBUSFS_CLI_STATE_TEST_CLEANUP;
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_RF_DONE:
|
||||
if (!tp->expect_pass) {
|
||||
pr_err("pattern test failed: %s", tp->path_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
pr_info("read file size: %zu", priv->read_data_len);
|
||||
actual_sum = isobusfs_cli_calculate_sum(priv->read_data,
|
||||
priv->read_data_len,
|
||||
priv->read_offset);
|
||||
expected_sum = isobusfs_cli_calculate_sum(NULL,
|
||||
priv->read_data_len,
|
||||
priv->read_offset);
|
||||
if (actual_sum != expected_sum) {
|
||||
pr_err("pattern test failed: incorrect sum in %s. Sum got: %d, expected: %d",
|
||||
tp->path_name, actual_sum, expected_sum);
|
||||
isobusfs_cmn_dump_last_x_bytes(priv->read_data,
|
||||
priv->read_data_len, 16);
|
||||
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
} else {
|
||||
pr_info("pattern test passed: %s. Sum got: %d, expected: %d",
|
||||
tp->path_name, actual_sum, expected_sum);
|
||||
isobusfs_cmn_dump_last_x_bytes(priv->read_data,
|
||||
priv->read_data_len, 16);
|
||||
}
|
||||
|
||||
free(priv->read_data);
|
||||
priv->read_data = NULL;
|
||||
|
||||
remaining_size = (tp->offset + tp->read_size) -
|
||||
(priv->read_offset + priv->read_data_len);
|
||||
pr_debug("remaining_size: %zd, read_offset: %zu, read_data_len: %zu, test read size: %zu, test offset %zu",
|
||||
remaining_size, priv->read_offset, priv->read_data_len,
|
||||
tp->read_size, tp->offset);
|
||||
if (remaining_size < 0) {
|
||||
pr_err("pattern test failed: %s. Read size is too big",
|
||||
tp->path_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
} else if (remaining_size > 0 && priv->read_data_len != 0) {
|
||||
priv->read_offset += priv->read_data_len;
|
||||
|
||||
if (remaining_size > 0xffff)
|
||||
read_size = 0xffff;
|
||||
else
|
||||
read_size = remaining_size;
|
||||
|
||||
ret = isobusfs_cli_fa_rf_req(priv, priv->handle,
|
||||
read_size);
|
||||
if (ret)
|
||||
goto test_fail;
|
||||
test_start_time = current_time;
|
||||
break;
|
||||
} else if (remaining_size > 0 && priv->read_data_len == 0 && tp->expect_pass) {
|
||||
pr_err("read test failed: %s. Read size is zero, but expected more data: %zd",
|
||||
tp->path_name, remaining_size);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
/* fall troth */
|
||||
case ISOBUSFS_CLI_STATE_TEST_CLEANUP:
|
||||
if (priv->handle != ISOBUSFS_FILE_HANDLE_ERROR) {
|
||||
isobusfs_cli_fa_cf_req(priv, priv->handle);
|
||||
test_start_time = current_time;
|
||||
} else {
|
||||
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
|
||||
}
|
||||
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_WAIT_CF_RESP:
|
||||
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
|
||||
ret = -ETIMEDOUT;
|
||||
goto test_fail;
|
||||
}
|
||||
break;
|
||||
case ISOBUSFS_CLI_STATE_CF_FAIL:
|
||||
pr_err("failed to close file: %s", tp->path_name);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
case ISOBUSFS_CLI_STATE_CF_DONE:
|
||||
case ISOBUSFS_CLI_STATE_TEST_DONE:
|
||||
current_rf_pattern_test++;
|
||||
|
||||
if (current_rf_pattern_test >= num_patterns)
|
||||
*complete = true;
|
||||
else
|
||||
priv->state = ISOBUSFS_CLI_STATE_SELFTEST;
|
||||
|
||||
break;
|
||||
default:
|
||||
pr_err("%s:%i: unknown state: %d", __func__, __LINE__,
|
||||
priv->state);
|
||||
ret = -EINVAL;
|
||||
goto test_fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
test_fail:
|
||||
*complete = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct isobusfs_cli_test_case test_cases[] = {
|
||||
{ isobusfs_cli_test_connect, "Server connection" },
|
||||
{ isobusfs_cli_test_property_req, "Server property request" },
|
||||
{ isobusfs_cli_test_volume_status_req, "Volume status request" },
|
||||
{ isobusfs_cli_test_current_dir_req, "Get current dir request" },
|
||||
{ isobusfs_cli_test_ccd_req, "Change current dir request" },
|
||||
{ isobusfs_cli_test_of_req, "Open File request" },
|
||||
{ isobusfs_cli_test_sf_req, "Seek File request" },
|
||||
{ isobusfs_cli_test_rf_req, "Read File request" },
|
||||
};
|
||||
|
||||
void isobusfs_cli_run_self_tests(struct isobusfs_priv *priv)
|
||||
{
|
||||
if (priv->run_selftest) {
|
||||
size_t num_tests = ARRAY_SIZE(test_cases);
|
||||
|
||||
if (current_test < num_tests) {
|
||||
bool test_complete = false;
|
||||
int ret;
|
||||
|
||||
if (!test_running) {
|
||||
pr_int("Executing test %zu: %s\n", current_test + 1, test_cases[current_test].test_description);
|
||||
test_running = true;
|
||||
priv->state = ISOBUSFS_CLI_STATE_SELFTEST;
|
||||
}
|
||||
|
||||
ret = test_cases[current_test].test_func(priv, &test_complete);
|
||||
|
||||
if (test_complete) {
|
||||
test_running = false;
|
||||
pr_int("Test %zu: %s.\n", current_test + 1, ret ? "FAILED" : "PASSED");
|
||||
current_test++;
|
||||
priv->state = ISOBUSFS_CLI_STATE_SELFTEST;
|
||||
}
|
||||
} else {
|
||||
pr_int("All tests completed.\n");
|
||||
priv->run_selftest = false;
|
||||
current_test = 0;
|
||||
priv->state = ISOBUSFS_CLI_STATE_IDLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
700
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cmn.c
Executable file
700
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cmn.c
Executable file
@@ -0,0 +1,700 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <linux/errqueue.h>
|
||||
#include <linux/net_tstamp.h>
|
||||
#include <linux/netlink.h>
|
||||
|
||||
#include "isobusfs_cmn.h"
|
||||
#include "isobusfs_srv.h"
|
||||
|
||||
static log_level_t log_level = LOG_LEVEL_INFO;
|
||||
static bool interactive_mode;
|
||||
|
||||
#define LOG_BUFFER_SIZE 1024
|
||||
#define LOG_ENTRY_MAX_SIZE 256
|
||||
|
||||
struct isobusfs_log_buffer {
|
||||
char buffer[LOG_BUFFER_SIZE][LOG_ENTRY_MAX_SIZE];
|
||||
int write_index;
|
||||
};
|
||||
|
||||
struct isobusfs_log_buffer log_buffer = { .write_index = 0 };
|
||||
|
||||
void add_log_to_buffer(const char *log_entry)
|
||||
{
|
||||
int idx = log_buffer.write_index;
|
||||
char *buffer = log_buffer.buffer[idx];
|
||||
|
||||
strncpy(buffer, log_entry, LOG_ENTRY_MAX_SIZE);
|
||||
buffer[LOG_ENTRY_MAX_SIZE - 1] = '\0'; /* Ensure null termination */
|
||||
log_buffer.write_index = (idx + 1) % LOG_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
void isobusfs_print_log_buffer(void)
|
||||
{
|
||||
printf("\n---- Log Buffer Start ----\n");
|
||||
for (int i = 0; i < LOG_BUFFER_SIZE; i++) {
|
||||
int idx = (log_buffer.write_index + i) % LOG_BUFFER_SIZE;
|
||||
|
||||
if (log_buffer.buffer[idx][0] != '\0')
|
||||
printf("%s\n", log_buffer.buffer[idx]);
|
||||
}
|
||||
printf("\n---- Log Buffer End ----\n");
|
||||
}
|
||||
|
||||
void isobusfs_log(log_level_t level, const char *fmt, ...)
|
||||
{
|
||||
char complete_log_entry[LOG_ENTRY_MAX_SIZE];
|
||||
char log_entry[LOG_ENTRY_MAX_SIZE - 64];
|
||||
const char *level_str;
|
||||
struct timeval tv;
|
||||
struct tm *time_info;
|
||||
char time_buffer[64];
|
||||
int milliseconds;
|
||||
va_list args;
|
||||
|
||||
if (level > log_level)
|
||||
return;
|
||||
|
||||
switch (level) {
|
||||
case LOG_LEVEL_DEBUG:
|
||||
level_str = "DEBUG";
|
||||
break;
|
||||
case LOG_LEVEL_INT:
|
||||
case LOG_LEVEL_INFO:
|
||||
level_str = "INFO";
|
||||
break;
|
||||
case LOG_LEVEL_WARN:
|
||||
level_str = "WARNING";
|
||||
break;
|
||||
case LOG_LEVEL_ERROR:
|
||||
level_str = "ERROR";
|
||||
break;
|
||||
default:
|
||||
level_str = "UNKNOWN";
|
||||
break;
|
||||
}
|
||||
|
||||
gettimeofday(&tv, NULL);
|
||||
time_info = localtime(&tv.tv_sec);
|
||||
milliseconds = tv.tv_usec / 1000;
|
||||
strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d %H:%M:%S",
|
||||
time_info);
|
||||
snprintf(time_buffer + strlen(time_buffer),
|
||||
sizeof(time_buffer) - strlen(time_buffer),
|
||||
".%03d", milliseconds);
|
||||
|
||||
va_start(args, fmt);
|
||||
vsnprintf(log_entry, sizeof(log_entry), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
snprintf(complete_log_entry, sizeof(complete_log_entry),
|
||||
"[%.40s] [%.10s]: %.150s", time_buffer, level_str, log_entry);
|
||||
|
||||
if (interactive_mode) {
|
||||
add_log_to_buffer(complete_log_entry);
|
||||
if (level == LOG_LEVEL_INT) {
|
||||
fprintf(stdout, "%s", log_entry);
|
||||
fflush(stdout);
|
||||
}
|
||||
} else {
|
||||
fprintf(stdout, "%s\n", complete_log_entry);
|
||||
}
|
||||
}
|
||||
|
||||
void isobusfs_set_interactive(bool interactive)
|
||||
{
|
||||
interactive_mode = interactive;
|
||||
}
|
||||
|
||||
/* set log level */
|
||||
void isobusfs_log_level_set(log_level_t level)
|
||||
{
|
||||
log_level = level;
|
||||
}
|
||||
|
||||
const char *isobusfs_error_to_str(enum isobusfs_error err)
|
||||
{
|
||||
switch (err) {
|
||||
case ISOBUSFS_ERR_ACCESS_DENIED:
|
||||
return "Access Denied";
|
||||
case ISOBUSFS_ERR_INVALID_ACCESS:
|
||||
return "Invalid Access";
|
||||
case ISOBUSFS_ERR_TOO_MANY_FILES_OPEN:
|
||||
return "Too many files open";
|
||||
case ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND:
|
||||
return "File or path not found";
|
||||
case ISOBUSFS_ERR_INVALID_HANDLE:
|
||||
return "Invalid handle";
|
||||
case ISOBUSFS_ERR_INVALID_SRC_NAME:
|
||||
return "Invalid given source name";
|
||||
case ISOBUSFS_ERR_INVALID_DST_NAME:
|
||||
return "Invalid given destination name";
|
||||
case ISOBUSFS_ERR_NO_SPACE:
|
||||
return "Volume out of free space";
|
||||
case ISOBUSFS_ERR_ON_WRITE:
|
||||
return "Failure during a write operation";
|
||||
case ISOBUSFS_ERR_MEDIA_IS_NOT_PRESENT:
|
||||
return "Media is not present";
|
||||
case ISOBUSFS_ERR_VOLUME_NOT_INITIALIZED:
|
||||
return "Volume is possibly not initialized";
|
||||
case ISOBUSFS_ERR_ON_READ:
|
||||
return "Failure during a read operation";
|
||||
case ISOBUSFS_ERR_FUNC_NOT_SUPPORTED:
|
||||
return "Function not supported";
|
||||
case ISOBUSFS_ERR_INVALID_REQUESTED_LENGHT:
|
||||
return "Invalid request length";
|
||||
case ISOBUSFS_ERR_OUT_OF_MEM:
|
||||
return "Out of memory";
|
||||
case ISOBUSFS_ERR_OTHER:
|
||||
return "Any other error";
|
||||
case ISOBUSFS_ERR_END_OF_FILE:
|
||||
return "End of file reached, will only be reported when file pointer is at end of file";
|
||||
default:
|
||||
return "<unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
enum isobusfs_error linux_error_to_isobusfs_error(int linux_err)
|
||||
{
|
||||
switch (linux_err) {
|
||||
case 0:
|
||||
return ISOBUSFS_ERR_SUCCESS;
|
||||
case -EINVAL:
|
||||
return ISOBUSFS_ERR_INVALID_DST_NAME;
|
||||
case -EACCES:
|
||||
return ISOBUSFS_ERR_ACCESS_DENIED;
|
||||
case -ENOTDIR:
|
||||
return ISOBUSFS_ERR_INVALID_ACCESS;
|
||||
case -EMFILE:
|
||||
return ISOBUSFS_ERR_TOO_MANY_FILES_OPEN;
|
||||
case -ENOENT:
|
||||
return ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
|
||||
case -EBADF:
|
||||
return ISOBUSFS_ERR_INVALID_HANDLE;
|
||||
case -ENAMETOOLONG:
|
||||
return ISOBUSFS_ERR_INVALID_SRC_NAME;
|
||||
case -ENOSPC:
|
||||
return ISOBUSFS_ERR_NO_SPACE;
|
||||
case -EIO:
|
||||
return ISOBUSFS_ERR_ON_WRITE;
|
||||
case -ENODEV:
|
||||
return ISOBUSFS_ERR_MEDIA_IS_NOT_PRESENT;
|
||||
case -EROFS:
|
||||
return ISOBUSFS_ERR_VOLUME_NOT_INITIALIZED;
|
||||
case -EFAULT:
|
||||
return ISOBUSFS_ERR_ON_READ;
|
||||
case -ENOSYS:
|
||||
return ISOBUSFS_ERR_FUNC_NOT_SUPPORTED;
|
||||
case -EMSGSIZE:
|
||||
return ISOBUSFS_ERR_INVALID_REQUESTED_LENGHT;
|
||||
case -ENOMEM:
|
||||
return ISOBUSFS_ERR_OUT_OF_MEM;
|
||||
case -EPERM:
|
||||
return ISOBUSFS_ERR_OTHER;
|
||||
case -ESPIPE:
|
||||
return ISOBUSFS_ERR_END_OF_FILE;
|
||||
case -EPROTO:
|
||||
return ISOBUSFS_ERR_TAN_ERR;
|
||||
case -EILSEQ:
|
||||
return ISOBUSFS_ERR_MALFORMED_REQUEST;
|
||||
default:
|
||||
return ISOBUSFS_ERR_OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
static void isobusfs_print_timestamp(struct isobusfs_err_msg *emsg,
|
||||
const char *name, struct timespec *cur)
|
||||
{
|
||||
struct isobusfs_stats *stats = emsg->stats;
|
||||
|
||||
/* TODO: make it configurable */
|
||||
return;
|
||||
|
||||
if (!(cur->tv_sec | cur->tv_nsec))
|
||||
return;
|
||||
|
||||
fprintf(stderr, " %s: %llu s %llu us (seq=%u/%u, send=%u)",
|
||||
name, (unsigned long long)cur->tv_sec, (unsigned long long)cur->tv_nsec / 1000,
|
||||
stats->tskey_sch, stats->tskey_ack, stats->send);
|
||||
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
static const char *isobusfs_tstype_to_str(int tstype)
|
||||
{
|
||||
switch (tstype) {
|
||||
case SCM_TSTAMP_SCHED:
|
||||
return " ENQ";
|
||||
case SCM_TSTAMP_SND:
|
||||
return " SND";
|
||||
case SCM_TSTAMP_ACK:
|
||||
return " ACK";
|
||||
default:
|
||||
return " unk";
|
||||
}
|
||||
}
|
||||
|
||||
/* Check the stats of SCM_TIMESTAMPING_OPT_STATS */
|
||||
static void isobusfs_scm_opt_stats(struct isobusfs_err_msg *emsg, void *buf, int len)
|
||||
{
|
||||
struct isobusfs_stats *stats = emsg->stats;
|
||||
int offset = 0;
|
||||
|
||||
while (offset < len) {
|
||||
struct nlattr *nla = (struct nlattr *) ((char *)buf + offset);
|
||||
|
||||
switch (nla->nla_type) {
|
||||
case J1939_NLA_BYTES_ACKED:
|
||||
stats->send = *(uint32_t *)((char *)nla + NLA_HDRLEN);
|
||||
break;
|
||||
default:
|
||||
warnx("not supported J1939_NLA field\n");
|
||||
}
|
||||
|
||||
offset += NLA_ALIGN(nla->nla_len);
|
||||
}
|
||||
}
|
||||
|
||||
static int isobusfs_extract_serr(struct isobusfs_err_msg *emsg)
|
||||
{
|
||||
struct isobusfs_stats *stats = emsg->stats;
|
||||
struct sock_extended_err *serr = emsg->serr;
|
||||
struct scm_timestamping *tss = emsg->tss;
|
||||
|
||||
switch (serr->ee_origin) {
|
||||
case SO_EE_ORIGIN_TIMESTAMPING:
|
||||
/*
|
||||
* We expect here following patterns:
|
||||
* serr->ee_info == SCM_TSTAMP_ACK
|
||||
* Activated with SOF_TIMESTAMPING_TX_ACK
|
||||
* or
|
||||
* serr->ee_info == SCM_TSTAMP_SCHED
|
||||
* Activated with SOF_TIMESTAMPING_SCHED
|
||||
* and
|
||||
* serr->ee_data == tskey
|
||||
* session message counter which is activate
|
||||
* with SOF_TIMESTAMPING_OPT_ID
|
||||
* the serr->ee_errno should be ENOMSG
|
||||
*/
|
||||
if (serr->ee_errno != ENOMSG)
|
||||
warnx("serr: expected ENOMSG, got: %i",
|
||||
serr->ee_errno);
|
||||
|
||||
if (serr->ee_info == SCM_TSTAMP_SCHED)
|
||||
stats->tskey_sch = serr->ee_data;
|
||||
else
|
||||
stats->tskey_ack = serr->ee_data;
|
||||
|
||||
isobusfs_print_timestamp(emsg, isobusfs_tstype_to_str(serr->ee_info),
|
||||
&tss->ts[0]);
|
||||
|
||||
if (serr->ee_info == SCM_TSTAMP_SCHED)
|
||||
return -EINTR;
|
||||
else
|
||||
return 0;
|
||||
case SO_EE_ORIGIN_LOCAL:
|
||||
/*
|
||||
* The serr->ee_origin == SO_EE_ORIGIN_LOCAL is
|
||||
* currently used to notify about locally
|
||||
* detected protocol/stack errors.
|
||||
* Following patterns are expected:
|
||||
* serr->ee_info == J1939_EE_INFO_TX_ABORT
|
||||
* is used to notify about session TX
|
||||
* abort.
|
||||
* serr->ee_data == tskey
|
||||
* session message counter which is activate
|
||||
* with SOF_TIMESTAMPING_OPT_ID
|
||||
* serr->ee_errno == actual error reason
|
||||
* error reason is converted from J1939
|
||||
* abort to linux error name space.
|
||||
*/
|
||||
if (serr->ee_info != J1939_EE_INFO_TX_ABORT)
|
||||
warnx("serr: unknown ee_info: %i",
|
||||
serr->ee_info);
|
||||
|
||||
isobusfs_print_timestamp(emsg, " ABT", &tss->ts[0]);
|
||||
warnx("serr: tx error: %i, %s", serr->ee_errno, strerror(serr->ee_errno));
|
||||
|
||||
return serr->ee_errno;
|
||||
default:
|
||||
warnx("serr: wrong origin: %u", serr->ee_origin);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isobusfs_parse_cm(struct isobusfs_err_msg *emsg,
|
||||
struct cmsghdr *cm)
|
||||
{
|
||||
const size_t hdr_len = CMSG_ALIGN(sizeof(struct cmsghdr));
|
||||
|
||||
if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SCM_TIMESTAMPING) {
|
||||
emsg->tss = (void *)CMSG_DATA(cm);
|
||||
} else if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SCM_TIMESTAMPING_OPT_STATS) {
|
||||
void *jstats = (void *)CMSG_DATA(cm);
|
||||
|
||||
/* Activated with SOF_TIMESTAMPING_OPT_STATS */
|
||||
isobusfs_scm_opt_stats(emsg, jstats, cm->cmsg_len - hdr_len);
|
||||
} else if (cm->cmsg_level == SOL_CAN_J1939 &&
|
||||
cm->cmsg_type == SCM_J1939_ERRQUEUE) {
|
||||
emsg->serr = (void *)CMSG_DATA(cm);
|
||||
} else
|
||||
warnx("serr: not supported type: %d.%d",
|
||||
cm->cmsg_level, cm->cmsg_type);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int isobusfs_recv_err(int sock, struct isobusfs_err_msg *emsg)
|
||||
{
|
||||
char control[200];
|
||||
struct cmsghdr *cm;
|
||||
int ret;
|
||||
struct msghdr msg = {
|
||||
.msg_control = control,
|
||||
.msg_controllen = sizeof(control),
|
||||
};
|
||||
|
||||
ret = recvmsg(sock, &msg, MSG_ERRQUEUE | MSG_DONTWAIT);
|
||||
if (ret == -1) {
|
||||
ret = -errno;
|
||||
pr_err("recvmsg error notification: %i (%s)", ret, strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (msg.msg_flags & MSG_CTRUNC) {
|
||||
pr_err("recvmsg error notification: truncated");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
emsg->serr = NULL;
|
||||
emsg->tss = NULL;
|
||||
|
||||
for (cm = CMSG_FIRSTHDR(&msg); cm && cm->cmsg_len;
|
||||
cm = CMSG_NXTHDR(&msg, cm)) {
|
||||
isobusfs_parse_cm(emsg, cm);
|
||||
if (emsg->serr && emsg->tss)
|
||||
return isobusfs_extract_serr(emsg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* send NACK message. function should use src, dst and socket as parameters */
|
||||
void isobusfs_send_nack(int sock, struct isobusfs_msg *msg)
|
||||
{
|
||||
struct sockaddr_can addr = msg->peername;
|
||||
struct isobusfs_nack nack;
|
||||
int ret;
|
||||
|
||||
nack.ctrl = ISOBUS_ACK_CTRL_NACK;
|
||||
nack.group_function = msg->buf[0];
|
||||
memset(&nack.reserved[0], 0xff, sizeof(nack.reserved));
|
||||
nack.address_nack = addr.can_addr.j1939.addr;
|
||||
memcpy(&nack.pgn_nack[0], &addr.can_addr.j1939.pgn,
|
||||
sizeof(nack.pgn_nack));
|
||||
|
||||
addr.can_addr.j1939.pgn = ISOBUS_PGN_ACK;
|
||||
ret = sendto(sock, &nack, sizeof(nack), MSG_DONTWAIT,
|
||||
(struct sockaddr *)&addr,
|
||||
sizeof(addr));
|
||||
if (ret < 0) {
|
||||
ret = -errno;
|
||||
pr_warn("failed to send NACK: %i (%s)", ret, strerror(ret));
|
||||
}
|
||||
|
||||
pr_debug("send NACK");
|
||||
}
|
||||
|
||||
/* store data to a recursive buffer */
|
||||
void isobufs_store_tx_data(struct isobusfs_buf_log *buffer, uint8_t *data)
|
||||
{
|
||||
struct isobusfs_buf *entry = &buffer->entries[buffer->index];
|
||||
|
||||
/* we assume :) that data is at least 8 bytes long */
|
||||
memcpy(entry->data, data, sizeof(entry->data));
|
||||
clock_gettime(CLOCK_REALTIME, &entry->ts);
|
||||
|
||||
buffer->index = (buffer->index + 1) % ISOBUSFS_MAX_BUF_ENTRIES;
|
||||
}
|
||||
|
||||
void isobusfs_dump_tx_data(const struct isobusfs_buf_log *buffer)
|
||||
{
|
||||
uint i;
|
||||
|
||||
for (i = 0; i < ISOBUSFS_MAX_BUF_ENTRIES; ++i) {
|
||||
const struct isobusfs_buf *entry = &buffer->entries[i];
|
||||
char data_str[ISOBUSFS_MIN_TRANSFER_LENGH * 3] = {0};
|
||||
uint j;
|
||||
|
||||
for (j = 0; j < ISOBUSFS_MIN_TRANSFER_LENGH; ++j)
|
||||
snprintf(data_str + j * 3, 4, "%02X ", entry->data[j]);
|
||||
|
||||
pr_debug("Entry %u: %s Timestamp: %ld.%09ld\n", i, data_str,
|
||||
entry->ts.tv_sec, entry->ts.tv_nsec);
|
||||
}
|
||||
}
|
||||
|
||||
/* wrapper for sendto() */
|
||||
int isobusfs_sendto(int sock, const void *data, size_t len,
|
||||
const struct sockaddr_can *addr,
|
||||
struct isobusfs_buf_log *isobusfs_tx_buffer)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* store to tx buffer */
|
||||
isobufs_store_tx_data(isobusfs_tx_buffer, (uint8_t *)data);
|
||||
|
||||
ret = sendto(sock, data, len, MSG_DONTWAIT,
|
||||
(struct sockaddr *)addr, sizeof(*addr));
|
||||
if (ret == -1) {
|
||||
ret = -errno;
|
||||
pr_warn("failed to send data: %i (%s)", ret, strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* wrapper for send() */
|
||||
int isobusfs_send(int sock, const void *data, size_t len,
|
||||
struct isobusfs_buf_log *isobusfs_tx_buffer)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* store to tx buffer */
|
||||
isobufs_store_tx_data(isobusfs_tx_buffer, (uint8_t *)data);
|
||||
|
||||
ret = send(sock, data, len, MSG_DONTWAIT);
|
||||
if (ret == -1) {
|
||||
ret = -errno;
|
||||
pr_warn("failed to send data: %i (%s)", ret, strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_cmn_configure_socket_filter - Configure a J1939 socket filter
|
||||
* @sock: Socket file descriptor
|
||||
* @pgn: Parameter Group Number to filter
|
||||
*
|
||||
* This function configures a J1939 socket filter for the provided PGN.
|
||||
* It allows ISOBUS FS role-specific PGN and ACK messages for troubleshooting.
|
||||
* Returns 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int isobusfs_cmn_configure_socket_filter(int sock, pgn_t pgn)
|
||||
{
|
||||
struct j1939_filter sock_filter[2] = {0};
|
||||
int ret;
|
||||
|
||||
if (pgn != ISOBUSFS_PGN_CL_TO_FS && pgn != ISOBUSFS_PGN_FS_TO_CL) {
|
||||
pr_err("invalid pgn: %d", pgn);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Allow ISOBUS FS role specific PGN */
|
||||
sock_filter[0].pgn = pgn;
|
||||
sock_filter[0].pgn_mask = J1939_PGN_PDU1_MAX;
|
||||
|
||||
/*
|
||||
* ISO 11783-3:2018 - 5.4.5 Acknowledgment.
|
||||
* Allow ACK messages for troubleshooting
|
||||
*/
|
||||
sock_filter[1].pgn = ISOBUS_PGN_ACK;
|
||||
sock_filter[1].pgn_mask = J1939_PGN_PDU1_MAX;
|
||||
|
||||
ret = setsockopt(sock, SOL_CAN_J1939, SO_J1939_FILTER, &sock_filter,
|
||||
sizeof(sock_filter));
|
||||
if (ret < 0) {
|
||||
ret = -errno;
|
||||
pr_err("failed to set j1939 filter: %d (%s)", ret, strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_cmn_configure_timestamping - Configure timestamping options for a
|
||||
* socket
|
||||
* @sock: Socket file descriptor
|
||||
*
|
||||
* This function configures various timestamping options for the given socket,
|
||||
* such as software timestamping, CMSG timestamping, transmission
|
||||
* acknowledgment, transmission scheduling, statistics, and timestamp-only
|
||||
* options. These options are needed to get a response from different kernel
|
||||
* j1939 stack layers about egress status, allowing the caller to know if the
|
||||
* ETP session has finished or if status messages have actually been sent.
|
||||
* These options make sense only in combination with SO_J1939_ERRQUEUE. Returns
|
||||
* 0 on success or a negative error code on failure.
|
||||
*/
|
||||
static int isobusfs_cmn_configure_timestamping(int sock)
|
||||
{
|
||||
unsigned int sock_opt;
|
||||
int ret;
|
||||
|
||||
sock_opt = SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_OPT_CMSG |
|
||||
SOF_TIMESTAMPING_TX_ACK | SOF_TIMESTAMPING_TX_SCHED |
|
||||
SOF_TIMESTAMPING_OPT_STATS | SOF_TIMESTAMPING_OPT_TSONLY |
|
||||
SOF_TIMESTAMPING_OPT_ID;
|
||||
|
||||
ret = setsockopt(sock, SOL_SOCKET, SO_TIMESTAMPING,
|
||||
(char *)&sock_opt, sizeof(sock_opt));
|
||||
if (ret < 0) {
|
||||
ret = -errno;
|
||||
pr_err("setsockopt timestamping: %d (%s)", ret, strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_cmn_configure_error_queue - Configure error queue for a J1939 socket
|
||||
* @sock: socket file descriptor
|
||||
*
|
||||
* This function configures the error queue for a given J1939 socket, enabling
|
||||
* timestamping options and the error queue itself. SO_J1939_ERRQUEUE enables
|
||||
* the actual feedback channel from the kernel J1939 stack. Timestamping options
|
||||
* are configured to subscribe the socket to different notifications over this
|
||||
* channel, providing information on egress status. This helps in determining if
|
||||
* an ETP session has finished or if status messages were actually sent.
|
||||
*
|
||||
* Return: 0 on success, or a negative error code on failure.
|
||||
*/
|
||||
int isobusfs_cmn_configure_error_queue(int sock)
|
||||
{
|
||||
int err_queue = true;
|
||||
int ret;
|
||||
|
||||
ret = setsockopt(sock, SOL_CAN_J1939, SO_J1939_ERRQUEUE,
|
||||
&err_queue, sizeof(err_queue));
|
||||
if (ret < 0) {
|
||||
ret = -errno;
|
||||
pr_err("set recverr: %d (%s)", ret, strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = isobusfs_cmn_configure_timestamping(sock);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int isobusfs_cmn_connect_socket(int sock, struct sockaddr_can *addr)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = connect(sock, (void *)addr, sizeof(*addr));
|
||||
if (ret < 0) {
|
||||
ret = -errno;
|
||||
pr_err("failed to connect socket: %d (%s)", ret, strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* FIXME: linger is currently not supported by the kernel J1939 stack
|
||||
* but it would be nice to have it. Especially if we wont to stop sending
|
||||
* messages on a socket when the connection is lost.
|
||||
*/
|
||||
int isobusfs_cmn_set_linger(int sock)
|
||||
{
|
||||
struct linger linger_opt;
|
||||
int ret;
|
||||
|
||||
linger_opt.l_onoff = 1;
|
||||
linger_opt.l_linger = 0;
|
||||
ret = setsockopt(sock, SOL_SOCKET, SO_LINGER, &linger_opt,
|
||||
sizeof(linger_opt));
|
||||
if (ret < 0) {
|
||||
ret = -errno;
|
||||
pr_err("setsockopt(SO_LINGER): %d (%s)", ret, strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void isobusfs_cmn_dump_last_x_bytes(const uint8_t *buffer, size_t buffer_size,
|
||||
size_t x)
|
||||
{
|
||||
size_t start_offset = 0;
|
||||
char *output_ptr;
|
||||
unsigned char c;
|
||||
int remaining;
|
||||
char output[80];
|
||||
int n, j;
|
||||
|
||||
if (x > 0 && x < buffer_size)
|
||||
start_offset = (buffer_size - x) & ~0x7;
|
||||
|
||||
for (size_t i = start_offset; i < buffer_size; i += 8) {
|
||||
output_ptr = output;
|
||||
remaining = (int)sizeof(output);
|
||||
|
||||
n = snprintf(output_ptr, remaining, "%08lx: ",
|
||||
(unsigned long)(start_offset + i));
|
||||
if (n < 0 || n >= remaining)
|
||||
break;
|
||||
|
||||
output_ptr += n;
|
||||
remaining -= n;
|
||||
|
||||
for (j = 0; j < 8 && i + j < buffer_size; ++j) {
|
||||
n = snprintf(output_ptr, remaining, "%02x ", buffer[i+j]);
|
||||
if (n < 0 || n >= remaining)
|
||||
break;
|
||||
|
||||
output_ptr += n;
|
||||
remaining -= n;
|
||||
}
|
||||
|
||||
for (j = buffer_size - i; j < 8; ++j) {
|
||||
n = snprintf(output_ptr, remaining, " ");
|
||||
if (n < 0 || n >= remaining)
|
||||
break;
|
||||
|
||||
output_ptr += n;
|
||||
remaining -= n;
|
||||
}
|
||||
|
||||
n = snprintf(output_ptr, remaining, " ");
|
||||
if (n < 0 || n >= remaining)
|
||||
break;
|
||||
|
||||
output_ptr += n;
|
||||
remaining -= n;
|
||||
|
||||
for (j = 0; j < 8 && i + j < buffer_size; ++j) {
|
||||
c = buffer[i+j];
|
||||
n = snprintf(output_ptr, remaining, "%c",
|
||||
isprint(c) ? c : '.');
|
||||
if (n < 0 || n >= remaining)
|
||||
break;
|
||||
|
||||
output_ptr += n;
|
||||
remaining -= n;
|
||||
}
|
||||
|
||||
pr_debug("%s", output);
|
||||
if (n < 0 || n >= remaining)
|
||||
break;
|
||||
}
|
||||
}
|
||||
343
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cmn.h
Executable file
343
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cmn.h
Executable file
@@ -0,0 +1,343 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#ifndef _ISOBUSFS_H_
|
||||
#define _ISOBUSFS_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <endian.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/kernel.h>
|
||||
#include "../libj1939.h"
|
||||
#include "../lib.h"
|
||||
|
||||
/* ISO 11783-13:2021 - C.1.1.a File Server to Client PGN */
|
||||
#define ISOBUSFS_PGN_FS_TO_CL 0x0ab00 /* 43766 */
|
||||
/* ISO 11783-13:2021 - C.1.1.b Client to File Server PGN */
|
||||
#define ISOBUSFS_PGN_CL_TO_FS 0x0aa00 /* 43520 */
|
||||
|
||||
#define ISOBUSFS_PRIO_DEFAULT 7
|
||||
#define ISOBUSFS_PRIO_FSS 5
|
||||
#define ISOBUSFS_PRIO_ACK 6
|
||||
#define ISOBUSFS_MAX_OPENED_FILES 255
|
||||
#define ISOBUSFS_MAX_SHORT_FILENAME_LENGH 12 /* 12 chars */
|
||||
#define ISOBUSFS_MAX_LONG_FILENAME_LENGH 31 /* 31 chars */
|
||||
/* ISO 11783-13:2021 - C.3.5.1 Maximal transfer size for TP (Transport Protocol) */
|
||||
#define ISOBUSFS_TP_MAX_TRANSFER_SIZE 1780
|
||||
/* ISO 11783-13:2021 - C.3.5.1 Maximal transfer size for ETP (Extended Transport Protocol) */
|
||||
#define ISOBUSFS_ETP_MAX_TRANSFER_SIZE 65530
|
||||
#define ISOBUSFS_MAX_DATA_LENGH 65530 /* Bytes */
|
||||
#define ISOBUSFS_MAX_TRANSFER_LENGH (6 + ISOBUSFS_MAX_DATA_LENGH)
|
||||
#define ISOBUSFS_MIN_TRANSFER_LENGH 8
|
||||
#define ISOBUSFS_CLIENT_TIMEOUT 6000 /* ms */
|
||||
#define ISOBUSFS_FS_TIMEOUT 6000 /* ms */
|
||||
#define ISOBUSFS_MAX_BUF_ENTRIES 10
|
||||
#define ISOBUSFS_MAX_PATH_NAME_LENGTH ISOBUSFS_MAX_DATA_LENGH
|
||||
|
||||
/* not documented, take some max value */
|
||||
#define ISOBUSFS_SRV_MAX_VOLUMES 10
|
||||
/* ISO 11783-13:2021 A.2.2.3 Volumes */
|
||||
#define ISOBUSFS_SRV_MAX_VOLUME_NAME_LEN 254
|
||||
#define ISOBUSFS_MAX_VOLUME_NAME_LENGTH 254
|
||||
#define ISOBUSFS_MAX_DIR_ENTRY_NAME_LENGTH 255
|
||||
/* not documented, take some max value */
|
||||
#define ISOBUSFS_SRV_MAX_PATH_LEN 4096
|
||||
|
||||
#define ISOBUSFS_FILE_HANDLE_ERROR 255
|
||||
|
||||
/* ISO 11783-3:2018 - 5.4.5 Acknowledgment */
|
||||
#define ISOBUS_PGN_ACK 0x0e800 /* 59392 */
|
||||
|
||||
enum isobusfs_ack_ctrl {
|
||||
ISOBUS_ACK_CTRL_ACK = 0,
|
||||
ISOBUS_ACK_CTRL_NACK = 1,
|
||||
};
|
||||
|
||||
struct isobusfs_nack {
|
||||
uint8_t ctrl;
|
||||
uint8_t group_function;
|
||||
uint8_t reserved[2];
|
||||
uint8_t address_nack;
|
||||
uint8_t pgn_nack[3];
|
||||
};
|
||||
|
||||
/* ISO 11783-13:2021 - Annex B.1 Command Groups (CG) */
|
||||
enum isobusfs_cg {
|
||||
ISOBUSFS_CG_CONNECTION_MANAGMENT = 0,
|
||||
ISOBUSFS_CG_DIRECTORY_HANDLING = 1,
|
||||
ISOBUSFS_CG_FILE_ACCESS = 2,
|
||||
ISOBUSFS_CG_FILE_HANDLING = 3,
|
||||
ISOBUSFS_CG_VOLUME_HANDLING = 4,
|
||||
};
|
||||
|
||||
#define ISOBUSFS_CM_F_CCM_RATE 2000 /* ms */
|
||||
|
||||
/* Connection Management functions: */
|
||||
/* ISO 11783-13:2021 - C.1.* Connection Management - Client to File Server
|
||||
* functions:
|
||||
*/
|
||||
enum isobusfs_cm_cl_to_fs_function {
|
||||
/* ISO 11783-13:2021 - C.1.3 Client Connection Maintenance */
|
||||
ISOBUSFS_CM_F_CC_MAINTENANCE = 0,
|
||||
/* ISO 11783-13:2021 - C.1.4 Get File Server Properties */
|
||||
ISOBUSFS_CM_GET_FS_PROPERTIES = 1,
|
||||
/* ISO 11783-13:2021 - C.1.6 Volume Status Request */
|
||||
ISOBUSFS_CM_VOLUME_STATUS_REQ = 2,
|
||||
};
|
||||
|
||||
/* ISO 11783-13:2021 - C.1.* Connection Management - File Server to client
|
||||
* functions:
|
||||
*/
|
||||
enum isobusfs_cm_fs_to_cl_function {
|
||||
/* ISO 11783-13:2021 - C.1.2 File Server Status */
|
||||
ISOBUSFS_CM_F_FS_STATUS = 0,
|
||||
/* ISO 11783-13:2021 - C.1.5 Get File Server Properties Response */
|
||||
ISOBUSFS_CM_GET_FS_PROPERTIES_RES = 1,
|
||||
/* ISO 11783-13:2021 - C.1.7 Volume Status Response */
|
||||
ISOBUSFS_CM_VOLUME_STATUS_RES = 2,
|
||||
};
|
||||
|
||||
/* Directory Handling functions: */
|
||||
/* send by server: */
|
||||
enum isobusfs_dh_fs_to_cl_function {
|
||||
ISOBUSFS_DH_F_GET_CURRENT_DIR_RES = 0,
|
||||
ISOBUSFS_DH_F_CHANGE_CURRENT_DIR_RES = 1,
|
||||
};
|
||||
|
||||
/* send by client: */
|
||||
enum isobusfs_dh_cl_to_fs_function {
|
||||
ISOBUSFS_DH_F_GET_CURRENT_DIR_REQ = 0,
|
||||
ISOBUSFS_DH_F_CHANGE_CURRENT_DIR_REQ = 1,
|
||||
};
|
||||
|
||||
/* File Access functions: */
|
||||
/* send by server: */
|
||||
enum isobusfs_fa_fs_to_cl_function {
|
||||
ISOBUSFS_FA_F_OPEN_FILE_RES = 0,
|
||||
ISOBUSFS_FA_F_SEEK_FILE_RES = 1,
|
||||
ISOBUSFS_FA_F_READ_FILE_RES = 2,
|
||||
ISOBUSFS_FA_F_WRITE_FILE_RES = 3,
|
||||
ISOBUSFS_FA_F_CLOSE_FILE_RES = 4,
|
||||
};
|
||||
|
||||
/* send by client: */
|
||||
enum isobusfs_fa_cl_to_fs_function {
|
||||
ISOBUSFS_FA_F_OPEN_FILE_REQ = 0,
|
||||
ISOBUSFS_FA_F_SEEK_FILE_REQ = 1,
|
||||
ISOBUSFS_FA_F_READ_FILE_REQ = 2,
|
||||
ISOBUSFS_FA_F_WRITE_FILE_REQ = 3,
|
||||
ISOBUSFS_FA_F_CLOSE_FILE_REQ = 4,
|
||||
};
|
||||
|
||||
/* File Handling functions: */
|
||||
/* send by server: */
|
||||
enum isobusfs_fh_fs_to_cl_function {
|
||||
ISOBUSFS_FH_F_MOVE_FILE_RES = 0,
|
||||
ISOBUSFS_FH_F_DELETE_FILE_RES = 1,
|
||||
ISOBUSFS_FH_F_GET_FILE_ATTR_RES = 2,
|
||||
ISOBUSFS_FH_F_SET_FILE_ATTR_RES = 3,
|
||||
ISOBUSFS_FH_F_GET_FILE_DATETIME_RES = 4,
|
||||
};
|
||||
|
||||
/* send by client: */
|
||||
enum isobusfs_fh_cl_to_fs_function {
|
||||
ISOBUSFS_FH_F_MOVE_FILE_REQ = 0,
|
||||
ISOBUSFS_FH_F_DELETE_FILE_REQ = 1,
|
||||
ISOBUSFS_FH_F_GET_FILE_ATTR_REQ = 2,
|
||||
ISOBUSFS_FH_F_SET_FILE_ATTR_REQ = 3,
|
||||
ISOBUSFS_FH_F_GET_FILE_DATETIME_REQ = 4,
|
||||
};
|
||||
|
||||
/* Volume Access functions: */
|
||||
/* Preparing or repairing the volume for files and directory structures.
|
||||
* These commands should be limited to initial setup, intended to be used by
|
||||
* service tool clients only.
|
||||
*/
|
||||
/* send by server: */
|
||||
/* Initialize Volume: Prepare the volume to accept files and directories. All
|
||||
* data will be lost upon completion of this command.
|
||||
*/
|
||||
enum isobusfs_va_fs_to_cl_function {
|
||||
ISOBUSFS_VA_F_INITIALIZE_VOLUME_RES = 0,
|
||||
};
|
||||
|
||||
/* send by client: */
|
||||
enum isobusfs_va_cl_to_fs_function {
|
||||
/* Initialize Volume: Prepare the volume to accept files and directories.
|
||||
* All data will be lost upon completion of this command.
|
||||
*/
|
||||
ISOBUSFS_VA_F_INITIALIZE_VOLUME_REQ = 0,
|
||||
};
|
||||
|
||||
/* ISO 11783-13:2021 - Annex B.9 Error Code */
|
||||
enum isobusfs_error {
|
||||
/* Success */
|
||||
ISOBUSFS_ERR_SUCCESS = 0,
|
||||
/* Access Denied */
|
||||
ISOBUSFS_ERR_ACCESS_DENIED = 1,
|
||||
/* Invalid Access */
|
||||
ISOBUSFS_ERR_INVALID_ACCESS = 2,
|
||||
/* Too many files open */
|
||||
ISOBUSFS_ERR_TOO_MANY_FILES_OPEN = 3,
|
||||
/* File or path not found */
|
||||
ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND = 4,
|
||||
/* Invalid handle */
|
||||
ISOBUSFS_ERR_INVALID_HANDLE = 5,
|
||||
/* Invalid given source name */
|
||||
ISOBUSFS_ERR_INVALID_SRC_NAME = 6,
|
||||
/* Invalid given destination name */
|
||||
ISOBUSFS_ERR_INVALID_DST_NAME = 7,
|
||||
/* Volume out of free space */
|
||||
ISOBUSFS_ERR_NO_SPACE = 8,
|
||||
/* Failure during a write operation */
|
||||
ISOBUSFS_ERR_ON_WRITE = 9,
|
||||
/* Media is not present */
|
||||
ISOBUSFS_ERR_MEDIA_IS_NOT_PRESENT = 10,
|
||||
/* Failure during a read operation */
|
||||
ISOBUSFS_ERR_ON_READ = 11,
|
||||
/* Function not supported */
|
||||
ISOBUSFS_ERR_FUNC_NOT_SUPPORTED = 12,
|
||||
/* Volume is possibly not initialized */
|
||||
ISOBUSFS_ERR_VOLUME_NOT_INITIALIZED = 13,
|
||||
/* Invalid request length */
|
||||
ISOBUSFS_ERR_INVALID_REQUESTED_LENGHT = 42,
|
||||
/* Out of memory */
|
||||
ISOBUSFS_ERR_OUT_OF_MEM = 43,
|
||||
/* Any other error */
|
||||
ISOBUSFS_ERR_OTHER = 44,
|
||||
/* End of file reached, will only be reported when file pointer is at
|
||||
* end of file
|
||||
*/
|
||||
ISOBUSFS_ERR_END_OF_FILE = 45,
|
||||
/* TAN error:
|
||||
* Same TAN, but different request compared to the previous one (change
|
||||
* in content or size).
|
||||
*/
|
||||
ISOBUSFS_ERR_TAN_ERR = 46,
|
||||
/* Malformed request:
|
||||
* Message is shorter than expected. If the message is too short to
|
||||
* provide a TAN (less than 2 bytes), the TAN shall be set to 0xff in
|
||||
* the response.
|
||||
*/
|
||||
ISOBUSFS_ERR_MALFORMED_REQUEST = 47,
|
||||
};
|
||||
|
||||
/* recursive buffer entry */
|
||||
struct isobusfs_buf {
|
||||
uint8_t data[ISOBUSFS_MIN_TRANSFER_LENGH];
|
||||
struct timespec ts;
|
||||
};
|
||||
|
||||
struct isobusfs_buf_log {
|
||||
struct isobusfs_buf entries[ISOBUSFS_MAX_BUF_ENTRIES];
|
||||
unsigned int index;
|
||||
};
|
||||
|
||||
struct isobusfs_stats {
|
||||
int err;
|
||||
uint32_t tskey_sch;
|
||||
uint32_t tskey_ack;
|
||||
uint32_t send;
|
||||
};
|
||||
|
||||
struct isobusfs_msg {
|
||||
uint8_t buf[ISOBUSFS_MAX_TRANSFER_LENGH];
|
||||
size_t buf_size;
|
||||
ssize_t len; /* length of received message */
|
||||
struct sockaddr_can peername;
|
||||
socklen_t peer_addr_len;
|
||||
int sock;
|
||||
};
|
||||
|
||||
struct isobusfs_err_msg {
|
||||
struct sock_extended_err *serr;
|
||||
struct scm_timestamping *tss;
|
||||
struct isobusfs_stats *stats;
|
||||
};
|
||||
|
||||
int isobusfs_recv_err(int sock, struct isobusfs_err_msg *emsg);
|
||||
|
||||
/*
|
||||
* min()/max()/clamp() macros that also do
|
||||
* strict type-checking.. See the
|
||||
* "unnecessary" pointer comparison.
|
||||
*/
|
||||
#define min(x, y) ({ \
|
||||
typeof(x) _min1 = (x); \
|
||||
typeof(y) _min2 = (y); \
|
||||
(void) (&_min1 == &_min2); \
|
||||
_min1 < _min2 ? _min1 : _min2; })
|
||||
|
||||
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
|
||||
|
||||
static inline int isobusfs_buf_to_cmd(uint8_t *buf)
|
||||
{
|
||||
return (buf[0] & 0xf0) >> 4;
|
||||
}
|
||||
|
||||
static inline int isobusfs_buf_to_function(uint8_t *buf)
|
||||
{
|
||||
return (buf[0] & 0xf);
|
||||
}
|
||||
|
||||
static inline uint8_t isobusfs_cg_function_to_buf(enum isobusfs_cg cg,
|
||||
uint8_t func)
|
||||
{
|
||||
return (func & 0xf) | ((cg & 0xf) << 4);
|
||||
}
|
||||
|
||||
const char *isobusfs_error_to_str(enum isobusfs_error err);
|
||||
enum isobusfs_error linux_error_to_isobusfs_error(int linux_err);
|
||||
|
||||
void isobusfs_send_nack(int sock, struct isobusfs_msg *msg);
|
||||
void isobufs_store_tx_data(struct isobusfs_buf_log *buffer, uint8_t *data);
|
||||
void isobusfs_dump_tx_data(const struct isobusfs_buf_log *buffer);
|
||||
int isobusfs_sendto(int sock, const void *data, size_t len,
|
||||
const struct sockaddr_can *addr,
|
||||
struct isobusfs_buf_log *isobusfs_tx_buffer);
|
||||
int isobusfs_send(int sock, const void *data, size_t len,
|
||||
struct isobusfs_buf_log *isobusfs_tx_buffer);
|
||||
|
||||
void isobusfs_cmn_dump_last_x_bytes(const uint8_t *buffer, size_t buffer_size,
|
||||
size_t x);
|
||||
|
||||
int isobusfs_cmn_configure_socket_filter(int sock, pgn_t pgn);
|
||||
int isobusfs_cmn_configure_error_queue(int sock);
|
||||
int isobusfs_cmn_connect_socket(int sock, struct sockaddr_can *addr);
|
||||
int isobusfs_cmn_set_linger(int sock);
|
||||
|
||||
/* ============ directory handling ============ */
|
||||
int isobusfs_cmn_dh_validate_dir_path(const char *path, bool writable);
|
||||
|
||||
/* ============ logging ============ */
|
||||
|
||||
typedef enum {
|
||||
LOG_LEVEL_INT,
|
||||
LOG_LEVEL_ERROR,
|
||||
LOG_LEVEL_WARN,
|
||||
LOG_LEVEL_INFO,
|
||||
LOG_LEVEL_DEBUG,
|
||||
} log_level_t;
|
||||
|
||||
void isobusfs_log_level_set(log_level_t level);
|
||||
void isobusfs_log(log_level_t level, const char *fmt, ...);
|
||||
void isobusfs_set_interactive(bool interactive);
|
||||
void isobusfs_print_log_buffer(void);
|
||||
|
||||
/* undefine kernel logging macros */
|
||||
#undef pr_int
|
||||
#undef pr_err
|
||||
#undef pr_warn
|
||||
#undef pr_info
|
||||
#undef pr_debug
|
||||
|
||||
/* pr_int - print for interactive session */
|
||||
#define pr_int(fmt, ...) isobusfs_log(LOG_LEVEL_INT, fmt, ##__VA_ARGS__)
|
||||
#define pr_err(fmt, ...) isobusfs_log(LOG_LEVEL_ERROR, fmt, ##__VA_ARGS__)
|
||||
#define pr_warn(fmt, ...) isobusfs_log(LOG_LEVEL_WARN, fmt, ##__VA_ARGS__)
|
||||
#define pr_info(fmt, ...) isobusfs_log(LOG_LEVEL_INFO, fmt, ##__VA_ARGS__)
|
||||
#define pr_debug(fmt, ...) isobusfs_log(LOG_LEVEL_DEBUG, fmt, ##__VA_ARGS__)
|
||||
|
||||
#endif /* !_ISOBUSFS_H_ */
|
||||
175
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cmn_cm.h
Executable file
175
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cmn_cm.h
Executable file
@@ -0,0 +1,175 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#ifndef ISOBUSFS_CMN_CM_H
|
||||
#define ISOBUSFS_CMN_CM_H
|
||||
|
||||
#include "isobusfs_cmn.h"
|
||||
|
||||
/* ISOBUSFS_CM_F_FS_STATUS */
|
||||
#define ISOBUSFS_CM_F_FS_STATUS_IDLE_RATE 2000 /* ms */
|
||||
#define ISOBUSFS_CM_F_FS_STATUS_BUSY_RATE 200 /* ms */
|
||||
#define ISOBUSFS_CM_F_FS_STATUS_RATE_JITTER 5 /* ms */
|
||||
/* File Server Status */
|
||||
#define ISOBUSFS_FS_SATUS_BUSY_WRITING BIT(1)
|
||||
#define ISOBUSFS_FS_SATUS_BUSY_READING BIT(0)
|
||||
|
||||
/**
|
||||
* C.1.2 File Server Status
|
||||
* struct isobusfs_cm_fss - File Server Status structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0000 (Command - Connection Management, see B.1)
|
||||
* Bits 3-0: 0b0001 (Function - File Server Status, see B.2)
|
||||
* @file_server_status: File Server Status (1 byte) (see B.3)
|
||||
* Bits 7-2: 000000 (Reserved, send as 000000)
|
||||
* Bit 1: 1 (Busy writing)
|
||||
* Bit 0: 1 (Busy reading)
|
||||
* @num_open_files: Number of open files (1 byte)
|
||||
* @reserved: Reserved for future use (5 bytes)
|
||||
*
|
||||
* Transmission repetition rate: 2 000 ms when the status is not busy, 200 ms
|
||||
* when the status is busy reading or writing and,
|
||||
* on change of byte 2, up to five messages per
|
||||
* second.
|
||||
* Data length: 8 bytes
|
||||
* Parameter group number: FS to client, destination-specific or use global address: 0xFF
|
||||
*/
|
||||
struct isobusfs_cm_fss {
|
||||
uint8_t fs_function;
|
||||
uint8_t status;
|
||||
uint8_t num_open_files;
|
||||
uint8_t reserved[5];
|
||||
};
|
||||
|
||||
/**
|
||||
* C.1.3 Client Connection Maintenance
|
||||
* struct isobusfs_cm_ccm - Client Connection Maintenance structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0000 (Command - Connection Management, see B.1)
|
||||
* Bits 3-0: 0b0000 (Function - Client Connection Maintenance, see B.2)
|
||||
* @version: Version number (1 byte) (see B.5)
|
||||
* @reserved: Reserved for future use (6 bytes)
|
||||
*
|
||||
* Transmission repetition rate: 2000 ms
|
||||
* Data length: 8 bytes
|
||||
* Parameter group number: Client to FS, destination-specific
|
||||
*/
|
||||
struct isobusfs_cm_ccm {
|
||||
uint8_t fs_function;
|
||||
uint8_t version;
|
||||
uint8_t reserved[6];
|
||||
};
|
||||
|
||||
/**
|
||||
* C.1.4 Get File Server Properties
|
||||
* struct isobusfs_cm_get_fs_props_req - Get File Server Properties structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0000 (Command - Connection Management, see B.1)
|
||||
* Bits 3-0: 0b0001 (Function - Get File Server Properties, see B.2)
|
||||
* @reserved: Reserved, transmit as 0xFF (7 bytes)
|
||||
*
|
||||
* Transmission repetition rate: On request
|
||||
* Data length: 8 bytes
|
||||
* Parameter group number: Client to FS, destination-specific
|
||||
*/
|
||||
struct isobusfs_cm_get_fs_props_req {
|
||||
uint8_t fs_function;
|
||||
uint8_t reserved[7];
|
||||
};
|
||||
|
||||
/* File Server Capabilities */
|
||||
/* server support removable volumes */
|
||||
#define ISOBUSFS_SRV_CAP_REMOVABLE_VOL BIT(1)
|
||||
/* server support multiple volumes */
|
||||
#define ISOBUSFS_SRV_CAP_MULTI_VOL BIT(0)
|
||||
|
||||
/**
|
||||
* C.1.5 Get File Server Properties Response
|
||||
* struct isobusfs_get_fs_props_resp - Get File Server Properties Response
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0000 (Command - Connection Management, see B.1)
|
||||
* Bits 3-0: 0b0001 (Function - Get File Server Properties, see B.2)
|
||||
* @version_number: Version Number (1 byte, see B.5)
|
||||
* @max_open_files: Maximum Number of Simultaneously Open Files (1 byte, see B.6)
|
||||
* @fs_capabilities: File Server Capabilities (1 byte, see B.7)
|
||||
* @reserved: Reserved, transmit as 0xFF (4 bytes)
|
||||
*
|
||||
* Transmission repetition rate: In response to Get File Server Properties message
|
||||
* Data length: 8 bytes
|
||||
* Parameter group number: FS to client, destination-specific
|
||||
*/
|
||||
struct isobusfs_cm_get_fs_props_resp {
|
||||
uint8_t fs_function;
|
||||
uint8_t version_number;
|
||||
uint8_t max_open_files;
|
||||
uint8_t fs_capabilities;
|
||||
uint8_t reserved[4];
|
||||
};
|
||||
|
||||
#define ISOBUSFS_VOL_MODE_PREP_TO_REMOVE BIT(1)
|
||||
#define ISOBUSFS_VOL_MODE_USED_BY_CLIENT BIT(0)
|
||||
#define ISOBUSFS_VOL_MODE_NOT_USED 0
|
||||
|
||||
/**
|
||||
* C.1.6 Volume Status Request
|
||||
* struct isobusfs_cm_vol_stat_req - Volume Status Request structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0000 (Command - Connection Management, see B.1)
|
||||
* Bits 3-0: 0b0010 (Function - Removable Media Status, see B.2)
|
||||
* @volume_mode: Volume Mode (1 byte) (see B.30)
|
||||
* @name_len: Path Name Length (2 bytes) (__le16, see B.12)
|
||||
* @name: Volume Name (variable length) (see B.34)
|
||||
*
|
||||
* Transmission repetition rate: Upon request
|
||||
* Data length: Variable
|
||||
* Parameter group number: Client to FS, specific to the destination
|
||||
*/
|
||||
|
||||
struct isobusfs_cm_vol_stat_req {
|
||||
uint8_t fs_function;
|
||||
uint8_t volume_mode;
|
||||
__le16 name_len;
|
||||
char name[ISOBUSFS_MAX_VOLUME_NAME_LENGTH];
|
||||
};
|
||||
|
||||
|
||||
enum isobusfs_vol_status {
|
||||
ISOBUSFS_VOL_STATUS_PRESENT = 0,
|
||||
ISOBUSFS_VOL_STATUS_IN_USE = 1,
|
||||
ISOBUSFS_VOL_STATUS_PREP_TO_REMOVE = 2,
|
||||
ISOBUSFS_VOL_STATUS_REMOVED = 3,
|
||||
};
|
||||
/**
|
||||
* C.1.7 Volume Status Response
|
||||
* struct isobusfs_cm_vol_stat_res - Volume Status Response structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0000 (Command - Connection Management, see B.1)
|
||||
* Bits 3-0: 0b0010 (Function - Volume Status, see B.2)
|
||||
* @volume_status: Volume Status (1 byte) (see B.31)
|
||||
* @max_time_before_removal: Maximum Time Before Volume Removal (1 byte) (see B.32)
|
||||
* @error_code: Error code (1 byte) (see B.9)
|
||||
* 0: Success
|
||||
* 1: Access denied
|
||||
* 2: Invalid Access
|
||||
* 4: File, path or volume not found
|
||||
* 6: Invalid given source name
|
||||
* 43: Out of memory
|
||||
* 44: Any other error
|
||||
* @name_len: Path Name Length (2 bytes) (__le16, see B.12)
|
||||
* @name: Volume Name (variable length) (see B.34)
|
||||
*
|
||||
* Transmission repetition rate: On request and on change of Volume Status
|
||||
* Data length: Variable
|
||||
* Parameter group number: FS to client, destination-specific or use global address: FF 16
|
||||
*/
|
||||
struct isobusfs_cm_vol_stat_res {
|
||||
uint8_t fs_function;
|
||||
uint8_t volume_status;
|
||||
uint8_t max_time_before_removal;
|
||||
uint8_t error_code;
|
||||
__le16 name_len;
|
||||
char name[ISOBUSFS_MAX_VOLUME_NAME_LENGTH];
|
||||
};
|
||||
|
||||
|
||||
#endif /* ISOBUSFS_CMN_CM_H */
|
||||
46
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cmn_dh.c
Executable file
46
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cmn_dh.c
Executable file
@@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "isobusfs_cmn.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int isobusfs_cmn_dh_validate_dir_path(const char *path, bool writable)
|
||||
{
|
||||
struct stat path_stat;
|
||||
int mode = R_OK;
|
||||
int ret;
|
||||
|
||||
mode |= writable ? W_OK : 0;
|
||||
ret = access(path, mode);
|
||||
if (ret == -1) {
|
||||
ret = -errno;
|
||||
pr_err("failed to acces path %s, for read %s. %s", path,
|
||||
writable ? "and write" : "", strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = stat(path, &path_stat);
|
||||
if (ret == -1) {
|
||||
ret = -errno;
|
||||
pr_err("failed to get stat information on path %s. %s", path,
|
||||
strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!S_ISDIR(path_stat.st_mode)) {
|
||||
pr_err("path %s is not a directory", path);
|
||||
return -ENOTDIR;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
107
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cmn_dh.h
Executable file
107
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cmn_dh.h
Executable file
@@ -0,0 +1,107 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#ifndef _ISOBUSFS_CMN_DH_H
|
||||
#define _ISOBUSFS_CMN_DH_H
|
||||
|
||||
#include "isobusfs_cmn.h"
|
||||
|
||||
/**
|
||||
* C.2.2.2 Get Current Directory Request
|
||||
* struct isobusfs_dh_get_cd_req - Get Current Directory Request structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0001 (Command - Directory Access, see B.1)
|
||||
* Bits 3-0: 0b0000 (Function - Get Current Directory, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @reserved: Reserved, transmit as 0xFF (6 bytes)
|
||||
*
|
||||
* Transmission repetition rate: On request
|
||||
* Data length: 8 bytes
|
||||
* Parameter group number: Client to FS, destination-specific
|
||||
*/
|
||||
struct isobusfs_dh_get_cd_req {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t reserved[6];
|
||||
};
|
||||
|
||||
/**
|
||||
* C.2.2.3 Get Current Directory Response
|
||||
* struct isobusfs_dh_get_cd_res - Get Current Directory Response structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0001 (Command - Directory Access, see B.1)
|
||||
* Bits 3-0: 0b0000 (Function - Get Current Directory, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @error_code: Error code (1 byte) (see B.9)
|
||||
* @total_space: Total space (4 bytes) (in units of 512 bytes, see B.11)
|
||||
* @free_space: Free space (4 bytes) (in units of 512 bytes, see B.11)
|
||||
* @name_len: Path Name Length (2 bytes) (__le16, see B.12)
|
||||
* @name: Path Name (variable length) (see B.13)
|
||||
*
|
||||
* Transmission repetition rate: In response to Get Current Directory Request message
|
||||
* Data length: Variable
|
||||
* Parameter group number: FS to client, destination-specific
|
||||
*/
|
||||
struct isobusfs_dh_get_cd_res {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t error_code;
|
||||
__le32 total_space;
|
||||
__le32 free_space;
|
||||
__le16 name_len;
|
||||
uint8_t name[];
|
||||
};
|
||||
|
||||
/**
|
||||
* C.2.3.2 Change Current Directory Request
|
||||
* struct isobusfs_dh_ccd_req - Change Current Directory Request structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0001 (Command - Directory Access, see B.1)
|
||||
* Bits 3-0: 0b0001 (Function - Change Current Directory, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @name_len: Path Name length (2 bytes) (__le16, see B.12)
|
||||
* @name: Path Name (variable length) (see B.13)
|
||||
*
|
||||
* Transmission repetition rate: On request
|
||||
* Data length: Variable
|
||||
* Parameter group number: Client to FS, destination-specific
|
||||
*/
|
||||
struct isobusfs_dh_ccd_req {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
__le16 name_len;
|
||||
uint8_t name[];
|
||||
};
|
||||
|
||||
/**
|
||||
* C.2.3.3 Change Current Directory Response
|
||||
* struct isobusfs_dh_ccd_res - Change Current Directory Response structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0001 (Command - Directory Access, see B.1)
|
||||
* Bits 3-0: 0b0001 (Function - Change Current Directory, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @error_code: Error code (1 byte) (see B.9)
|
||||
* 0: Success
|
||||
* 1: Access denied
|
||||
* 2: Invalid access
|
||||
* 4: File, path or volume not found
|
||||
* 7: Invalid destination name given
|
||||
* 10: Media is not present
|
||||
* 13: Volume is possibly not initialized
|
||||
* 43: Out of memory
|
||||
* 44: Any other error
|
||||
* @reserved: Reserved, transmit as 0xFF (5 bytes)
|
||||
*
|
||||
* Transmission repetition rate: In response to Change Current Directory Request message
|
||||
* Data length: 8 bytes
|
||||
* Parameter group number: FS to client, destination-specific
|
||||
*/
|
||||
struct isobusfs_dh_ccd_res {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t error_code;
|
||||
uint8_t reserved[5];
|
||||
};
|
||||
|
||||
|
||||
#endif /* _ISOBUSFS_CMN_DH_H */
|
||||
384
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cmn_fa.h
Executable file
384
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cmn_fa.h
Executable file
@@ -0,0 +1,384 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#ifndef _ISOBUSFS_CMN_FA_H
|
||||
#define _ISOBUSFS_CMN_FA_H
|
||||
|
||||
#include <linux/kernel.h>
|
||||
|
||||
#include "isobusfs_cmn.h"
|
||||
|
||||
|
||||
/* B.14 Flags */
|
||||
/**
|
||||
* ISOBUSFS_FA_REPORT_HIDDEN: (version 4 and later)
|
||||
* 0 - Do not report hidden files and folders in directory listing.
|
||||
* 1 - Report hidden files and folders in directory listing.
|
||||
*/
|
||||
#define ISOBUSFS_FA_REPORT_HIDDEN BIT(5)
|
||||
/**
|
||||
* ISOBUSFS_FA_OPEN_EXCLUSIVE:
|
||||
* 0 - Open file for shared read access
|
||||
* 1 - Open file with exclusive access (fails if already open)
|
||||
*/
|
||||
#define ISOBUSFS_FA_OPEN_EXCLUSIVE BIT(4)
|
||||
/**
|
||||
* ISOBUSFS_FA_OPEN_APPEND
|
||||
* 0 - Open file for random access (file pointer set to the start of
|
||||
* the file)
|
||||
* 1 - Open file for appending data to the end of the file (file
|
||||
* pointer set to the end of the file).
|
||||
*/
|
||||
#define ISOBUSFS_FA_OPEN_APPEND BIT(3)
|
||||
/**
|
||||
* ISOBUSFS_FA_CREATE_FILE_DIR:
|
||||
* 0 - Open an existing file (fails if non-existent file)
|
||||
* 1 - Create a new file and/or directories if not yet existing
|
||||
*/
|
||||
#define ISOBUSFS_FA_CREATE_FILE_DIR BIT(2)
|
||||
#define ISOBUSFS_FA_OPEN_MASK GENMASK(1, 0)
|
||||
#define ISOBUSFS_FA_OPEN_FILE_RO 0
|
||||
#define ISOBUSFS_FA_OPEN_FILE_WO 1
|
||||
#define ISOBUSFS_FA_OPEN_FILE_WR 2
|
||||
#define ISOBUSFS_FA_OPEN_DIR 3
|
||||
|
||||
/*
|
||||
* ISO 11783-13:2021 B.15 - File Attributes
|
||||
* Bit 7: Case Sensitivity
|
||||
* 0 - Volume is case-insensitive
|
||||
* 1 - Volume is case-sensitive (Version 3 and later FS support this attribute)
|
||||
* Bit 6: Removability
|
||||
* 0 - Volume is removable
|
||||
* 1 - Volume is not removable
|
||||
* Bit 5: Long Filename Support
|
||||
* 0 - Volume does not support long filenames
|
||||
* 1 - Volume supports long filenames
|
||||
* Bit 4: Directory Specification
|
||||
* 0 - Does not specify a directory
|
||||
* 1 - Specifies a directory
|
||||
* Bit 3: Volume Specification
|
||||
* 0 - Does not specify a volume
|
||||
* 1 - Specifies a volume
|
||||
* Bit 2: Hidden Attribute Support
|
||||
* 0 - Volume does not support hidden attribute
|
||||
* 1 - Volume supports hidden attribute and implementation supports it for the given volume
|
||||
* Bit 1: Hidden Attribute Setting
|
||||
* 0 - "Hidden" attribute is not set
|
||||
* 1 - "Hidden" attribute is set (not applicable unless volume supports hidden attribute)
|
||||
* Bit 0: Read-Only Attribute
|
||||
* 0 - "Read-only" attribute is not set
|
||||
* 1 - "Read-only" attribute is set
|
||||
*/
|
||||
|
||||
#define ISOBUSFS_ATTR_CASE_SENSITIVE BIT(7)
|
||||
#define ISOBUSFS_ATTR_REMOVABLE BIT(6)
|
||||
#define ISOBUSFS_ATTR_LONG_FILENAME BIT(5)
|
||||
#define ISOBUSFS_ATTR_DIRECTORY BIT(4)
|
||||
#define ISOBUSFS_ATTR_VOLUME BIT(3)
|
||||
#define ISOBUSFS_ATTR_HIDDEN_SUPPORT BIT(2)
|
||||
#define ISOBUSFS_ATTR_HIDDEN BIT(1)
|
||||
#define ISOBUSFS_ATTR_READ_ONLY BIT(0)
|
||||
|
||||
/**
|
||||
* C.3.3.2 Open File Request
|
||||
* struct isobusfs_fa_openf_req - Open File Request structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0010 (Command - File Access)
|
||||
* Bits 3-0: 0b0000 (Function - Read File) (see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @flags: Flags (1 byte) (see B.14)
|
||||
* name_len: Path Name Length (2 bytes) (see B.12)
|
||||
* @name: Name (Filename, Path or Wildcard name, depending on Flags)
|
||||
* (Variable length)
|
||||
*
|
||||
* Transmission repetition rate: On request
|
||||
* Data length: Variable
|
||||
* Parameter group number: Client to FS, destination-specific
|
||||
*
|
||||
* Open File Request message is used to request opening a file, directory or
|
||||
* using a wildcard name. The message contains the TAN, Flags, Path Name
|
||||
* Length, and Name.
|
||||
*/
|
||||
struct isobusfs_fa_openf_req {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t flags;
|
||||
__le16 name_len;
|
||||
uint8_t name[];
|
||||
};
|
||||
|
||||
/**
|
||||
* C.3.3.3 Open File Response
|
||||
* struct isobusfs_fa_openf_res - Open File Response structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0010 (Command - File Access) (see B.1)
|
||||
* Bits 3-0: 0b0000 (Function - Open File) (see B.2)
|
||||
* @tan: Transaction number (1 byte)
|
||||
* @error_code: Error code (1 byte), possible values:
|
||||
* 0: Success
|
||||
* 1: Access denied
|
||||
* 2: Invalid access
|
||||
* 3: Too many files open
|
||||
* 4: File, path or volume not found
|
||||
* 6: Invalid given source name
|
||||
* 8: Volume out of free space
|
||||
* 10: Media is not present
|
||||
* 13: Volume is possibly not initialized
|
||||
* 43: Out of memory
|
||||
* 44: Any other error
|
||||
* @handle: File handle (1 byte)
|
||||
* @attributes: File attributes (1 byte)
|
||||
* @reserved: Reserved, transmit as 0xFF (3 bytes)
|
||||
*
|
||||
* Transmission repetition rate: In response to Open File Request message
|
||||
* Data length: 8 bytes
|
||||
* Parameter group number: FS to client, destination-specific
|
||||
*/
|
||||
struct isobusfs_fa_openf_res {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t error_code;
|
||||
uint8_t handle;
|
||||
uint8_t attributes;
|
||||
uint8_t reserved[3];
|
||||
};
|
||||
|
||||
/* B.17 Position mode */
|
||||
/* From the beginning of the file */
|
||||
#define ISOBUSFS_FA_SEEK_SET 0
|
||||
/* From the current position in the file */
|
||||
#define ISOBUSFS_FA_SEEK_CUR 1
|
||||
/* From the end of the file (can only be negative or 0 value) */
|
||||
#define ISOBUSFS_FA_SEEK_END 2
|
||||
|
||||
/**
|
||||
* C.3.4.2 Seek File Request
|
||||
* struct isobusfs_fa_seekf_req - Seek File Request structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0010 (Command - File Access) (see B.1)
|
||||
* Bits 3-0: 0b0001 (Function - Seek File) (see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @handle: Handle (1 byte) (see B.10)
|
||||
* @position_mode: Position mode (1 byte) (see B.17)
|
||||
* @offset: Offset (4 bytes) (see B.18)
|
||||
*/
|
||||
struct isobusfs_fa_seekf_req {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t handle;
|
||||
uint8_t position_mode;
|
||||
__le32 offset;
|
||||
};
|
||||
|
||||
/**
|
||||
* C.3.4.2 Seek File Response
|
||||
* struct isobusfs_fa_seekf_res - Seek File Response structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0010 (Command - File Access) (see B.1)
|
||||
* Bits 3-0: 0b0001 (Function - Seek File) (see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @error_code: Error code (1 byte) (see B.9)
|
||||
* 0: Success
|
||||
* 1: Access denied
|
||||
* 5: Invalid Handle
|
||||
* 11: Failure during a read operation
|
||||
* 42: Invalid request length
|
||||
* 43: Out of memory
|
||||
* 44: Any other error
|
||||
* 45: File pointer at end of file
|
||||
* @reserved: Reserved, transmit as 0xFF (1 byte)
|
||||
* @position: Position (4 bytes) (see B.19)
|
||||
*/
|
||||
struct isobusfs_fa_seekf_res {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t error_code;
|
||||
uint8_t reserved;
|
||||
__le32 position;
|
||||
};
|
||||
|
||||
/**
|
||||
* C.3.5.2 Read File Request
|
||||
* struct isobusfs_fa_readf_req - Read File Request structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0010 (Command: File Access, see B.1)
|
||||
* Bits 3-0: 0b0010 (Function: Read File, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @handle: File handle (1 byte) (see B.10)
|
||||
* @count: Count (2 bytes) (see B.20)
|
||||
* @reserved: Reserved, transmit as 0xFF (3 bytes)
|
||||
* Byte 6: Version 4 and later: Reserved
|
||||
* Version 3 and prior: Report Hidden Files (see B.28)
|
||||
*
|
||||
* Transmission repetition rate: On request
|
||||
* Data length: 8 bytes
|
||||
* Parameter group number: Client to FS, destination-specific
|
||||
*/
|
||||
struct isobusfs_fa_readf_req {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t handle;
|
||||
__le16 count;
|
||||
uint8_t reserved[3];
|
||||
};
|
||||
|
||||
/**
|
||||
* C.3.5.3 Read File Response (Handle-referenced file)
|
||||
* struct isobusfs_read_file_response - Read File Response structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0010 (Command: File Access, see B.1)
|
||||
* Bits 3-0: 0b0010 (Function: Read File, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @error_code: Error code (1 byte) (see B.9)
|
||||
* 0: Success
|
||||
* 1: Access denied
|
||||
* 5: Invalid Handle
|
||||
* 11: Failure during a read operation
|
||||
* 43: Out of memory
|
||||
* 44: Any other error
|
||||
* 45: File pointer at end of file
|
||||
* @count: Count (2 bytes) (see B.20)
|
||||
* @data: Data (variable length)
|
||||
*
|
||||
* Transmission repetition rate: In response to Read File Request message
|
||||
* Data length: Variable
|
||||
* Parameter group number: FS to client, destination-specific
|
||||
*/
|
||||
struct isobusfs_read_file_response {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t error_code;
|
||||
__le16 count;
|
||||
uint8_t data[];
|
||||
};
|
||||
|
||||
/**
|
||||
* C.3.5.4 Read Directory Response (Handle-referenced directory)
|
||||
* struct isobusfs_read_dir_response - Read Directory Response structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0010 (Command: File Access, see B.1)
|
||||
* Bits 3-0: 0b0010 (Function: Read File, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @error_code: Error code (1 byte) (see B.9)
|
||||
* 0: Success
|
||||
* 1: Access denied
|
||||
* 5: Invalid Handle
|
||||
* 11: Failure during a read operation
|
||||
* 43: Out of memory
|
||||
* 44: Any other error
|
||||
* 45: File pointer at end of file
|
||||
* @count: Count (2 bytes) (see B.20)
|
||||
* @data: Data (variable length) (see B.21)
|
||||
*
|
||||
* Transmission repetition rate: In response to Read File Request message
|
||||
* Data length: Variable
|
||||
* Parameter group number: FS to client, destination-specific
|
||||
*/
|
||||
struct isobusfs_read_dir_response {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t error_code;
|
||||
__le16 count;
|
||||
uint8_t data[];
|
||||
};
|
||||
|
||||
/**
|
||||
* C.3.6.2 Write File Request
|
||||
* struct isobusfs_write_file_request - Write File Request structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0010 (Command: File Access, see B.1)
|
||||
* Bits 3-0: 0b0011 (Function: Write File, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @handle: Handle (1 byte) (see B.10)
|
||||
* @count: Count (2 bytes) (see B.20)
|
||||
* @data: Data (variable length)
|
||||
*
|
||||
* Transmission repetition rate: On request
|
||||
* Data length: Variable
|
||||
* Parameter group number: Client to FS, destination-specific
|
||||
*/
|
||||
struct isobusfs_write_file_request {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t handle;
|
||||
__le16 count;
|
||||
uint8_t data[];
|
||||
};
|
||||
|
||||
/**
|
||||
* C.3.6.3 Write File Response
|
||||
* struct isobusfs_write_file_response - Write File Response structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0010 (Command: File Access, see B.1)
|
||||
* Bits 3-0: 0b0011 (Function: Write File, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @error_code: Error code (1 byte) (see B.9)
|
||||
* 0: Success
|
||||
* 1: Access denied
|
||||
* 5: Invalid Handle
|
||||
* 8: Volume out of space
|
||||
* 9: Failure during a write operation
|
||||
* 43: Out of memory
|
||||
* 44: Any other error
|
||||
* @count: Count (2 bytes) (see B.20)
|
||||
* @reserved: Reserved, transmit as 0xFF (3 bytes)
|
||||
*
|
||||
* Transmission repetition rate: In response to Write File Request message
|
||||
* Data length: 8 bytes
|
||||
* Parameter group number: FS to client, destination-specific
|
||||
*/
|
||||
struct isobusfs_write_file_response {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t error_code;
|
||||
__le16 count;
|
||||
uint8_t reserved[3];
|
||||
};
|
||||
|
||||
/**
|
||||
* C.3.7.1 Close File Request
|
||||
* struct isobusfs_close_file_request - Close File Request structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0010 (Command: File Access, see B.1)
|
||||
* Bits 3-0: 0b0100 (Function: Close File, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @handle: Handle (1 byte) (see B.10)
|
||||
* @reserved: Reserved, transmit as 0xFF (5 bytes)
|
||||
*
|
||||
* Transmission repetition rate: On request
|
||||
* Data length: 8 bytes
|
||||
* Parameter group number: Client to FS, destination-specific
|
||||
*/
|
||||
struct isobusfs_close_file_request {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t handle;
|
||||
uint8_t reserved[5];
|
||||
};
|
||||
|
||||
/**
|
||||
* C.3.7.2 Close File Response
|
||||
* struct isobusfs_close_file_response - Close File Response structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0010 (Command: File Access, see B.1)
|
||||
* Bits 3-0: 0b0100 (Function: Close File, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @error_code: Error code (1 byte) (see B.9)
|
||||
* 0: Success
|
||||
* 1: Access denied
|
||||
* 5: Invalid Handle
|
||||
* 8: Volume out of space
|
||||
* 9: Failure during a write operation
|
||||
* 43: Out of memory
|
||||
* 44: Any other error
|
||||
* @reserved: Reserved, transmit as 0xFF (6 bytes)
|
||||
*/
|
||||
struct isobusfs_close_file_res {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t error_code;
|
||||
uint8_t reserved[6];
|
||||
};
|
||||
|
||||
|
||||
#endif /* _LINUX_ISOBUS_FS_H */
|
||||
283
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cmn_fh.h
Executable file
283
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cmn_fh.h
Executable file
@@ -0,0 +1,283 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#ifndef _ISOBUSFS_CMN_FH_H
|
||||
#define _ISOBUSFS_CMN_FH_H
|
||||
|
||||
#include "isobusfs_cmn.h"
|
||||
|
||||
/**
|
||||
* C.4.2.2 Move File Request
|
||||
* struct isobusfs_move_file_request - Move File Request structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0011 (Command - File Handling, see B.1)
|
||||
* Bits 3-0: 0b0000 (Function - Move File, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @file_handling_mode: File Handling Mode (1 byte) (see B.27)
|
||||
* @src_path_name_length: Source Path Name Length (2 bytes) (see B.12)
|
||||
* @dst_path_name_length: Destination Path Name Length (2 bytes) (see B.12)
|
||||
* @src_path: Source Volume, Path, File and Wildcard Name (variable) (see B.34)
|
||||
* @dst_path: Destination Volume, Path, File and Wildcard Name (variable) (see B.34)
|
||||
*
|
||||
* Transmission repetition rate: On request
|
||||
* Data length: Variable
|
||||
* Parameter group number: Client to FS, destination-specific
|
||||
*/
|
||||
struct isobusfs_move_file_request {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t file_handling_mode;
|
||||
__le16 src_path_name_length;
|
||||
__le16 dst_path_name_length;
|
||||
/* Variable length data follows */
|
||||
/* uint8_t src_path[]; */
|
||||
/* uint8_t dst_path[]; */
|
||||
};
|
||||
|
||||
/**
|
||||
* C.4.2.3 Move File Response
|
||||
* struct isobusfs_move_file_response - Move File Response structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0011 (Command - File Handling, see B.1)
|
||||
* Bits 3-0: 0b0000 (Function - Move File, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @error_code: Error code (1 byte) (see B.9)
|
||||
* 0: Success
|
||||
* 1: Access denied
|
||||
* 2: Invalid access
|
||||
* 4: File, path or volume not found
|
||||
* 6: Invalid given source name
|
||||
* 7: Invalid given destination name
|
||||
* 8: Volume out of free space
|
||||
* 9: Failure during read operation
|
||||
* 13: Volume is possibly not initialized
|
||||
* 43: Out of memory
|
||||
* 44: Any other error
|
||||
* @reserved: Reserved, transmit as 0xFF (5 bytes)
|
||||
*
|
||||
* Transmission repetition rate: In response to Move File Request message
|
||||
* Data length: 8 bytes
|
||||
* Parameter group number: FS to client, destination-specific
|
||||
*/
|
||||
struct isobusfs_move_file_response {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t error_code;
|
||||
uint8_t reserved[5];
|
||||
};
|
||||
|
||||
/**
|
||||
* C.4.3.1 Delete File Request
|
||||
* struct isobusfs_delete_file_request - Delete File Request structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0011 (Command - File Handling, see B.1)
|
||||
* Bits 3-0: 0b0001 (Function - Delete File, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @mode: File Handling Mode (1 byte) (see B.27)
|
||||
* @path_len: Path Name Length (2 bytes) (see B.12)
|
||||
* @path: Volume, Path, File, and Wildcard Name (variable length) (see B.34)
|
||||
*
|
||||
* Transmission repetition rate: On request
|
||||
* Data length: Variable
|
||||
* Parameter group number: Client to FS, destination-specific
|
||||
*/
|
||||
struct isobusfs_delete_file_request {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t mode;
|
||||
__le16 path_len;
|
||||
uint8_t path[];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* C.4.3.2 Delete File Response
|
||||
* struct isobusfs_delete_file_response - Delete File Response structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0011 (Command - File Handling, see B.1)
|
||||
* Bits 3-0: 0b0001 (Function - Delete File, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @error_code: Error code (1 byte) (see B.9)
|
||||
* 0: Success
|
||||
* 1: Access denied
|
||||
* 2: Invalid access
|
||||
* 4: File, path, or volume not found
|
||||
* 6: Invalid given file name
|
||||
* 9: Failure during a write operation
|
||||
* 13: Volume is possibly not initialized
|
||||
* 43: Out of memory
|
||||
* 44: Any other error
|
||||
* @reserved: Reserved, transmit as 0xFF (5 bytes)
|
||||
*
|
||||
* Transmission repetition rate: In response to Delete File Request message
|
||||
* Data length: 8 bytes
|
||||
* Parameter group number: FS to client, destination-specific
|
||||
*/
|
||||
struct isobusfs_delete_file_response {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t error_code;
|
||||
uint8_t reserved[5];
|
||||
};
|
||||
|
||||
/**
|
||||
* C.4.4.2 Get File Attributes Request
|
||||
* struct isobusfs_get_file_attributes_request - Get File Attributes Request structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0011 (Command - File Handling, see B.1)
|
||||
* Bits 3-0: 0b0010 (Function - Get File Attributes, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @pathname_len: Pathname Length (2 bytes) (__le16, see B.12)
|
||||
* @pathname: Volume, Path and Filename (variable length) (see B.35)
|
||||
*
|
||||
* Transmission repetition rate: On request
|
||||
* Data length: Variable
|
||||
* Parameter group number: Client to FS, destination-specific
|
||||
*/
|
||||
struct isobusfs_get_file_attributes_request {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
__le16 pathname_len;
|
||||
uint8_t pathname[];
|
||||
};
|
||||
|
||||
/**
|
||||
* C.4.4.3 Get File Attributes Response
|
||||
* struct isobusfs_get_file_attributes_response - Get File Attributes Response structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0011 (Command - File Handling, see B.1)
|
||||
* Bits 3-0: 0b0010 (Function - Get File Attributes, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @error_code: Error code (1 byte) (see B.9)
|
||||
* 0: Success
|
||||
* 1: Access denied
|
||||
* 2: Invalid access
|
||||
* 4: File, path or volume not found
|
||||
* 6: Invalid given name
|
||||
* 11: Failure during a read operation
|
||||
* 13: Volume is possibly not initialized
|
||||
* 43: Out of memory
|
||||
* 44: Any other error
|
||||
* @attributes: File attributes (1 byte) (see B.15)
|
||||
* @file_size: File size (4 bytes) (see B.26)
|
||||
*
|
||||
* Transmission repetition rate: In response to Get File Attributes Request message
|
||||
* Data length: Variable
|
||||
* Parameter group number: FS to client, destination-specific
|
||||
*/
|
||||
struct isobusfs_get_file_attributes_response {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t error_code;
|
||||
uint8_t attributes;
|
||||
__le32 file_size;
|
||||
};
|
||||
|
||||
/**
|
||||
* C.4.5.2 Set File Attributes Request
|
||||
* struct isobusfs_set_file_attributes_request - Set File Attributes Request structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0011 (Command - File Handling, see B.1)
|
||||
* Bits 3-0: 0b0011 (Function - Set File Attributes, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @attributes: Set Attributes Command (1 byte) (see B.16)
|
||||
* @name_length: Name length (__le16) (see B.12)
|
||||
* @name: Path, File and Wildcard Name (n bytes) (see B.34)
|
||||
*
|
||||
* Transmission repetition rate: On request
|
||||
* Data length: Variable
|
||||
* Parameter group number: Client to FS, destination-specific
|
||||
*/
|
||||
struct isobusfs_set_file_attributes_request {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t attributes;
|
||||
__le16 name_length;
|
||||
uint8_t name[]; /* Variable length */
|
||||
};
|
||||
|
||||
/**
|
||||
* C.4.5.3 Set File Attributes Response
|
||||
* struct isobusfs_set_file_attributes_response - Set File Attributes Response structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0011 (Command - File Handling, see B.1)
|
||||
* Bits 3-0: 0b0011 (Function - Set File Attributes, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @error_code: Error code (1 byte) (see B.9)
|
||||
* 0: Success
|
||||
* 1: Access denied
|
||||
* 4: File, path or volume not found
|
||||
* 6: Invalid given name
|
||||
* 8: Volume out of free space
|
||||
* 9: Failure during a write operation
|
||||
* 10: Media is not present
|
||||
* 13: Volume is possibly not initialized
|
||||
* 43: Out of memory
|
||||
* 44: Any other error
|
||||
* @reserved: Reserved, transmit as 0xFF (5 bytes)
|
||||
*
|
||||
* Transmission repetition rate: In response to Get File Attributes Request message
|
||||
* Data length: Variable
|
||||
* Parameter group number: FS to client, destination-specific
|
||||
*/
|
||||
struct isobusfs_set_file_attributes_response {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t error_code;
|
||||
uint8_t reserved[5];
|
||||
};
|
||||
|
||||
/**
|
||||
* C.4.6.2 Get File Date & Time Request
|
||||
* struct isobusfs_get_file_date_time_request - Get File Date & Time Request structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0011 (Command - File Handling, see B.1)
|
||||
* Bits 3-0: 0b0100 (Function - Get File Date & Time, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @name_length: Path Name length (2 bytes) (see B.12)
|
||||
* @name: Path, File and Name (n bytes) (see B.35)
|
||||
*
|
||||
* Transmission repetition rate: On request
|
||||
* Data length: Variable
|
||||
* Parameter group number: Client to FS, destination-specific
|
||||
*/
|
||||
struct isobusfs_get_file_date_time_request {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
__le16 name_length;
|
||||
uint8_t name[]; /* Variable length */
|
||||
};
|
||||
|
||||
/**
|
||||
* C.4.6.2 Get File Date & Time Response
|
||||
* struct isobusfs_get_file_date_time_response - Get File Date & Time Response structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0011 (Command - File Handling, see B.1)
|
||||
* Bits 3-0: 0b0100 (Function - Get File Date & Time, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @error_code: Error code (1 byte) (see B.9)
|
||||
* 0: Success
|
||||
* 1: Access denied
|
||||
* 4: File, path, or volume not found
|
||||
* 6: Invalid given name
|
||||
* 10: Media is not present
|
||||
* 11: Failure during read operation
|
||||
* 13: Volume is possibly not initialized
|
||||
* 43: Out of memory
|
||||
* 44: Any other error
|
||||
* @date: File date (2 bytes) (see B.24)
|
||||
* @time: File time (2 bytes) (see B.25)
|
||||
*
|
||||
* Transmission repetition rate: In response to Get File Date & Time Request message
|
||||
* Data length: 7 bytes
|
||||
* Parameter group number: FS to client, destination-specific
|
||||
*/
|
||||
struct isobusfs_get_file_date_time_response {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t error_code;
|
||||
__le16 date;
|
||||
__le16 time;
|
||||
};
|
||||
|
||||
#endif /* _LINUX_ISOBUS_FS_H */
|
||||
67
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cmn_va.h
Executable file
67
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_cmn_va.h
Executable file
@@ -0,0 +1,67 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#ifndef _ISOBUSFS_CMN_VA_H
|
||||
#define _ISOBUSFS_CMN_VA_H
|
||||
|
||||
#include "isobusfs_cmn.h"
|
||||
|
||||
/**
|
||||
* C.5.2.2 Initialize Volume Request
|
||||
* struct isobusfs_va_init_vol_req - Initialize Volume Request structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0100 (Command - Volume Access, see B.1)
|
||||
* Bits 3-0: 0b0000 (Function - Initialize Volume, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @space: Space (2 bytes) (see B.11)
|
||||
* @volume_flags: Volume Flags (1 byte) (see B.29)
|
||||
* @name_len: Pathname Length (2 bytes) (__le16, see B.12)
|
||||
* @name: Volume, Path and Filename (variable length) (see B.35)
|
||||
*
|
||||
* Transmission repetition rate: On request
|
||||
* Data length: Variable
|
||||
* Parameter group number: Client to FS, destination-specific
|
||||
*/
|
||||
struct isobusfs_va_init_vol_req {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
__le16 space;
|
||||
uint8_t volume_flags;
|
||||
__le16 name_len;
|
||||
uint8_t name[];
|
||||
};
|
||||
|
||||
/**
|
||||
* C.5.2.3 Initialize Volume Response
|
||||
* struct isobusfs_va_init_vol_res - Initialize Volume Response structure
|
||||
* @fs_function: Function and command (1 byte)
|
||||
* Bits 7-4: 0b0100 (Command - Volume Access, see B.1)
|
||||
* Bits 3-0: 0b0000 (Function - Initialize Volume, see B.2)
|
||||
* @tan: Transaction number (1 byte) (see B.8)
|
||||
* @error_code: Error code (1 byte) (see B.9)
|
||||
* 0: Success
|
||||
* 1: Access denied
|
||||
* 4: Volume, path or file not found
|
||||
* 6: Invalid given source name
|
||||
* 8: Volume out of free space
|
||||
* 9: Failure during write operation
|
||||
* 10: Media is not present
|
||||
* 11 Failure during read operation
|
||||
* 12: Function not supported
|
||||
* 13: Volume is possibly not initialized
|
||||
* 43: Out of memory
|
||||
* 44: Any other error
|
||||
* @reserved: Reserved, transmit as 0xFF (4 bytes)
|
||||
*
|
||||
* Transmission repetition rate: In response to Initialize Volume Request message
|
||||
* Data length: 8 bytes
|
||||
* Parameter group number: FS to client, destination-specific
|
||||
*/
|
||||
struct isobusfs_va_init_vol_res {
|
||||
uint8_t fs_function;
|
||||
uint8_t tan;
|
||||
uint8_t error_code;
|
||||
uint8_t reserved[4];
|
||||
};
|
||||
|
||||
#endif /* _ISOBUSFS_CMN_VA_H */
|
||||
66
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_create_test_dirs.sh
Executable file
66
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_create_test_dirs.sh
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: LGPL-2.0-only
|
||||
# SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
mkdir dir1
|
||||
mkdir dir1/dir2
|
||||
mkdir dir1/dir2/dir3
|
||||
mkdir dir1/dir2/dir3/dir4
|
||||
mkdir dir1/dir2/dir3/dir5
|
||||
mkdir MCMC0683
|
||||
mkdir MCMC0683/msd_dir1/msd_dir2
|
||||
mkdir MCMC0683/msd_dir1/msd_dir2/~
|
||||
mkdir MCMC0683/msd_dir1/msd_dir2/~/~tilde_dir
|
||||
mkdir dir1/~
|
||||
mkdir dir1/~/~
|
||||
|
||||
echo "hello" > dir1/dir2/file0
|
||||
|
||||
./isobusfs_create_test_file.sh dir1/dir2/file1k 1024
|
||||
./isobusfs_create_test_file.sh dir1/dir2/file1m 1048576
|
||||
|
||||
File and directory names testing
|
||||
mkdir 'dir1/dir2/special_chars_*?/'
|
||||
mkdir 'dir1/dir2/unicode_名字'
|
||||
|
||||
# Define the long suffix for the filenames
|
||||
long_suffix="abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnop"
|
||||
|
||||
# Loop to create 300 files with long names
|
||||
for count in {1..300}; do
|
||||
touch "long_name_${count}_${long_suffix}"
|
||||
done
|
||||
|
||||
touch dir1/dir2/hidden_file
|
||||
touch dir1/dir2/readonly_file
|
||||
touch dir1/dir2/executable_file
|
||||
touch dir1/dir2/no_read_permission_file
|
||||
|
||||
# Setting permissions
|
||||
chmod 444 dir1/dir2/readonly_file # Read-only file
|
||||
chmod +x dir1/dir2/executable_file # Executable file
|
||||
chmod 000 dir1/dir2/no_read_permission_file # No read permission for anyone
|
||||
|
||||
# Create directories for date problems
|
||||
mkdir "dir1/dir2/y2000_problem"
|
||||
mkdir "dir1/dir2/y2038_problem"
|
||||
mkdir "dir1/dir2/y1979_problem"
|
||||
mkdir "dir1/dir2/y1980_problem"
|
||||
mkdir "dir1/dir2/y2107_problem"
|
||||
mkdir "dir1/dir2/y2108_problem"
|
||||
|
||||
# Change timestamps to reflect specific years
|
||||
# Year 2000
|
||||
touch -d '2000-01-01 00:00:00' dir1/dir2/y2000_problem
|
||||
|
||||
# Year 2038 - beyond 19 January 2038, Unix timestamp issue
|
||||
touch -d '2038-01-20 00:00:00' dir1/dir2/y2038_problem
|
||||
|
||||
# Year 1980 - minimal year supported
|
||||
touch -d '1979-12-31 23:59:59' dir1/dir2/y1979_problem
|
||||
touch -d '1980-01-01 00:00:00' dir1/dir2/y1980_problem
|
||||
|
||||
# Year 2107 - max year 1980+127
|
||||
touch -d '2107-12-31 23:59:59' dir1/dir2/y2107_problem
|
||||
touch -d '2108-01-01 00:00:00' dir1/dir2/y2108_problem
|
||||
|
||||
24
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_create_test_file.sh
Executable file
24
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_create_test_file.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: LGPL-2.0-only
|
||||
# SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
# Arguments: path to the file and the file size
|
||||
FILEPATH=$1
|
||||
FILESIZE=$2
|
||||
|
||||
# Variable to store the increasing number
|
||||
counter=0
|
||||
|
||||
# XOR pattern (change this to whatever you like)
|
||||
pattern=0xdeadbeef
|
||||
|
||||
# Calculate the number of iterations needed
|
||||
let iterations=$FILESIZE/4
|
||||
|
||||
# Use 'dd' command to generate the file with increasing numbers
|
||||
for ((i=0; i<$iterations; i++)); do
|
||||
# Print the 32-bit number in binary format to the file
|
||||
printf "%08x" $((counter ^ pattern)) | xxd -r -p >> $FILEPATH
|
||||
let counter=counter+1
|
||||
done
|
||||
|
||||
803
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_srv.c
Executable file
803
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_srv.c
Executable file
@@ -0,0 +1,803 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <poll.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include <net/if.h>
|
||||
|
||||
#include <linux/net_tstamp.h>
|
||||
|
||||
#include "isobusfs_srv.h"
|
||||
|
||||
int isobusfs_srv_sendto(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg, const void *buf,
|
||||
size_t buf_size)
|
||||
{
|
||||
struct sockaddr_can addr = msg->peername;
|
||||
|
||||
addr.can_addr.j1939.pgn = ISOBUSFS_PGN_FS_TO_CL;
|
||||
return isobusfs_sendto(msg->sock, buf, buf_size, &addr,
|
||||
&priv->tx_buf_log);
|
||||
}
|
||||
|
||||
int isobusfs_srv_send_error(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg,
|
||||
enum isobusfs_error err)
|
||||
{
|
||||
uint8_t buf[ISOBUSFS_MIN_TRANSFER_LENGH];
|
||||
|
||||
/* copy 2 bytes with command group, function and TAN from the source
|
||||
* package
|
||||
*/
|
||||
memcpy(buf, &msg->buf[0], 2);
|
||||
buf[2] = err;
|
||||
|
||||
/* not used space should be filled with 0xff */
|
||||
memset(&buf[3], 0xff, ARRAY_SIZE(buf) - 3);
|
||||
|
||||
pr_debug("> tx error: 0x%02x (%s)", err, isobusfs_error_to_str(err));
|
||||
|
||||
return isobusfs_srv_sendto(priv, msg, &buf[0], ARRAY_SIZE(buf));
|
||||
}
|
||||
|
||||
/* server side rx */
|
||||
static int isobusfs_srv_rx_fs(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg)
|
||||
{
|
||||
enum isobusfs_cg cg = isobusfs_buf_to_cmd(msg->buf);
|
||||
uint8_t addr = msg->peername.can_addr.j1939.addr;
|
||||
struct isobusfs_srv_client *client;
|
||||
int ret = 0;
|
||||
|
||||
switch (cg) {
|
||||
case ISOBUSFS_CG_CONNECTION_MANAGMENT:
|
||||
case ISOBUSFS_CG_DIRECTORY_HANDLING:
|
||||
case ISOBUSFS_CG_FILE_ACCESS:
|
||||
case ISOBUSFS_CG_FILE_HANDLING:
|
||||
case ISOBUSFS_CG_VOLUME_HANDLING:
|
||||
break;
|
||||
default:
|
||||
pr_warn("%s: unsupported command group (%i)", __func__,
|
||||
cg);
|
||||
|
||||
/* ISO 11783-13:2021 - Annex C.1.1 Overview:
|
||||
* If a client sends a command, which is not defined withing this
|
||||
* documentation, the file server shall respond with a
|
||||
* NACK (ISO 11783-3:2018 Chapter 5.4.5)
|
||||
*/
|
||||
isobusfs_send_nack(priv->sock_nack, msg);
|
||||
|
||||
/* Not a critical error */
|
||||
return 0;
|
||||
}
|
||||
|
||||
client = isobusfs_srv_get_client(priv, addr);
|
||||
if (!client) {
|
||||
pr_warn("%s: client not found", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
msg->sock = client->sock;
|
||||
|
||||
switch (cg) {
|
||||
case ISOBUSFS_CG_CONNECTION_MANAGMENT:
|
||||
ret = isobusfs_srv_rx_cg_cm(priv, msg);
|
||||
break;
|
||||
case ISOBUSFS_CG_DIRECTORY_HANDLING:
|
||||
ret = isobusfs_srv_rx_cg_dh(priv, msg);
|
||||
break;
|
||||
case ISOBUSFS_CG_FILE_ACCESS:
|
||||
ret = isobusfs_srv_rx_cg_fa(priv, msg);
|
||||
break;
|
||||
case ISOBUSFS_CG_FILE_HANDLING:
|
||||
ret = isobusfs_srv_rx_cg_fh(priv, msg);
|
||||
break;
|
||||
case ISOBUSFS_CG_VOLUME_HANDLING:
|
||||
ret = isobusfs_srv_rx_cg_vh(priv, msg);
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int isobusfs_srv_rx_ack(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg)
|
||||
{
|
||||
enum isobusfs_ack_ctrl ctrl = msg->buf[0];
|
||||
|
||||
switch (ctrl) {
|
||||
case ISOBUS_ACK_CTRL_ACK:
|
||||
pr_debug("< rx: ACK?????");
|
||||
break;
|
||||
case ISOBUS_ACK_CTRL_NACK:
|
||||
/* we did something wrong */
|
||||
pr_debug("< rx: NACK!!!!!");
|
||||
isobusfs_dump_tx_data(&priv->tx_buf_log);
|
||||
break;
|
||||
default:
|
||||
pr_warn("%s: unsupported ACK control: %i", __func__, ctrl);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Not a critical error */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isobusfs_srv_rx_buf(struct isobusfs_srv_priv *priv, struct isobusfs_msg *msg)
|
||||
{
|
||||
pgn_t pgn = msg->peername.can_addr.j1939.pgn;
|
||||
int ret;
|
||||
|
||||
switch (pgn) {
|
||||
case ISOBUSFS_PGN_CL_TO_FS:
|
||||
ret = isobusfs_srv_rx_fs(priv, msg);
|
||||
break;
|
||||
case ISOBUS_PGN_ACK:
|
||||
ret = isobusfs_srv_rx_ack(priv, msg);
|
||||
break;
|
||||
default:
|
||||
pr_warn("%s: unsupported PGN: %i", __func__, pgn);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int isobusfs_srv_recv_one(struct isobusfs_srv_priv *priv, int sock)
|
||||
{
|
||||
struct isobusfs_msg *msg;
|
||||
int flags = 0;
|
||||
int ret;
|
||||
|
||||
msg = malloc(sizeof(*msg));
|
||||
if (!msg) {
|
||||
pr_err("can't allocate rx msg struct");
|
||||
goto done;
|
||||
}
|
||||
msg->buf_size = ISOBUSFS_MAX_TRANSFER_LENGH;
|
||||
msg->peer_addr_len = sizeof(msg->peername);
|
||||
msg->sock = sock;
|
||||
|
||||
ret = recvfrom(sock, &msg->buf[0], msg->buf_size, flags,
|
||||
(struct sockaddr *)&msg->peername, &msg->peer_addr_len);
|
||||
if (ret < 0) {
|
||||
pr_err("recvfrom(): %i (%s)", errno, strerror(errno));
|
||||
goto free_msg;
|
||||
}
|
||||
|
||||
if (ret < ISOBUSFS_MIN_TRANSFER_LENGH) {
|
||||
pr_warn("buf is less then min transfer: %i < %i. Dropping.",
|
||||
ret, ISOBUSFS_MIN_TRANSFER_LENGH);
|
||||
|
||||
/* TODO: The file server shall respond with Error Code 47
|
||||
* Malformed Request, if the message is shorter than expected.
|
||||
*/
|
||||
isobusfs_send_nack(priv->sock_nack, msg);
|
||||
|
||||
goto free_msg;
|
||||
}
|
||||
|
||||
msg->len = ret;
|
||||
|
||||
ret = isobusfs_srv_rx_buf(priv, msg);
|
||||
if (ret < 0) {
|
||||
pr_err("unhandled error by rx buf: %i", ret);
|
||||
goto free_msg;
|
||||
}
|
||||
|
||||
free_msg:
|
||||
free(msg);
|
||||
done:
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int isobusfs_srv_handle_events(struct isobusfs_srv_priv *priv, unsigned int nfds)
|
||||
{
|
||||
int ret;
|
||||
unsigned int n;
|
||||
|
||||
for (n = 0; n < nfds && n < priv->cmn.epoll_events_size; ++n) {
|
||||
struct epoll_event *ev = &priv->cmn.epoll_events[n];
|
||||
|
||||
if (!ev->events) {
|
||||
warn("no events");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ev->data.fd == priv->sock_fss) {
|
||||
if (ev->events & POLLERR) {
|
||||
struct isobusfs_err_msg emsg = {
|
||||
.stats = &priv->st_msg_stats,
|
||||
};
|
||||
|
||||
ret = isobusfs_recv_err(priv->sock_fss, &emsg);
|
||||
if (ret)
|
||||
pr_warn("error queue reported error: %i", ret);
|
||||
}
|
||||
}
|
||||
|
||||
if (ev->events & POLLIN) {
|
||||
ret = isobusfs_srv_recv_one(priv, ev->data.fd);
|
||||
if (ret) {
|
||||
warn("recv one");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isobusfs_srv_handle_periodic_tasks(struct isobusfs_srv_priv *priv)
|
||||
{
|
||||
/* remove timeouted clients */
|
||||
isobusfs_srv_remove_timeouted_clients(priv);
|
||||
|
||||
/* this function will send status only if it is proper time to do so */
|
||||
return isobusfs_srv_fss_send(priv);
|
||||
}
|
||||
|
||||
static int isobusfs_srv_process_events_and_tasks(struct isobusfs_srv_priv *priv)
|
||||
{
|
||||
int ret, nfds;
|
||||
|
||||
ret = libj1939_prepare_for_events(&priv->cmn, &nfds, false);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (nfds > 0) {
|
||||
ret = isobusfs_srv_handle_events(priv, nfds);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return isobusfs_srv_handle_periodic_tasks(priv);
|
||||
}
|
||||
|
||||
static int isobusfs_srv_sock_fss_prepare(struct isobusfs_srv_priv *priv)
|
||||
{
|
||||
struct sockaddr_can addr = priv->addr;
|
||||
int ret;
|
||||
|
||||
ret = libj1939_open_socket();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
priv->sock_fss = ret;
|
||||
|
||||
ret = isobusfs_cmn_configure_error_queue(priv->sock_fss);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* keep address and name and overwrite PGN */
|
||||
/* TOOD: actually, this is PGN input filter. Should we use different
|
||||
* PGN?
|
||||
*/
|
||||
addr.can_addr.j1939.pgn = ISOBUSFS_PGN_CL_TO_FS;
|
||||
ret = libj1939_bind_socket(priv->sock_fss, &addr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = libj1939_set_broadcast(priv->sock_fss);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = isobusfs_cmn_set_linger(priv->sock_fss);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = libj1939_socket_prio(priv->sock_fss, ISOBUSFS_PRIO_FSS);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* connect to broadcast address */
|
||||
addr.can_addr.j1939.name = J1939_NO_NAME;
|
||||
addr.can_addr.j1939.addr = J1939_NO_ADDR;
|
||||
addr.can_addr.j1939.pgn = ISOBUSFS_PGN_FS_TO_CL;
|
||||
ret = isobusfs_cmn_connect_socket(priv->sock_fss, &addr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* poll for errors to get confirmation if our packets are send */
|
||||
return libj1939_add_socket_to_epoll(priv->cmn.epoll_fd, priv->sock_fss,
|
||||
EPOLLERR);
|
||||
}
|
||||
|
||||
static int isobusfs_srv_sock_in_prepare(struct isobusfs_srv_priv *priv)
|
||||
{
|
||||
struct sockaddr_can addr = priv->addr;
|
||||
int ret;
|
||||
|
||||
ret = libj1939_open_socket();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
priv->sock_in = ret;
|
||||
|
||||
/* keep address and name and overwrite PGN */
|
||||
addr.can_addr.j1939.pgn = ISOBUSFS_PGN_CL_TO_FS;
|
||||
ret = libj1939_bind_socket(priv->sock_in, &addr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return libj1939_add_socket_to_epoll(priv->cmn.epoll_fd, priv->sock_in,
|
||||
EPOLLIN);
|
||||
}
|
||||
|
||||
static int isobusfs_srv_sock_nack_prepare(struct isobusfs_srv_priv *priv)
|
||||
{
|
||||
struct sockaddr_can addr = priv->addr;
|
||||
int ret;
|
||||
|
||||
ret = libj1939_open_socket();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
priv->sock_nack = ret;
|
||||
|
||||
addr.can_addr.j1939.pgn = ISOBUS_PGN_ACK;
|
||||
ret = libj1939_bind_socket(priv->sock_nack, &addr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = libj1939_socket_prio(priv->sock_nack, ISOBUSFS_PRIO_ACK);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* poll for errors to get confirmation if our packets are send */
|
||||
return libj1939_add_socket_to_epoll(priv->cmn.epoll_fd,
|
||||
priv->sock_nack, EPOLLIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_srv_sock_prepare - Prepares the control socket and epoll instance
|
||||
* @priv: pointer to the isobusfs_srv_priv structure
|
||||
*
|
||||
* This function calls multiple helper functions to prepare the control socket
|
||||
* and epoll instance for the ISOBUS server.
|
||||
* Returns: 0 on success, negative error code on failure
|
||||
*/
|
||||
static int isobusfs_srv_sock_prepare(struct isobusfs_srv_priv *priv)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = libj1939_create_epoll();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
priv->cmn.epoll_fd = ret;
|
||||
|
||||
priv->cmn.epoll_events = calloc(ISOBUSFS_SRV_MAX_EPOLL_EVENTS,
|
||||
sizeof(struct epoll_event));
|
||||
if (!priv->cmn.epoll_events)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->cmn.epoll_events_size = ISOBUSFS_SRV_MAX_EPOLL_EVENTS;
|
||||
|
||||
ret = isobusfs_srv_sock_fss_prepare(priv);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = isobusfs_srv_sock_in_prepare(priv);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return isobusfs_srv_sock_nack_prepare(priv);
|
||||
}
|
||||
|
||||
static int isobusfs_srv_parse_volume_ext(struct isobusfs_srv_priv *priv,
|
||||
const char *optarg,
|
||||
char ***volumes, int *volumes_count)
|
||||
{
|
||||
char *volume_info;
|
||||
char *token;
|
||||
|
||||
if (*volumes_count >= ISOBUSFS_SRV_MAX_VOLUMES) {
|
||||
pr_err("Maximum number of volumes (%d) exceeded\n",
|
||||
ISOBUSFS_SRV_MAX_VOLUMES);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
volume_info = strdup(optarg);
|
||||
token = strtok(volume_info, ",");
|
||||
while (token) {
|
||||
*volumes_count += 1;
|
||||
*volumes = realloc(*volumes,
|
||||
*volumes_count * sizeof(char *));
|
||||
*volumes[*volumes_count - 1] = strdup(token);
|
||||
token = strtok(NULL, ",");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isobusfs_srv_parse_volumes(struct isobusfs_srv_priv *priv,
|
||||
const char *optarg)
|
||||
{
|
||||
struct isobusfs_srv_volume *volumes = priv->volumes;
|
||||
char *volume_info, *name, *path;
|
||||
size_t name_len, path_len;
|
||||
int ret;
|
||||
|
||||
if (priv->volume_count >= ISOBUSFS_SRV_MAX_VOLUMES) {
|
||||
pr_err("Maximum number of volumes (%d) exceeded\n",
|
||||
ISOBUSFS_SRV_MAX_VOLUMES);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
volume_info = strdup(optarg);
|
||||
name = strtok(volume_info, ":");
|
||||
path = strtok(NULL, ":");
|
||||
|
||||
if (!name || !path) {
|
||||
pr_err("Error: volume or path name is missing\n");
|
||||
ret = -EINVAL;
|
||||
goto free_volume_info;
|
||||
}
|
||||
|
||||
name_len = strnlen(name, ISOBUSFS_SRV_MAX_VOLUME_NAME_LEN + 2);
|
||||
if (name_len > ISOBUSFS_SRV_MAX_VOLUME_NAME_LEN) {
|
||||
pr_err("Error: Volume name exceeds maximum length (%d)\n",
|
||||
ISOBUSFS_SRV_MAX_VOLUME_NAME_LEN);
|
||||
ret = -EINVAL;
|
||||
goto free_volume_info;
|
||||
}
|
||||
|
||||
path_len = strnlen(path, ISOBUSFS_SRV_MAX_PATH_LEN + 2);
|
||||
if (path_len > ISOBUSFS_SRV_MAX_PATH_LEN) {
|
||||
pr_err("Error: Path name exceeds maximum length (%d)\n",
|
||||
ISOBUSFS_SRV_MAX_PATH_LEN);
|
||||
ret = -EINVAL;
|
||||
goto free_volume_info;
|
||||
}
|
||||
|
||||
volumes[priv->volume_count].name = strdup(name);
|
||||
volumes[priv->volume_count].path = strdup(path);
|
||||
priv->volume_count++;
|
||||
|
||||
ret = 0;
|
||||
free_volume_info:
|
||||
free(volume_info);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void isobusfs_srv_generate_mfs_dir_name(struct isobusfs_srv_priv *priv)
|
||||
{
|
||||
uint16_t manufacturer_code = (priv->local_name >> 21) & 0x07FF;
|
||||
|
||||
snprintf(&priv->mfs_dir[0], sizeof(priv->mfs_dir), "MCMC%04u",
|
||||
manufacturer_code);
|
||||
}
|
||||
|
||||
static void isobusfs_srv_print_help(void)
|
||||
{
|
||||
|
||||
printf("Usage: isobusfs-srv [options]\n");
|
||||
printf("Options:\n");
|
||||
printf(" --address <local_address_hex> or -a <local_address_hex>\n");
|
||||
printf(" --default-volume <volume_name> or -d <volume_name>\n");
|
||||
printf(" --interface <interface_name> or -i <interface_name>\n");
|
||||
printf(" --log-level <logging_level> or -l <loging_level>\n");
|
||||
printf(" --name <local_name_hex> or -n <local_name_hex>\n");
|
||||
printf(" --removable-volume <volume_name_1,volume_name_2,...> or -r <volume_name_1,volume_name_2,...>\n");
|
||||
printf(" --server-version <version_number> or -s <version_number>\n");
|
||||
printf(" --volume <volume_name>:<path> or -v <volume_name>:<path>\n");
|
||||
printf(" --writeable-volume <volume_name_1,volume_name_2,...> or -w <volume_name_1,volume_name_2,...>\n");
|
||||
printf("Note: Local address and local name are mutually exclusive\n");
|
||||
}
|
||||
|
||||
static int isobusfs_srv_parse_args(struct isobusfs_srv_priv *priv, int argc,
|
||||
char *argv[])
|
||||
{
|
||||
struct sockaddr_can *addr = &priv->addr;
|
||||
char **removable_volumes = NULL;
|
||||
char **writeable_volumes = NULL;
|
||||
uint32_t local_address = 0;
|
||||
uint64_t local_name = 0;
|
||||
bool local_address_set = false;
|
||||
bool local_name_set = false;
|
||||
bool voluem_set = false;
|
||||
bool interface_set = false;
|
||||
int ret, level, version;
|
||||
int opt, i, j;
|
||||
int writeable_volumes_count = 0;
|
||||
int long_index = 0;
|
||||
|
||||
struct option long_options[] = {
|
||||
{"address", required_argument, NULL, 'a'},
|
||||
{"default-volume", required_argument, NULL, 'd'},
|
||||
{"interface", required_argument, NULL, 'i'},
|
||||
{"log-level", required_argument, NULL, 'l'},
|
||||
{"name", required_argument, NULL, 'n'},
|
||||
{"removable-volume", required_argument, 0, 'r'},
|
||||
{"server-version", required_argument, 0, 's'},
|
||||
{"volume", required_argument, NULL, 'v'},
|
||||
{"writeable-volume", required_argument, 0, 'w'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{NULL, 0, NULL, 0}
|
||||
};
|
||||
|
||||
while ((opt = getopt_long(argc, argv, "a:d:i:l:n:r:s:v:w:h",
|
||||
long_options, &long_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 'a': {
|
||||
if (local_name_set) {
|
||||
pr_err("Both local address and local name provided, they are mutually exclusive\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
sscanf(optarg, "%x", &local_address);
|
||||
addr->can_addr.j1939.addr = local_address;
|
||||
local_address_set = true;
|
||||
break;
|
||||
}
|
||||
case 'd': {
|
||||
priv->default_volume = strdup(optarg);
|
||||
break;
|
||||
}
|
||||
case 'i': {
|
||||
addr->can_ifindex = if_nametoindex(optarg);
|
||||
if (!addr->can_ifindex) {
|
||||
pr_err("Interface %s not found. Error: %d (%s)\n",
|
||||
optarg, errno, strerror(errno));
|
||||
return -EINVAL;
|
||||
}
|
||||
interface_set = true;
|
||||
break;
|
||||
}
|
||||
case 'l': {
|
||||
level = strtoul(optarg, NULL, 0);
|
||||
if (level < LOG_LEVEL_ERROR || level > LOG_LEVEL_DEBUG)
|
||||
pr_err("invalid debug level %d", level);
|
||||
isobusfs_log_level_set(level);
|
||||
break;
|
||||
}
|
||||
case 'n': {
|
||||
if (local_address_set) {
|
||||
pr_err("Both local address and local name provided, they are mutually exclusive\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
sscanf(optarg, "%" SCNx64, &local_name);
|
||||
priv->local_name = local_name;
|
||||
addr->can_addr.j1939.name = local_name;
|
||||
local_name_set = true;
|
||||
break;
|
||||
}
|
||||
case 'r': {
|
||||
ret = isobusfs_srv_parse_volume_ext(priv, optarg,
|
||||
&removable_volumes,
|
||||
&priv->removable_volumes_count);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
break;
|
||||
}
|
||||
case 's': {
|
||||
version = atoi(optarg);
|
||||
if (version < 0 || version > 255)
|
||||
pr_err("Invalid server version %d. Using default version: %d",
|
||||
version, ISOBUSFS_SRV_VERSION);
|
||||
break;
|
||||
}
|
||||
case 'v': {
|
||||
ret = isobusfs_srv_parse_volumes(priv, optarg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
voluem_set = true;
|
||||
break;
|
||||
}
|
||||
case 'w': {
|
||||
|
||||
ret = isobusfs_srv_parse_volume_ext(priv, optarg,
|
||||
&writeable_volumes,
|
||||
&writeable_volumes_count);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
break;
|
||||
}
|
||||
case 'h':
|
||||
default:
|
||||
isobusfs_srv_print_help();
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!local_address_set && !local_name_set) {
|
||||
pr_err("Error: local address or local name is missing");
|
||||
isobusfs_srv_print_help();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!voluem_set) {
|
||||
pr_err("Error: volume is missing");
|
||||
isobusfs_srv_print_help();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!interface_set) {
|
||||
pr_err("Error: interface is missing");
|
||||
isobusfs_srv_print_help();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!priv->volume_count) {
|
||||
pr_err("Error: volume is missing");
|
||||
isobusfs_srv_print_help();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (priv->volume_count == 1) {
|
||||
if (priv->default_volume) {
|
||||
pr_err("Error: default volume is not needed for single volume");
|
||||
isobusfs_srv_print_help();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
priv->default_volume = strdup(priv->volumes[0].name);
|
||||
} else if (priv->volume_count > 1) {
|
||||
if (!priv->default_volume) {
|
||||
pr_err("Error: default volume is missing");
|
||||
isobusfs_srv_print_help();
|
||||
return -EINVAL;
|
||||
}
|
||||
/* Check if default volume is valid */
|
||||
for (i = 0; i < priv->volume_count; i++) {
|
||||
if (!strcmp(priv->default_volume, priv->volumes[i].name))
|
||||
break;
|
||||
if (i == priv->volume_count - 1) {
|
||||
pr_err("Error: default volume should be one of defined volumes");
|
||||
isobusfs_srv_print_help();
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < priv->removable_volumes_count; i++) {
|
||||
bool found = false;
|
||||
|
||||
for (j = 0; j < priv->volume_count; j++) {
|
||||
if (!strcmp(removable_volumes[i],
|
||||
priv->volumes[j].name)) {
|
||||
priv->volumes[j].removable = true;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
pr_err("Error: removable volume %s is not defined",
|
||||
removable_volumes[i]);
|
||||
isobusfs_srv_print_help();
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < writeable_volumes_count; i++) {
|
||||
bool found = false;
|
||||
|
||||
for (j = 0; j < priv->volume_count; j++) {
|
||||
if (!strcmp(writeable_volumes[i],
|
||||
priv->volumes[j].name)) {
|
||||
priv->volumes[j].writeable = true;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
pr_err("Error: writeable volume %s is not defined",
|
||||
writeable_volumes[i]);
|
||||
isobusfs_srv_print_help();
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < priv->volume_count; i++) {
|
||||
ret = isobusfs_cmn_dh_validate_dir_path(priv->volumes[i].path,
|
||||
priv->volumes[i].writeable);
|
||||
if (ret < 0) {
|
||||
if (ret == -ENOTDIR)
|
||||
pr_err("Error: path %s is not a directory",
|
||||
priv->volumes[i].path);
|
||||
else if (ret == -EACCES)
|
||||
pr_err("Error: can't access path %s, error %i (%s)",
|
||||
priv->volumes[i].path, ret, strerror(ret));
|
||||
|
||||
/* If volume is not removable, return error. Probably
|
||||
* we will be able to detect it later.
|
||||
*/
|
||||
if (!priv->volumes[i].removable)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (!local_name_set)
|
||||
pr_warn("local name is not set. Wont be able to generate proper manufacturer-specific directory name. Falling mack to MCMC0000");
|
||||
isobusfs_srv_generate_mfs_dir_name(priv);
|
||||
|
||||
pr_debug("Server configuration:");
|
||||
pr_debug(" local NAME: 0x%" SCNx64, priv->local_name);
|
||||
pr_debug(" manufacturer-specific directory: %s", priv->mfs_dir);
|
||||
pr_debug("Configured volumes:");
|
||||
for (i = 0; i < priv->volume_count; i++) {
|
||||
pr_debug(" %s: %s", priv->volumes[i].name,
|
||||
priv->volumes[i].path);
|
||||
pr_debug(" %s", priv->volumes[i].writeable ? "writeable" : "read-only");
|
||||
pr_debug(" %s", priv->volumes[i].removable ? "removable" : "non-removable");
|
||||
}
|
||||
|
||||
for (i = 0; i < priv->removable_volumes_count; i++)
|
||||
free(removable_volumes[i]);
|
||||
|
||||
free(removable_volumes);
|
||||
|
||||
for (i = 0; i < writeable_volumes_count; i++)
|
||||
free(writeable_volumes[i]);
|
||||
|
||||
free(writeable_volumes);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct isobusfs_srv_priv *priv;
|
||||
struct timespec ts;
|
||||
int ret;
|
||||
|
||||
/* Allocate memory for the private structure */
|
||||
priv = malloc(sizeof(*priv));
|
||||
if (!priv)
|
||||
err(EXIT_FAILURE, "can't allocate priv");
|
||||
|
||||
/* Clear memory for the private structure */
|
||||
memset(priv, 0, sizeof(*priv));
|
||||
|
||||
/* Initialize sockaddr_can with a non-configurable PGN */
|
||||
libj1939_init_sockaddr_can(&priv->addr, J1939_NO_PGN);
|
||||
|
||||
priv->server_version = ISOBUSFS_SRV_VERSION;
|
||||
/* Parse command line arguments */
|
||||
ret = isobusfs_srv_parse_args(priv, argc, argv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Prepare sockets for the server */
|
||||
ret = isobusfs_srv_sock_prepare(priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Initialize File Server Status structure */
|
||||
isobusfs_srv_fss_init(priv);
|
||||
/* Initialize client structures */
|
||||
isobusfs_srv_init_clients(priv);
|
||||
|
||||
/* Init next st_next_send_time value to avoid warnings */
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
priv->cmn.next_send_time = ts;
|
||||
|
||||
/* Start the isobusfsd server */
|
||||
pr_info("Starting isobusfs-srv");
|
||||
while (1) {
|
||||
ret = isobusfs_srv_process_events_and_tasks(priv);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Close epoll and control sockets */
|
||||
close(priv->cmn.epoll_fd);
|
||||
free(priv->cmn.epoll_events);
|
||||
close(priv->sock_fss);
|
||||
close(priv->sock_in);
|
||||
close(priv->sock_nack);
|
||||
|
||||
return ret;
|
||||
}
|
||||
173
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_srv.h
Executable file
173
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_srv.h
Executable file
@@ -0,0 +1,173 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#ifndef ISOBUSFS_SRV_H
|
||||
#define ISOBUSFS_SRV_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#include "isobusfs_cmn.h"
|
||||
#include "isobusfs_cmn_cm.h"
|
||||
|
||||
#define ISOBUSFS_SRV_VERSION 4
|
||||
#define ISOBUSFS_SRV_MAX_CTRL_SOCKETS 1
|
||||
#define ISOBUSFS_SRV_MAX_CLIENT_SOCKETS 255
|
||||
#define ISOBUSFS_SRV_MAX_EPOLL_EVENTS (ISOBUSFS_SRV_MAX_CTRL_SOCKETS + \
|
||||
ISOBUSFS_SRV_MAX_CLIENT_SOCKETS)
|
||||
#define ISOBUSFS_SRV_MAX_OPENED_HANDLES 255
|
||||
/*
|
||||
* ISO 11783-13:2021 standard does not explicitly specify a maximum number of
|
||||
* clients that can be supported on the network. However, the ISO 11783 standard
|
||||
* is built on top of the SAE J1939 protocol, which has a maximum of 238
|
||||
* available addresses for nodes. This number is calculated from the available
|
||||
* address range for assignment to nodes on the network, which includes 127
|
||||
* addresses in the range 1-127 and 111 addresses in the range 248-254,
|
||||
* inclusive. Some addresses in the total range (0-255) are reserved for
|
||||
* specific purposes, such as broadcast messages and null addresses.
|
||||
*
|
||||
* The maximum number of 238 nodes includes both clients and servers, so the
|
||||
* actual number of clients that can be supported will be less than 238.
|
||||
*
|
||||
* It is important to note that the practical limit of clients in an ISO
|
||||
* 11783-13 network could be lower due to factors such as network bandwidth,
|
||||
* performance constraints of the individual devices, and the complexity of the
|
||||
* network.
|
||||
*/
|
||||
#define ISOBUSFS_SRV_MAX_CLIENTS 237
|
||||
|
||||
enum isobusfs_srv_fss_state {
|
||||
ISOBUSFS_SRV_STATE_IDLE = 0, /* send status with 2000ms interval */
|
||||
ISOBUSFS_SRV_STATE_STAT_CHANGE_1, /* send status with 200ms interval */
|
||||
ISOBUSFS_SRV_STATE_STAT_CHANGE_2, /* send status with 200ms interval */
|
||||
ISOBUSFS_SRV_STATE_STAT_CHANGE_3, /* send status with 200ms interval */
|
||||
ISOBUSFS_SRV_STATE_STAT_CHANGE_4, /* send status with 200ms interval */
|
||||
ISOBUSFS_SRV_STATE_STAT_CHANGE_5, /* send status with 200ms interval */
|
||||
ISOBUSFS_SRV_STATE_BUSY, /* send status with 200ms interval */
|
||||
};
|
||||
|
||||
struct isobusfs_srv_client {
|
||||
int sock;
|
||||
struct timespec last_received;
|
||||
uint8_t addr;
|
||||
uint8_t tan;
|
||||
uint8_t version;
|
||||
char current_dir[ISOBUSFS_SRV_MAX_PATH_LEN];
|
||||
};
|
||||
|
||||
struct isobusfs_srv_volume {
|
||||
char *name;
|
||||
char *path;
|
||||
bool removable;
|
||||
bool writeable;
|
||||
int refcount;
|
||||
struct isobusfs_srv_client *clients[ISOBUSFS_SRV_MAX_CLIENTS];
|
||||
};
|
||||
|
||||
struct isobusfs_srv_handles {
|
||||
char *path;
|
||||
int refcount;
|
||||
int fd;
|
||||
off_t offset;
|
||||
int32_t dir_pos;
|
||||
DIR *dir;
|
||||
struct isobusfs_srv_client *clients[ISOBUSFS_SRV_MAX_CLIENTS];
|
||||
};
|
||||
|
||||
struct isobusfs_srv_priv {
|
||||
/* incoming traffic from peers */
|
||||
int sock_in;
|
||||
/*
|
||||
* egress only File Server Status broadcast packets with different
|
||||
* prio
|
||||
*/
|
||||
int sock_fss;
|
||||
/*
|
||||
* bidirectional socket for NACK packets.
|
||||
* ISO 11783-3:2018 5.4.5 Acknowledgement
|
||||
*/
|
||||
int sock_nack;
|
||||
struct sockaddr_can addr;
|
||||
|
||||
int server_version;
|
||||
|
||||
/* fs status related variables */
|
||||
struct isobusfs_cm_fss st; /* file server status message */
|
||||
enum isobusfs_srv_fss_state st_state;
|
||||
struct isobusfs_stats st_msg_stats;
|
||||
|
||||
/* client related variables */
|
||||
struct isobusfs_srv_client clients[ISOBUSFS_SRV_MAX_CLIENTS];
|
||||
int clients_count;
|
||||
struct isobusfs_buf_log tx_buf_log;
|
||||
|
||||
struct libj1939_cmn cmn;
|
||||
|
||||
struct isobusfs_srv_volume volumes[ISOBUSFS_SRV_MAX_VOLUMES];
|
||||
int volume_count;
|
||||
int removable_volumes_count;
|
||||
const char *default_volume;
|
||||
/* manufacturer-specific directory */
|
||||
char mfs_dir[9];
|
||||
uint64_t local_name;
|
||||
|
||||
struct isobusfs_srv_handles handles[ISOBUSFS_SRV_MAX_OPENED_HANDLES];
|
||||
int handles_count;
|
||||
};
|
||||
|
||||
/* isobusfs_srv.c */
|
||||
int isobusfs_srv_send_error(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg, enum isobusfs_error err);
|
||||
int isobusfs_srv_sendto(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg, const void *buf,
|
||||
size_t buf_size);
|
||||
|
||||
/* isobusfs_srv_cm_fss.c */
|
||||
void isobusfs_srv_fss_init(struct isobusfs_srv_priv *priv);
|
||||
int isobusfs_srv_fss_send(struct isobusfs_srv_priv *priv);
|
||||
|
||||
/* isobusfs_srv_cm.c */
|
||||
int isobusfs_srv_rx_cg_cm(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg);
|
||||
void isobusfs_srv_remove_timeouted_clients(struct isobusfs_srv_priv *priv);
|
||||
void isobusfs_srv_init_clients(struct isobusfs_srv_priv *priv);
|
||||
struct isobusfs_srv_client *isobusfs_srv_get_client(
|
||||
struct isobusfs_srv_priv *priv, uint8_t addr);
|
||||
struct isobusfs_srv_client *isobusfs_srv_get_client_by_msg(
|
||||
struct isobusfs_srv_priv *priv, struct isobusfs_msg *msg);
|
||||
|
||||
/* isobusfs_srv_dh.c */
|
||||
int isobusfs_srv_rx_cg_dh(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg);
|
||||
int isobusfs_path_to_linux_path(struct isobusfs_srv_priv *priv,
|
||||
const char *isobusfs_path, size_t isobusfs_path_size,
|
||||
char *linux_path, size_t linux_path_size);
|
||||
int isobusfs_check_current_dir_access(struct isobusfs_srv_priv *priv,
|
||||
const char *path, size_t path_size);
|
||||
int isobusfs_convert_relative_to_absolute(struct isobusfs_srv_priv *priv,
|
||||
const char *current_dir,
|
||||
const char *rel_path,
|
||||
size_t rel_path_size, char *abs_path,
|
||||
size_t abs_path_size);
|
||||
void isobusfs_srv_set_default_current_dir(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_srv_client *client);
|
||||
|
||||
|
||||
/* isobusfs_srv_vh.c */
|
||||
int isobusfs_srv_rx_cg_vh(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg);
|
||||
|
||||
/* isobusfs_srv_fh.c */
|
||||
int isobusfs_srv_rx_cg_fh(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg);
|
||||
|
||||
/* isobusfs_srv_fa.c */
|
||||
int isobusfs_srv_rx_cg_fa(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg);
|
||||
void isobusfs_srv_remove_client_from_handles(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_srv_client *client);
|
||||
|
||||
|
||||
#endif /* ISOBUSFS_SRV_H */
|
||||
654
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_srv_cm.c
Executable file
654
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_srv_cm.c
Executable file
@@ -0,0 +1,654 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
/*
|
||||
* ISOBUS File System Server Connection Management (isobusfs_srv_cm.c)
|
||||
*
|
||||
* This code implements the Connection Management functionality for the
|
||||
* ISOBUS File System Server, according to the ISO 11783-13:2021 standard,
|
||||
* specifically Section 5.10: Connection/Disconnection of a client.
|
||||
*
|
||||
* The ISOBUS File System Server provides a way for clients to interact with
|
||||
* files and directories on an ISOBUS network. Connection Management is
|
||||
* responsible for handling client connections and disconnections, ensuring
|
||||
* proper communication and resource allocation between the server and its
|
||||
* clients.
|
||||
*
|
||||
* This code includes functions for initializing clients, adding new clients,
|
||||
* managing client connections, and handling client disconnections. It also
|
||||
* provides utility functions for managing sockets and filters for the
|
||||
* communication between the server and its clients.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <linux/kernel.h>
|
||||
|
||||
#include "isobusfs_cmn.h"
|
||||
#include "isobusfs_srv.h"
|
||||
#include "isobusfs_cmn_cm.h"
|
||||
|
||||
/* Request volume by client */
|
||||
int isobusfs_srv_request_volume(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_srv_client *client,
|
||||
struct isobusfs_srv_volume *volume)
|
||||
{
|
||||
unsigned int j;
|
||||
|
||||
/* Check if the client already requested this volume */
|
||||
for (j = 0; j < ARRAY_SIZE(volume->clients); j++) {
|
||||
if (volume->clients[j] == client) {
|
||||
/* Client already requested this volume, do not
|
||||
* increase refcount
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add client to the volume's client list and increase refcount */
|
||||
for (j = 0; j < ARRAY_SIZE(volume->clients); j++) {
|
||||
if (volume->clients[j] == NULL) {
|
||||
volume->clients[j] = client;
|
||||
volume->refcount++;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
/* Release volume by client */
|
||||
int isobusfs_srv_release_volume(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_srv_client *client,
|
||||
const char *volume_name)
|
||||
{
|
||||
struct isobusfs_srv_volume *volume;
|
||||
int i;
|
||||
|
||||
/* Find the requested volume */
|
||||
for (i = 0; i < priv->volume_count; i++) {
|
||||
if (strcmp(priv->volumes[i].name, volume_name) == 0) {
|
||||
volume = &priv->volumes[i];
|
||||
|
||||
/* Find the client in the volume's client list and
|
||||
* decrease refcount
|
||||
*/
|
||||
for (int j = 0; j < ISOBUSFS_SRV_MAX_CLIENTS; j++) {
|
||||
if (volume->clients[j] == client) {
|
||||
volume->clients[j] = NULL;
|
||||
volume->refcount--;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static void
|
||||
isobusfs_srv_remove_client_from_volumes(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_srv_client *client)
|
||||
{
|
||||
for (int i = 0; i < priv->volume_count; i++) {
|
||||
struct isobusfs_srv_volume *volume = &priv->volumes[i];
|
||||
|
||||
for (int j = 0; j < ISOBUSFS_SRV_MAX_CLIENTS; j++) {
|
||||
if (volume->clients[j] == client) {
|
||||
volume->clients[j] = NULL;
|
||||
volume->refcount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_srv_init_clients - Initialize the list of clients for the server
|
||||
* @priv: Pointer to the isobusfs_srv_priv structure containing clients array
|
||||
*
|
||||
* This function initializes the clients array in the isobusfs_srv_priv
|
||||
* structure by setting the socket value to -1 for each client in the list.
|
||||
* This indicates that the clients are not currently connected.
|
||||
*/
|
||||
void isobusfs_srv_init_clients(struct isobusfs_srv_priv *priv)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ISOBUSFS_SRV_MAX_CLIENTS; i++)
|
||||
priv->clients[i].sock = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_srv_get_client - Find a client in the list of clients by address
|
||||
* @priv: Pointer to the isobusfs_srv_priv structure containing clients array
|
||||
* @addr: Address of the client to find
|
||||
*
|
||||
* This function searches for a client in the list of clients using the
|
||||
* provided address. If a client with a matching address is found, the
|
||||
* function returns a pointer to the corresponding isobusfs_srv_client
|
||||
* structure. If no matching client is found, the function returns NULL.
|
||||
*
|
||||
* Note: The function skips clients with negative socket values.
|
||||
*/
|
||||
static struct isobusfs_srv_client *isobusfs_srv_find_client(
|
||||
struct isobusfs_srv_priv *priv, uint8_t addr)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < priv->clients_count; i++) {
|
||||
struct isobusfs_srv_client *client = &priv->clients[i];
|
||||
|
||||
if (client->sock < 0)
|
||||
continue;
|
||||
|
||||
if (client->addr == addr)
|
||||
return &priv->clients[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_srv_remove_client - Remove a client from the list of clients
|
||||
* @priv: Pointer to the isobusfs_srv_priv structure containing clients array
|
||||
* @client: Pointer to the isobusfs_srv_client structure to be removed
|
||||
*
|
||||
* This function removes a client from the list of clients, adjusts the
|
||||
* clients array, and decrements the clients_count. The function also closes
|
||||
* the client's socket.
|
||||
*/
|
||||
static void isobusfs_srv_remove_client(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_srv_client *client)
|
||||
{
|
||||
int index = client - priv->clients;
|
||||
int i;
|
||||
|
||||
if (client->sock < 0)
|
||||
return;
|
||||
|
||||
close(client->sock);
|
||||
client->sock = -1;
|
||||
|
||||
isobusfs_srv_remove_client_from_handles(priv, client);
|
||||
isobusfs_srv_remove_client_from_volumes(priv, client);
|
||||
|
||||
/* Shift all elements after the removed client to the left by one
|
||||
* position
|
||||
*/
|
||||
for (i = index; i < priv->clients_count - 1; i++)
|
||||
priv->clients[i] = priv->clients[i + 1];
|
||||
|
||||
priv->clients_count--;
|
||||
|
||||
pr_debug("client 0x%02x removed", client->addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_srv_init_client - Initialize a client's socket and connection
|
||||
* @priv: pointer to the server's private data structure
|
||||
* @client: pointer to the client's data structure
|
||||
*
|
||||
* This function initializes a client's socket, binds it to the server's address,
|
||||
* sets the socket options, and connects the socket to the destination address.
|
||||
* If any step fails, it will log a warning message, close the socket if needed,
|
||||
* and return an error code.
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure
|
||||
*/
|
||||
static int isobusfs_srv_init_client(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_srv_client *client)
|
||||
{
|
||||
struct sockaddr_can addr = priv->addr;
|
||||
struct j1939_filter filt = {
|
||||
.addr = J1939_NO_ADDR,
|
||||
.addr_mask = J1939_NO_ADDR, /* mask is 0xff */
|
||||
};
|
||||
int ret;
|
||||
|
||||
if (client->sock >= 0) {
|
||||
pr_warn("client %d already initialized", client->addr);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = libj1939_open_socket();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
client->sock = ret;
|
||||
|
||||
addr.can_addr.j1939.pgn = ISOBUSFS_PGN_CL_TO_FS;
|
||||
ret = libj1939_bind_socket(client->sock, &addr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = isobusfs_cmn_set_linger(client->sock);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* use positive filter to not no allow any unicast messages. At same
|
||||
* time do not allow any broadcast messages. So, this will be transmit
|
||||
* only socket. This is needed to not let the J1939 kernel stack to
|
||||
* ACK ETP/TP transfers on the bus and provide false information to
|
||||
* the client about received data.
|
||||
*/
|
||||
ret = setsockopt(client->sock, SOL_CAN_J1939, SO_J1939_FILTER, &filt,
|
||||
sizeof(filt));
|
||||
if (ret < 0) {
|
||||
ret = -errno;
|
||||
pr_warn("can't set socket filter for client 0x%02x. Error: %i (%s)",
|
||||
client->addr, ret, strerror(ret));
|
||||
goto close_socket;
|
||||
}
|
||||
|
||||
ret = libj1939_socket_prio(client->sock, ISOBUSFS_PRIO_DEFAULT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
addr.can_addr.j1939.name = J1939_NO_NAME;
|
||||
addr.can_addr.j1939.addr = client->addr;
|
||||
addr.can_addr.j1939.pgn = ISOBUSFS_PGN_FS_TO_CL;
|
||||
ret = isobusfs_cmn_connect_socket(client->sock, &addr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
|
||||
close_socket:
|
||||
close(client->sock);
|
||||
client->sock = -1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_srv_add_client - Add a new client to the server's client list
|
||||
* @priv: pointer to the server's private data structure
|
||||
* @addr: address of the new client
|
||||
*
|
||||
* This function adds a new client to the server's client list if the list
|
||||
* hasn't reached its maximum capacity (ISOBUSFS_SRV_MAX_CLIENTS).
|
||||
* If the maximum number of clients is reached, a warning message will be
|
||||
* logged, and the function returns NULL.
|
||||
*
|
||||
* Return: pointer to the new client on success, NULL on failure
|
||||
*/
|
||||
static struct isobusfs_srv_client *isobusfs_srv_add_client(
|
||||
struct isobusfs_srv_priv *priv, uint8_t addr)
|
||||
{
|
||||
struct isobusfs_srv_client *client;
|
||||
int ret;
|
||||
|
||||
if (priv->clients_count >= ISOBUSFS_SRV_MAX_CLIENTS) {
|
||||
pr_warn("too many clients");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
client = &priv->clients[priv->clients_count];
|
||||
client->addr = addr;
|
||||
|
||||
ret = isobusfs_srv_init_client(priv, client);
|
||||
if (ret < 0)
|
||||
return NULL;
|
||||
|
||||
priv->clients_count++;
|
||||
pr_debug("client 0x%02x added", client->addr);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
struct isobusfs_srv_client *isobusfs_srv_get_client(
|
||||
struct isobusfs_srv_priv *priv, uint8_t addr)
|
||||
{
|
||||
struct isobusfs_srv_client *client;
|
||||
|
||||
/* Get the client with the specified address */
|
||||
client = isobusfs_srv_find_client(priv, addr);
|
||||
/* If client is not found, create a new one */
|
||||
if (!client) {
|
||||
client = isobusfs_srv_add_client(priv, addr);
|
||||
if (!client) {
|
||||
pr_warn("can't add client");
|
||||
/* Keep running. Nothing we can do here */
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the client's last_received timestamp */
|
||||
client->last_received = priv->cmn.last_time;
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
struct isobusfs_srv_client *isobusfs_srv_get_client_by_msg(
|
||||
struct isobusfs_srv_priv *priv, struct isobusfs_msg *msg)
|
||||
{
|
||||
uint8_t addr = msg->peername.can_addr.j1939.addr;
|
||||
struct isobusfs_srv_client *client;
|
||||
|
||||
client = isobusfs_srv_get_client(priv, addr);
|
||||
if (!client) {
|
||||
pr_warn("%s: client not found", __func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_srv_remove_timeouted_clients - Remove clients that have timed out
|
||||
* @priv: pointer to the server's private data structure
|
||||
*
|
||||
* This function checks each client in the server's client list to determine
|
||||
* if the client has timed out. If a client has timed out, it is removed
|
||||
* from the list.
|
||||
*/
|
||||
void isobusfs_srv_remove_timeouted_clients(struct isobusfs_srv_priv *priv)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < priv->clients_count; i++) {
|
||||
struct isobusfs_srv_client *client = &priv->clients[i];
|
||||
int64_t time_diff;
|
||||
|
||||
if (client->sock < 0)
|
||||
continue;
|
||||
|
||||
time_diff = timespec_diff_ms(&priv->cmn.last_time,
|
||||
&client->last_received);
|
||||
|
||||
if (time_diff > ISOBUSFS_CLIENT_TIMEOUT) {
|
||||
isobusfs_srv_remove_client(priv, client);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_srv_property_res - Send a Get File Server Properties Response
|
||||
* @priv: pointer to the server's private data structure
|
||||
* @msg: pointer to the received message that requires a response
|
||||
*
|
||||
* This function sends a response to a client's Get File Server Properties
|
||||
* request according to ISO 11783-13:2021, Annex C.1.5. The response contains
|
||||
* information about the server's capabilities, version, and maximum number
|
||||
* of simultaneously open files.
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure
|
||||
*/
|
||||
static int isobusfs_srv_property_res(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg)
|
||||
{
|
||||
struct isobusfs_cm_get_fs_props_resp resp;
|
||||
int ret;
|
||||
|
||||
resp.fs_function =
|
||||
isobusfs_cg_function_to_buf(ISOBUSFS_CG_CONNECTION_MANAGMENT,
|
||||
ISOBUSFS_CM_GET_FS_PROPERTIES_RES);
|
||||
/* Version number:
|
||||
* 0 - Draft
|
||||
* 1 - Final draft
|
||||
* 2 - First published version
|
||||
*/
|
||||
resp.version_number = priv->server_version;
|
||||
/* Maximum Number of Simultaneously Open Files */
|
||||
resp.max_open_files = ISOBUSFS_MAX_OPENED_FILES;
|
||||
/* File Server Capabilities */
|
||||
resp.fs_capabilities = 0;
|
||||
|
||||
/* not used space should be filled with 0xff */
|
||||
memset(&resp.reserved[0], 0xff, sizeof(resp.reserved));
|
||||
|
||||
ret = isobusfs_srv_sendto(priv, msg, &resp, sizeof(resp));
|
||||
if (ret < 0) {
|
||||
pr_warn("can't send property response");
|
||||
return ret;
|
||||
}
|
||||
|
||||
pr_debug("> tx property response");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_srv_handle_ccm - Handle a Connection Control Maintenance message
|
||||
* @priv: pointer to the server's private data structure
|
||||
* @msg: pointer to the received message that requires a response
|
||||
*
|
||||
* This function handles a Connection Control Maintenance (CCM) message
|
||||
* according to ISO 11783-13:2021, Annex C.1.3. The CCM is used to establish a
|
||||
* connection between a client and a server. If the client is not found in the
|
||||
* server's client list, it is added to the list. If the client is found, the
|
||||
* client's socket is reinitialized.
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure
|
||||
*/
|
||||
static int isobusfs_srv_handle_ccm(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg)
|
||||
{
|
||||
struct isobusfs_cm_ccm *ccm = (struct isobusfs_cm_ccm *)msg->buf;
|
||||
|
||||
pr_debug("< rx ccm version: %d", ccm->version);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isobusfs_extract_volume_name(const char *src, size_t src_size,
|
||||
char *dst, size_t dst_size)
|
||||
{
|
||||
size_t i = 0, j = 0;
|
||||
size_t n;
|
||||
|
||||
if (src == NULL || dst == NULL || dst_size == 0 || src_size == 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (!(src[0] == '\\' && src[1] == '\\' && src[2] != '\0'))
|
||||
return -EINVAL;
|
||||
|
||||
n = min((size_t)3, min(src_size, dst_size - 1));
|
||||
memcpy(dst, src, n);
|
||||
|
||||
while (i < src_size && src[i] != '\0' && src[i] != '\\' &&
|
||||
j < dst_size - 1) {
|
||||
dst[j++] = src[i++];
|
||||
}
|
||||
dst[j] = '\0';
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isobusfs_srv_process_volume_status_request(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg, struct isobusfs_cm_vol_stat_res *resp)
|
||||
{
|
||||
struct isobusfs_cm_vol_stat_req *req =
|
||||
(struct isobusfs_cm_vol_stat_req *)msg->buf;
|
||||
char isobusfs_volume_path[ISOBUSFS_MAX_VOLUME_NAME_LENGTH];
|
||||
size_t path_len, req_name_len, resp_name_len;
|
||||
char linux_path[ISOBUSFS_SRV_MAX_PATH_LEN];
|
||||
struct isobusfs_srv_volume *volume = NULL;
|
||||
struct isobusfs_srv_client *client;
|
||||
const char *path;
|
||||
int ret, i;
|
||||
|
||||
req_name_len = le16toh(req->name_len);
|
||||
|
||||
pr_debug("< rx volume status request. mode: %x, length: %d, name: %s",
|
||||
req->volume_mode, req_name_len, req->name);
|
||||
|
||||
client = isobusfs_srv_get_client_by_msg(priv, msg);
|
||||
if (!client) {
|
||||
pr_warn("can't find client");
|
||||
return ISOBUSFS_ERR_OTHER;
|
||||
}
|
||||
|
||||
if (req_name_len == 0) {
|
||||
path = client->current_dir;
|
||||
path_len = sizeof(client->current_dir);
|
||||
} else {
|
||||
path = req->name;
|
||||
path_len = req_name_len;
|
||||
}
|
||||
|
||||
ret = isobusfs_extract_volume_name(path, path_len, isobusfs_volume_path,
|
||||
sizeof(isobusfs_volume_path));
|
||||
if (ret < 0) {
|
||||
pr_warn("can't extract volume name");
|
||||
return ISOBUSFS_ERR_OTHER;
|
||||
}
|
||||
|
||||
resp_name_len = strlen(isobusfs_volume_path);
|
||||
resp->name_len = htole16(resp_name_len);
|
||||
/* the isobusfs_volume_path is already null terminated
|
||||
* by isobusfs_extract_volume_name()
|
||||
*/
|
||||
memcpy(resp->name, isobusfs_volume_path, resp_name_len + 1);
|
||||
|
||||
ret = isobusfs_path_to_linux_path(priv, isobusfs_volume_path,
|
||||
sizeof(isobusfs_volume_path),
|
||||
linux_path, sizeof(linux_path));
|
||||
if (ret < 0) {
|
||||
pr_warn("can't convert %s path to linux path", isobusfs_volume_path);
|
||||
return ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
|
||||
}
|
||||
|
||||
ret = isobusfs_cmn_dh_validate_dir_path(linux_path, false);
|
||||
if (ret < 0)
|
||||
return ISOBUSFS_ERR_INVALID_ACCESS;
|
||||
|
||||
/* TODO: we already searched for volume in isobusfs_path_to_linux_path()
|
||||
* function. We should use the result of that search instead of searching
|
||||
* again.
|
||||
*/
|
||||
for (i = 0; i < priv->volume_count; i++) {
|
||||
if (strncmp(priv->volumes[i].name, isobusfs_volume_path,
|
||||
sizeof(isobusfs_volume_path)) == 0) {
|
||||
volume = &priv->volumes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!volume)
|
||||
return ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
|
||||
|
||||
if (req->volume_mode & ISOBUSFS_VOL_MODE_PREP_TO_REMOVE) {
|
||||
if (!volume->removable ||
|
||||
(req_name_len == 0 &&
|
||||
0 /* Current directory is not set condition */)) {
|
||||
/* Volume is not removable, or the Path Name Length of
|
||||
* request is zero and the current directory is not set
|
||||
*/
|
||||
return ISOBUSFS_ERR_INVALID_ACCESS;
|
||||
}
|
||||
/* TODO: Check if the volume is in use
|
||||
* TODO: Check if the volume is the current directory
|
||||
* TODO: add hot removal support.
|
||||
*/
|
||||
resp->volume_status = ISOBUSFS_VOL_STATUS_PREP_TO_REMOVE;
|
||||
} else if (req->volume_mode & ISOBUSFS_VOL_MODE_USED_BY_CLIENT) {
|
||||
ret = isobusfs_srv_request_volume(priv, client,
|
||||
volume);
|
||||
if (ret < 0)
|
||||
return ISOBUSFS_ERR_INVALID_ACCESS;
|
||||
}
|
||||
|
||||
if (volume->refcount > 0)
|
||||
resp->volume_status = ISOBUSFS_VOL_STATUS_IN_USE;
|
||||
else
|
||||
resp->volume_status = ISOBUSFS_VOL_STATUS_PRESENT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isobusfs_srv_volume_status_resp(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg)
|
||||
{
|
||||
struct isobusfs_cm_vol_stat_res resp = {0};
|
||||
size_t buf_size;
|
||||
int ret;
|
||||
|
||||
resp.fs_function =
|
||||
isobusfs_cg_function_to_buf(ISOBUSFS_CG_CONNECTION_MANAGMENT,
|
||||
ISOBUSFS_CM_VOLUME_STATUS_RES);
|
||||
|
||||
ret = isobusfs_srv_process_volume_status_request(priv, msg, &resp);
|
||||
resp.error_code = ret;
|
||||
|
||||
buf_size = sizeof(resp) - sizeof(resp.name) + le16toh(resp.name_len);
|
||||
if (buf_size < ISOBUSFS_MIN_TRANSFER_LENGH) {
|
||||
/* Fill the rest of the buffer with 0xFF. We need to fill
|
||||
* only buffers under 8 bytes. Padding for ETP/TP is done
|
||||
* by the kernel.
|
||||
*/
|
||||
memset(((uint8_t *) &resp) + buf_size, 0xFF,
|
||||
ISOBUSFS_MIN_TRANSFER_LENGH - buf_size);
|
||||
buf_size = ISOBUSFS_MIN_TRANSFER_LENGH;
|
||||
} else if (buf_size > ISOBUSFS_MAX_TRANSFER_LENGH) {
|
||||
pr_warn("volume status response too long");
|
||||
resp.error_code = ISOBUSFS_ERR_OUT_OF_MEM;
|
||||
buf_size = ISOBUSFS_MAX_TRANSFER_LENGH;
|
||||
}
|
||||
|
||||
ret = isobusfs_srv_sendto(priv, msg, &resp, buf_size);
|
||||
if (ret < 0) {
|
||||
pr_warn("can't send volume status response");
|
||||
return ret;
|
||||
}
|
||||
|
||||
pr_debug("> tx volume status response. status: %d, error code: %d, name len: %d, name: %s",
|
||||
resp.volume_status, resp.error_code, resp.name_len, resp.name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_srv_rx_cg_cm - Handle received connection management commands
|
||||
* @priv: pointer to the server's private data structure
|
||||
* @msg: pointer to the received message that requires processing
|
||||
*
|
||||
* This function handles the received connection management commands according
|
||||
* to the specific function code provided in the message. It then delegates
|
||||
* the processing to the corresponding handler for each supported function.
|
||||
* Unsupported functions will result in a warning message and an error response.
|
||||
*
|
||||
* Return: 0 on success or when an unsupported function is encountered,
|
||||
* negative error code on failure
|
||||
*/
|
||||
int isobusfs_srv_rx_cg_cm(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg)
|
||||
{
|
||||
enum isobusfs_cm_cl_to_fs_function func =
|
||||
isobusfs_buf_to_function(msg->buf);
|
||||
int ret = 0;
|
||||
|
||||
/* Process the received function code and delegate to appropriate
|
||||
* handlers
|
||||
*/
|
||||
switch (func) {
|
||||
case ISOBUSFS_CM_F_CC_MAINTENANCE:
|
||||
ret = isobusfs_srv_handle_ccm(priv, msg);
|
||||
break;
|
||||
case ISOBUSFS_CM_GET_FS_PROPERTIES:
|
||||
ret = isobusfs_srv_property_res(priv, msg);
|
||||
break;
|
||||
case ISOBUSFS_CM_VOLUME_STATUS_REQ:
|
||||
if (priv->server_version < 3)
|
||||
goto not_supported;
|
||||
|
||||
ret = isobusfs_srv_volume_status_resp(priv, msg);
|
||||
break;
|
||||
default:
|
||||
goto not_supported;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
not_supported:
|
||||
/* Handle unsupported functions */
|
||||
isobusfs_srv_send_error(priv, msg, ISOBUSFS_ERR_FUNC_NOT_SUPPORTED);
|
||||
|
||||
pr_warn("%s: unsupported function: %i", __func__, func);
|
||||
|
||||
/* Not a critical error */
|
||||
return 0;
|
||||
}
|
||||
126
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_srv_cm_fss.c
Executable file
126
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_srv_cm_fss.c
Executable file
@@ -0,0 +1,126 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
/*
|
||||
* This file implements Annex C.1.2 File Server Status according to
|
||||
* ISO 11783-13:2021.
|
||||
*/
|
||||
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <poll.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "isobusfs_srv.h"
|
||||
|
||||
/*
|
||||
* isobusfs_srv_fss_init - Initialize the file server status structure
|
||||
* @priv: Pointer to the private data structure of the ISOBUS file server
|
||||
*
|
||||
* This function initializes the file server status structure, which
|
||||
* represents the status of the file server according to Annex C.1.2
|
||||
* of ISO 11783-13:2021.
|
||||
*/
|
||||
void isobusfs_srv_fss_init(struct isobusfs_srv_priv *priv)
|
||||
{
|
||||
struct isobusfs_cm_fss *st = &priv->st;
|
||||
|
||||
st->fs_function =
|
||||
isobusfs_cg_function_to_buf(ISOBUSFS_CG_CONNECTION_MANAGMENT,
|
||||
ISOBUSFS_CM_F_FS_STATUS);
|
||||
st->status = 0;
|
||||
st->num_open_files = 0;
|
||||
memset(st->reserved, 0xFF, sizeof(st->reserved));
|
||||
}
|
||||
|
||||
/*
|
||||
* isobusfs_srv_fss_get_rate - Get the rate of File Server Status transmission
|
||||
* @priv: Pointer to the private data structure of the ISOBUS file server
|
||||
*
|
||||
* Returns: the transmission rate of the File Server Status messages depending
|
||||
* on the current state of the file server.
|
||||
*/
|
||||
static unsigned int isobusfs_srv_fss_get_rate(struct isobusfs_srv_priv *priv)
|
||||
{
|
||||
switch (priv->st_state) {
|
||||
case ISOBUSFS_SRV_STATE_IDLE:
|
||||
return ISOBUSFS_CM_F_FS_STATUS_IDLE_RATE;
|
||||
/*
|
||||
* On every change of Byte 2 "File Server Status" send max 5 status
|
||||
* messages per second.
|
||||
*/
|
||||
case ISOBUSFS_SRV_STATE_STAT_CHANGE_1: /* fall through */
|
||||
case ISOBUSFS_SRV_STATE_STAT_CHANGE_2: /* fall through */
|
||||
case ISOBUSFS_SRV_STATE_STAT_CHANGE_3: /* fall through */
|
||||
case ISOBUSFS_SRV_STATE_STAT_CHANGE_4: /* fall through */
|
||||
case ISOBUSFS_SRV_STATE_STAT_CHANGE_5:
|
||||
priv->st_state--;
|
||||
return ISOBUSFS_CM_F_FS_STATUS_BUSY_RATE;
|
||||
case ISOBUSFS_SRV_STATE_BUSY:
|
||||
return ISOBUSFS_CM_F_FS_STATUS_BUSY_RATE;
|
||||
default:
|
||||
pr_warn("%s:%i: unknown state %d", __func__, __LINE__,
|
||||
priv->st_state);
|
||||
}
|
||||
|
||||
/*
|
||||
* In case something is wrong, fall back to idle rate to not spam the
|
||||
* bus.
|
||||
*/
|
||||
return ISOBUSFS_CM_F_FS_STATUS_IDLE_RATE;
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_srv_fss_send - Send periodic File Server Status messages
|
||||
* @priv: Pointer to the private data structure of the ISOBUS file server
|
||||
*
|
||||
* Returns: 0 if the message was sent successfully, a negative error code
|
||||
* otherwise.
|
||||
*/
|
||||
int isobusfs_srv_fss_send(struct isobusfs_srv_priv *priv)
|
||||
{
|
||||
unsigned int next_msg_rate;
|
||||
int64_t time_diff;
|
||||
int ret;
|
||||
|
||||
/* Test if it is proper time to send next status message. */
|
||||
time_diff = timespec_diff_ms(&priv->cmn.next_send_time,
|
||||
&priv->cmn.last_time);
|
||||
if (time_diff > ISOBUSFS_CM_F_FS_STATUS_RATE_JITTER) {
|
||||
/* too early to send next message */
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (time_diff < -ISOBUSFS_CM_F_FS_STATUS_RATE_JITTER) {
|
||||
pr_warn("too late to send next fs status message: %ld ms",
|
||||
time_diff);
|
||||
}
|
||||
|
||||
/* Make sure we send the message with the latest stats */
|
||||
if (priv->st_msg_stats.tskey_sch != priv->st_msg_stats.tskey_ack)
|
||||
pr_warn("previous message was not acked");
|
||||
|
||||
/* send periodic file servers status messages. */
|
||||
ret = send(priv->sock_fss, &priv->st, sizeof(priv->st), MSG_DONTWAIT);
|
||||
if (ret < 0) {
|
||||
ret = -errno;
|
||||
pr_warn("Failed to send FS status message, error code: %d (%s)",
|
||||
ret, strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
pr_debug("> tx FS status: 0x%02x, opened files: %d",
|
||||
priv->st.status, priv->st.num_open_files);
|
||||
|
||||
/* Calculate time for the next status message */
|
||||
next_msg_rate = isobusfs_srv_fss_get_rate(priv);
|
||||
priv->cmn.next_send_time = priv->cmn.last_time;
|
||||
timespec_add_ms(&priv->cmn.next_send_time, next_msg_rate);
|
||||
|
||||
return 0;
|
||||
}
|
||||
758
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_srv_dh.c
Executable file
758
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_srv_dh.c
Executable file
@@ -0,0 +1,758 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "isobusfs_srv.h"
|
||||
#include "isobusfs_cmn_dh.h"
|
||||
|
||||
void isobusfs_srv_set_default_current_dir(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_srv_client *client)
|
||||
{
|
||||
snprintf(client->current_dir, ISOBUSFS_SRV_MAX_PATH_LEN, "\\\\%s",
|
||||
priv->default_volume);
|
||||
}
|
||||
|
||||
static const char *isobusfs_srv_get_volume_end(const char *path, size_t path_size)
|
||||
{
|
||||
const char *vol_end = NULL;
|
||||
size_t i;
|
||||
|
||||
if (!path || !path_size)
|
||||
return NULL;
|
||||
|
||||
if (!(path[0] == '\\' && path[1] == '\\' && path[2] != '\0'))
|
||||
return NULL;
|
||||
|
||||
for (i = 2; i < path_size; i++) {
|
||||
if (path[i] == '\\' || path[i] == '\0') {
|
||||
vol_end = &path[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!vol_end)
|
||||
vol_end = &path[i];
|
||||
|
||||
return vol_end;
|
||||
}
|
||||
|
||||
int isobusfs_path_to_linux_path(struct isobusfs_srv_priv *priv,
|
||||
const char *isobusfs_path, size_t isobusfs_path_size,
|
||||
char *linux_path, size_t linux_path_size)
|
||||
{
|
||||
struct isobusfs_srv_volume *volume = NULL;
|
||||
size_t isobusfs_path_pos = 0;
|
||||
const char *vol_end;
|
||||
char *ptr;
|
||||
int i;
|
||||
|
||||
if (!priv || !isobusfs_path || !linux_path || !linux_path_size ||
|
||||
!isobusfs_path_size) {
|
||||
pr_err("%s: invalid argument\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
vol_end = isobusfs_srv_get_volume_end(isobusfs_path, isobusfs_path_size);
|
||||
if (!vol_end) {
|
||||
pr_err("%s: invalid path %s. Can't find end of volume string\n",
|
||||
__func__, isobusfs_path);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Search for the volume in the priv->volumes array */
|
||||
for (i = 0; i < priv->volume_count; i++) {
|
||||
size_t volume_name_len = vol_end - (isobusfs_path + 2);
|
||||
|
||||
if (volume_name_len == strlen(priv->volumes[i].name) &&
|
||||
memcmp(priv->volumes[i].name, isobusfs_path + 2,
|
||||
volume_name_len) == 0) {
|
||||
volume = &priv->volumes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!volume) {
|
||||
pr_err("%s: invalid path %s. Can't find volume\n",
|
||||
__func__, isobusfs_path);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Copy the volume's Linux path to the output buffer */
|
||||
strncpy(linux_path, volume->path, linux_path_size - 1);
|
||||
linux_path[linux_path_size - 1] = '\0';
|
||||
|
||||
isobusfs_path_pos = vol_end - isobusfs_path;
|
||||
/* Add a forward slash if path ends after volume name */
|
||||
if (*vol_end == '\0' || isobusfs_path_pos == isobusfs_path_size - 1)
|
||||
strncat(linux_path, "/",
|
||||
linux_path_size - strlen(linux_path) - 1);
|
||||
|
||||
|
||||
if (isobusfs_path_pos + 3 < isobusfs_path_size && strncmp(vol_end, "\\~\\", 3) == 0) {
|
||||
strncat(linux_path, "/", linux_path_size - strlen(linux_path) - 1);
|
||||
/* convert tilde to manufacturer-specific directory */
|
||||
strncat(linux_path, priv->mfs_dir,
|
||||
linux_path_size - strlen(linux_path) - 1);
|
||||
vol_end += 2;
|
||||
}
|
||||
|
||||
/* Replace backslashes with forward slashes for the rest of the path */
|
||||
ptr = linux_path + strlen(linux_path);
|
||||
while (vol_end < isobusfs_path + isobusfs_path_size && *vol_end) {
|
||||
if (*vol_end == '\\')
|
||||
*ptr = '/';
|
||||
else
|
||||
*ptr = *vol_end;
|
||||
|
||||
ptr++;
|
||||
vol_end++;
|
||||
if (ptr - linux_path >= (long int)linux_path_size) {
|
||||
/* Ensure null termination */
|
||||
linux_path[linux_path_size - 1] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int isobusfs_check_current_dir_access(struct isobusfs_srv_priv *priv,
|
||||
const char *path, size_t path_size)
|
||||
{
|
||||
char linux_path[ISOBUSFS_SRV_MAX_PATH_LEN];
|
||||
int ret;
|
||||
|
||||
ret = isobusfs_path_to_linux_path(priv, path, path_size,
|
||||
linux_path, sizeof(linux_path));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
pr_debug("convert ISOBUS FS path to linux path: %.*s -> %s",
|
||||
path_size, path, linux_path);
|
||||
|
||||
ret = isobusfs_cmn_dh_validate_dir_path(linux_path, false);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* current directory response function */
|
||||
static int isobusfs_srv_dh_current_dir_res(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg)
|
||||
{
|
||||
struct isobusfs_dh_get_cd_req *req =
|
||||
(struct isobusfs_dh_get_cd_req *)msg->buf;
|
||||
uint8_t error_code = ISOBUSFS_ERR_SUCCESS;
|
||||
struct isobusfs_dh_get_cd_res *res;
|
||||
struct isobusfs_srv_client *client;
|
||||
size_t str_len, buf_size;
|
||||
size_t fixed_res_size;
|
||||
size_t padding_size = 0;
|
||||
int ret;
|
||||
|
||||
client = isobusfs_srv_get_client_by_msg(priv, msg);
|
||||
if (!client) {
|
||||
pr_warn("client not found");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
if (client->current_dir[0] == '\0')
|
||||
isobusfs_srv_set_default_current_dir(priv, client);
|
||||
|
||||
ret = isobusfs_check_current_dir_access(priv, client->current_dir,
|
||||
sizeof(client->current_dir));
|
||||
if (ret < 0) {
|
||||
switch (ret) {
|
||||
case -ENOENT:
|
||||
error_code = ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
|
||||
case -ENOMEDIUM:
|
||||
error_code = ISOBUSFS_ERR_VOLUME_NOT_INITIALIZED;
|
||||
case -ENOMEM:
|
||||
error_code = ISOBUSFS_ERR_OUT_OF_MEM;
|
||||
default:
|
||||
error_code = ISOBUSFS_ERR_OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
fixed_res_size = sizeof(*res);
|
||||
str_len = strlen(client->current_dir) + 1;
|
||||
buf_size = fixed_res_size + str_len;
|
||||
|
||||
if (buf_size > ISOBUSFS_MAX_TRANSFER_LENGH) {
|
||||
pr_warn("current directory response too long");
|
||||
|
||||
/* Calculate the maximum allowed string length based on the
|
||||
* buffer size
|
||||
*/
|
||||
str_len = ISOBUSFS_MAX_TRANSFER_LENGH - fixed_res_size;
|
||||
|
||||
/* Update the buffer size accordingly */
|
||||
buf_size = fixed_res_size + str_len;
|
||||
|
||||
error_code = ISOBUSFS_ERR_OUT_OF_MEM;
|
||||
|
||||
} else if (buf_size < ISOBUSFS_MIN_TRANSFER_LENGH) {
|
||||
/* Update the buffer size accordingly */
|
||||
padding_size = ISOBUSFS_MIN_TRANSFER_LENGH - buf_size;
|
||||
buf_size = ISOBUSFS_MIN_TRANSFER_LENGH;
|
||||
}
|
||||
|
||||
res = malloc(buf_size);
|
||||
if (!res) {
|
||||
pr_err("failed to allocate memory for current directory response");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
res->fs_function =
|
||||
isobusfs_cg_function_to_buf(ISOBUSFS_CG_DIRECTORY_HANDLING,
|
||||
ISOBUSFS_DH_F_GET_CURRENT_DIR_RES);
|
||||
res->tan = req->tan;
|
||||
res->error_code = error_code;
|
||||
/* TODO: implement total_space and free_space */
|
||||
res->total_space = htole16(0);
|
||||
res->free_space = htole16(0);
|
||||
res->name_len = htole16(str_len);
|
||||
memcpy(res->name, client->current_dir, str_len);
|
||||
|
||||
if (padding_size) {
|
||||
/* Fill the rest of the res structure with 0xff */
|
||||
memset(((uint8_t *)res) + buf_size - padding_size, 0xff,
|
||||
padding_size);
|
||||
}
|
||||
|
||||
/* send to socket */
|
||||
ret = isobusfs_srv_sendto(priv, msg, res, buf_size);
|
||||
if (ret < 0) {
|
||||
pr_warn("can't send current directory response");
|
||||
goto free_res;
|
||||
}
|
||||
|
||||
pr_debug("> tx: current directory response: %s, total space: %i, free space: %i",
|
||||
client->current_dir, le16toh(res->total_space),
|
||||
le16toh(res->free_space));
|
||||
|
||||
free_res:
|
||||
free(res);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_is_forbidden_char() - check if the given character is forbidden
|
||||
* @ch: character to check
|
||||
*
|
||||
* Return: true if the character is forbidden, false otherwise
|
||||
*
|
||||
* The function checks if the given character is forbidden in the ISOBUS FS
|
||||
* as defined in ISO 11783-13:2021, section A.2.2.1 Names:
|
||||
* To avoid incompatibility between different operating systems, the client
|
||||
* shall not create folder/files with names, which only differs in case, and
|
||||
* names shall not end with a '.' or include ‘<’, ‘>’, ‘|’ (the latter three
|
||||
* may cause issues on FAT32).
|
||||
* ....
|
||||
* LongNameChar ::= any single character defined by Unicode/ISO/IEC 10646,
|
||||
* except 0x00 to 0x1f, 0x7f to 0x9f, ‘\’, ‘*’, ‘?’, ‘/’.
|
||||
*/
|
||||
static bool isobusfs_is_forbidden_char(wchar_t ch)
|
||||
{
|
||||
if (ch >= 0x00 && ch <= 0x1f)
|
||||
return true;
|
||||
if (ch >= 0x7f && ch <= 0x9f)
|
||||
return true;
|
||||
if (ch == L'*' || ch == L'?' || ch == L'/' ||
|
||||
ch == L'<' || ch == L'>' || ch == L'|')
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static int isobusfs_validate_path_chars(const char *path, size_t size)
|
||||
{
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
wchar_t ch = path[i];
|
||||
|
||||
if (isobusfs_is_forbidden_char(ch))
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isobusfs_handle_path_prefix(const char *current_dir,
|
||||
size_t current_dir_len,
|
||||
const char *rel_path,
|
||||
size_t rel_path_size,
|
||||
size_t *rel_path_pos, char *abs_path,
|
||||
size_t abs_path_size,
|
||||
size_t *abs_path_pos)
|
||||
{
|
||||
if (strncmp(rel_path, "~\\", 2) == 0) {
|
||||
size_t vol_len;
|
||||
const char *vol_end;
|
||||
|
||||
vol_end = isobusfs_srv_get_volume_end(current_dir,
|
||||
current_dir_len);
|
||||
if (!vol_end)
|
||||
return -EINVAL;
|
||||
|
||||
vol_len = vol_end - current_dir;
|
||||
strncpy(abs_path, current_dir, vol_len);
|
||||
abs_path[vol_len] = '\\';
|
||||
*abs_path_pos = vol_len + 1;
|
||||
} else if (strncmp(rel_path, "\\\\", 2) == 0) {
|
||||
/* Too many back slashes, drop it. */
|
||||
if (rel_path[2] == '\\')
|
||||
return -EINVAL;
|
||||
|
||||
strncpy(abs_path, rel_path, 2);
|
||||
*abs_path_pos = 2;
|
||||
*rel_path_pos = 2;
|
||||
} else {
|
||||
strncpy(abs_path, current_dir, abs_path_size);
|
||||
*abs_path_pos = current_dir_len;
|
||||
if (abs_path[*abs_path_pos - 1] != '\\') {
|
||||
if (*abs_path_pos < abs_path_size - 1) {
|
||||
abs_path[*abs_path_pos] = '\\';
|
||||
*abs_path_pos += 1;
|
||||
} else {
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
if (rel_path[*rel_path_pos] == '\\')
|
||||
*rel_path_pos += 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* is_valid_path_char - Check if the current character is valid in the path
|
||||
* @rel_path: The relative path being processed
|
||||
* @rel_path_size: The size of the relative path
|
||||
* @rel_path_pos: Pointer to the current position in the relative path
|
||||
*
|
||||
* Checks if the current character at the position in the relative path
|
||||
* is not the end of the string, not a null character, and not a backslash.
|
||||
*
|
||||
* Return: True if the current character is valid, False otherwise.
|
||||
*/
|
||||
static bool is_valid_path_char(const char *rel_path, size_t rel_path_size,
|
||||
const size_t *rel_path_pos)
|
||||
{
|
||||
return *rel_path_pos < rel_path_size &&
|
||||
rel_path[*rel_path_pos] != '\0' &&
|
||||
rel_path[*rel_path_pos] != '\\';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified number of positions ahead in the relative path
|
||||
* are either the end of the buffer or a backslash.
|
||||
*
|
||||
* @param rel_path The relative path being processed.
|
||||
* @param rel_path_size The size of the relative path.
|
||||
* @param rel_path_pos The current position in the relative path.
|
||||
* @param look_ahead The number of positions ahead to check.
|
||||
* @return True if the specified positions ahead are the end or a backslash,
|
||||
* False otherwise.
|
||||
*/
|
||||
static bool is_end_or_backslash(const char *rel_path, size_t rel_path_size,
|
||||
const size_t *rel_path_pos, size_t look_ahead)
|
||||
{
|
||||
if (*rel_path_pos + look_ahead >= rel_path_size)
|
||||
return true; /* End of buffer */
|
||||
|
||||
return rel_path[*rel_path_pos + look_ahead] == '\\';
|
||||
}
|
||||
|
||||
/**
|
||||
* is_path_separator - Check if the current character is a path separator
|
||||
* @rel_path: The relative path being processed
|
||||
* @rel_path_size: The size of the relative path
|
||||
* @rel_path_pos: Pointer to the current position in the relative path
|
||||
*
|
||||
* Checks if the current character at the position in the relative path
|
||||
* is a backslash and the position is within the string size.
|
||||
*
|
||||
* Return: True if the current character is a backslash, False otherwise.
|
||||
*/
|
||||
static bool is_path_separator(const char *rel_path, size_t rel_path_size,
|
||||
const size_t *rel_path_pos)
|
||||
{
|
||||
return *rel_path_pos < rel_path_size && rel_path[*rel_path_pos] == '\\';
|
||||
}
|
||||
|
||||
static bool isobusfs_is_dot_directive(const char *rel_path,
|
||||
size_t rel_path_size,
|
||||
const size_t *rel_path_pos)
|
||||
{
|
||||
if (rel_path[*rel_path_pos] == '.') {
|
||||
/* Check for '.' followed by a backslash or at the end of the
|
||||
* string
|
||||
*/
|
||||
if (is_end_or_backslash(rel_path, rel_path_size,
|
||||
rel_path_pos, 1) ||
|
||||
rel_path[*rel_path_pos + 1] == '\0') {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Check for '..' followed by a backslash or at the end of the
|
||||
* string
|
||||
*/
|
||||
if (rel_path[*rel_path_pos + 1] == '.') {
|
||||
if (is_end_or_backslash(rel_path, rel_path_size,
|
||||
rel_path_pos, 2) ||
|
||||
rel_path[*rel_path_pos + 2] == '\0') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_handle_single_dot - Processes a single dot directive in a relative
|
||||
* path
|
||||
* @rel_path: The relative path being processed
|
||||
* @rel_path_size: The size of the relative path
|
||||
* @rel_path_pos: Pointer to the current position in the relative path
|
||||
*
|
||||
* This function checks if the current segment in the relative path is a single
|
||||
* dot ('.'). A single dot represents the current directory. If the next
|
||||
* character after the dot is either a backslash or the end of the string,
|
||||
* the function advances the path position appropriately. The function returns
|
||||
* true if it processes a single dot, indicating that the current directory
|
||||
* directive was found and handled.
|
||||
*
|
||||
* Return: True if a single dot directive is detected, False otherwise.
|
||||
*/
|
||||
static bool isobusfs_handle_single_dot(const char *rel_path,
|
||||
size_t rel_path_size,
|
||||
size_t *rel_path_pos)
|
||||
{
|
||||
bool is_dot = false;
|
||||
|
||||
if (is_end_or_backslash(rel_path, rel_path_size, rel_path_pos, 1)) {
|
||||
*rel_path_pos += 2;
|
||||
is_dot = true;
|
||||
} else if (rel_path[*rel_path_pos + 1] == '\0') {
|
||||
*rel_path_pos += 1;
|
||||
is_dot = true;
|
||||
}
|
||||
|
||||
return is_dot;
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_handle_double_dots - Processes a double dot directive in a relative
|
||||
* path
|
||||
*
|
||||
* @rel_path: The relative path being processed
|
||||
* @rel_path_size: The size of the relative path
|
||||
* @rel_path_pos: Pointer to the current position in the relative path
|
||||
* @abs_path: Buffer to store the absolute path being constructed
|
||||
* @abs_path_pos: Pointer to the current position in the absolute path buffer
|
||||
*
|
||||
* This function processes the double dot directive ('..') in a relative path.
|
||||
* The double dot represents the parent directory. If the double dot directive
|
||||
* is followed by a backslash or is at the end of the string, the function
|
||||
* advances the path position accordingly. Additionally, it adjusts the
|
||||
* absolute path position to move up one directory in the path hierarchy. The
|
||||
* function ensures that it does not go beyond the root of the absolute path
|
||||
* while moving up the directory hierarchy.
|
||||
*/
|
||||
static void isobusfs_handle_double_dots(const char *rel_path,
|
||||
size_t rel_path_size,
|
||||
size_t *rel_path_pos, char *abs_path,
|
||||
size_t *abs_path_pos)
|
||||
{
|
||||
/* Move the relative path position forward after handling '..' */
|
||||
if (is_end_or_backslash(rel_path, rel_path_size, rel_path_pos, 2))
|
||||
*rel_path_pos += 3;
|
||||
else if (rel_path[*rel_path_pos + 2] == '\0')
|
||||
*rel_path_pos += 2;
|
||||
|
||||
/* Move the absolute path position backward to simulate moving up a
|
||||
* directory
|
||||
*/
|
||||
if (*abs_path_pos > 2 && abs_path[*abs_path_pos - 1] == '\\')
|
||||
*abs_path_pos -= 1;
|
||||
|
||||
while (*abs_path_pos > 2 && abs_path[*abs_path_pos - 1] != '\\')
|
||||
*abs_path_pos -= 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_handle_dot_directive - Processes '.' and '..' directives in a path
|
||||
* @rel_path: The relative path being processed
|
||||
* @rel_path_size: The size of the relative path
|
||||
* @rel_path_pos: Pointer to the current position in the relative path
|
||||
* @abs_path: Buffer to store the absolute path being constructed
|
||||
* @abs_path_pos: Pointer to the current position in the absolute path buffer
|
||||
*
|
||||
* This function processes the dot directives found in a relative path. It
|
||||
* handles both single dot ('.') and double dot ('..') directives. A single dot
|
||||
* represents the current directory, while a double dot represents moving up to
|
||||
* the parent directory.
|
||||
*/
|
||||
static void isobusfs_handle_dot_directive(const char *rel_path,
|
||||
size_t rel_path_size,
|
||||
size_t *rel_path_pos, char *abs_path,
|
||||
size_t *abs_path_pos)
|
||||
{
|
||||
if (rel_path[*rel_path_pos] == '.') {
|
||||
bool is_dot = isobusfs_handle_single_dot(rel_path, rel_path_size,
|
||||
rel_path_pos);
|
||||
|
||||
if (!is_dot && rel_path[*rel_path_pos + 1] == '.') {
|
||||
isobusfs_handle_double_dots(rel_path, rel_path_size,
|
||||
rel_path_pos, abs_path,
|
||||
abs_path_pos);
|
||||
}
|
||||
}
|
||||
|
||||
/* Skip additional backslashes after '.' or '..' */
|
||||
while (is_path_separator(rel_path, rel_path_size, rel_path_pos))
|
||||
*rel_path_pos += 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* isobusfs_process_path_segment - Processes normal path segments
|
||||
* @rel_path: The relative path being processed
|
||||
* @rel_path_size: The size of the relative path
|
||||
* @rel_path_pos: Pointer to the current position in the relative path
|
||||
* @abs_path: The buffer to store the absolute path
|
||||
* @abs_path_size: The size of the absolute path buffer
|
||||
* @abs_path_pos: Pointer to the position in the absolute path buffer
|
||||
*
|
||||
* This function processes normal segments of a relative path, copying them
|
||||
* into the absolute path buffer. It handles each character until it encounters
|
||||
* a path separator or reaches the end of the relative path. If a path separator
|
||||
* is found, it adds a single backslash to the absolute path. The function
|
||||
* ensures that the buffer limits are respected to prevent buffer overflows.
|
||||
*
|
||||
* Return: 0 on successful processing of the segment, -ENOMEM if the absolute
|
||||
* path buffer runs out of space.
|
||||
*/
|
||||
static int isobusfs_process_path_segment(const char *rel_path,
|
||||
size_t rel_path_size,
|
||||
size_t *rel_path_pos, char *abs_path,
|
||||
size_t abs_path_size,
|
||||
size_t *abs_path_pos)
|
||||
{
|
||||
/* Process the current character from the relative path */
|
||||
abs_path[*abs_path_pos] = rel_path[*rel_path_pos];
|
||||
*abs_path_pos += 1;
|
||||
*rel_path_pos += 1;
|
||||
|
||||
/* Continue processing until a path separator or end of the string is
|
||||
* reached
|
||||
*/
|
||||
while (is_valid_path_char(rel_path, rel_path_size, rel_path_pos)) {
|
||||
if (*abs_path_pos >= abs_path_size - 1)
|
||||
return -ENOMEM;
|
||||
|
||||
abs_path[*abs_path_pos] = rel_path[*rel_path_pos];
|
||||
*rel_path_pos += 1;
|
||||
*abs_path_pos += 1;
|
||||
}
|
||||
/* Add a single backslash if next character is a backslash */
|
||||
if (is_path_separator(rel_path, rel_path_size, rel_path_pos)) {
|
||||
*rel_path_pos += 1;
|
||||
if (*abs_path_pos < abs_path_size - 1) {
|
||||
abs_path[*abs_path_pos] = '\\';
|
||||
*abs_path_pos += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isobusfs_handle_relative_path(const char *rel_path,
|
||||
size_t rel_path_size,
|
||||
size_t *rel_path_pos, char *abs_path,
|
||||
size_t abs_path_size,
|
||||
size_t *abs_path_pos)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (*abs_path_pos >= abs_path_size - 1)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Check for '.' or '..' followed by a backslash or at the end of the
|
||||
* string
|
||||
*/
|
||||
if (isobusfs_is_dot_directive(rel_path, rel_path_size, rel_path_pos)) {
|
||||
isobusfs_handle_dot_directive(rel_path, rel_path_size,
|
||||
rel_path_pos, abs_path,
|
||||
abs_path_pos);
|
||||
} else {
|
||||
/* Process normally for filenames or directories */
|
||||
ret = isobusfs_process_path_segment(rel_path, rel_path_size,
|
||||
rel_path_pos, abs_path,
|
||||
abs_path_size,
|
||||
abs_path_pos);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int isobusfs_convert_relative_to_absolute(struct isobusfs_srv_priv *priv,
|
||||
const char *current_dir,
|
||||
const char *rel_path,
|
||||
size_t rel_path_size, char *abs_path,
|
||||
size_t abs_path_size)
|
||||
{
|
||||
size_t abs_path_pos = 0;
|
||||
size_t rel_path_pos = 0;
|
||||
size_t current_dir_len;
|
||||
int ret;
|
||||
|
||||
if (!current_dir || !rel_path || !abs_path || !rel_path_size ||
|
||||
!abs_path_size)
|
||||
return -EINVAL;
|
||||
|
||||
ret = isobusfs_validate_path_chars(rel_path, rel_path_size);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
current_dir_len = strlen(current_dir);
|
||||
if (current_dir_len >= abs_path_size)
|
||||
return -ENOMEM;
|
||||
if (current_dir_len == 0)
|
||||
return -EINVAL;
|
||||
|
||||
ret = isobusfs_handle_path_prefix(current_dir, current_dir_len,
|
||||
rel_path, rel_path_size,
|
||||
&rel_path_pos, abs_path,
|
||||
abs_path_size, &abs_path_pos);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
while (rel_path_pos < rel_path_size && rel_path[rel_path_pos] != '\0') {
|
||||
ret = isobusfs_handle_relative_path(rel_path, rel_path_size,
|
||||
&rel_path_pos, abs_path,
|
||||
abs_path_size,
|
||||
&abs_path_pos);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
abs_path[abs_path_pos] = '\0';
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* change current directory response function */
|
||||
static int isobusfs_srv_dh_ccd_res(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg)
|
||||
{
|
||||
struct isobusfs_dh_ccd_req *req =
|
||||
(struct isobusfs_dh_ccd_req *)msg->buf;
|
||||
uint8_t error_code = ISOBUSFS_ERR_SUCCESS;
|
||||
struct isobusfs_srv_client *client;
|
||||
struct isobusfs_dh_ccd_res res;
|
||||
size_t abs_path_len;
|
||||
char *abs_path;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* We assime, the relative path stored in res->name is not longer
|
||||
* than absolue path
|
||||
*/
|
||||
if (req->name_len > ISOBUSFS_SRV_MAX_PATH_LEN) {
|
||||
pr_warn("path too long");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
client = isobusfs_srv_get_client_by_msg(priv, msg);
|
||||
if (!client) {
|
||||
pr_warn("client not found");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
abs_path_len = ISOBUSFS_SRV_MAX_PATH_LEN;
|
||||
abs_path = malloc(abs_path_len);
|
||||
if (!abs_path) {
|
||||
pr_warn("failed to allocate memory");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
pr_debug("< rx change current directory request from client 0x%2x: %.*s. Current directory: %s",
|
||||
client->addr, req->name_len, req->name, client->current_dir);
|
||||
/* Normalize provided string and convert it to absolute ISOBUS FS path */
|
||||
ret = isobusfs_convert_relative_to_absolute(priv, client->current_dir,
|
||||
(char *)req->name, req->name_len,
|
||||
abs_path, abs_path_len);
|
||||
if (ret < 0)
|
||||
goto process_error;
|
||||
|
||||
pr_debug("converted relative to absolute ISOBUS FS internal path: %s", abs_path);
|
||||
ret = isobusfs_check_current_dir_access(priv, abs_path, abs_path_len);
|
||||
process_error:
|
||||
if (ret < 0) {
|
||||
/* linux_error_to_isobusfs_error() can't distinguish between
|
||||
* -EINVAL vor SRC and DST, so we have to do it manually.
|
||||
*/
|
||||
if (ret == -EINVAL)
|
||||
error_code = ISOBUSFS_ERR_INVALID_DST_NAME;
|
||||
else
|
||||
error_code = linux_error_to_isobusfs_error(ret);
|
||||
} else {
|
||||
/* change current directory */
|
||||
strncpy(client->current_dir, abs_path, ISOBUSFS_SRV_MAX_PATH_LEN);
|
||||
}
|
||||
|
||||
res.fs_function =
|
||||
isobusfs_cg_function_to_buf(ISOBUSFS_CG_DIRECTORY_HANDLING,
|
||||
ISOBUSFS_DH_F_CHANGE_CURRENT_DIR_RES);
|
||||
res.tan = req->tan;
|
||||
res.error_code = error_code;
|
||||
memset(&res.reserved[0], 0xff, sizeof(res.reserved));
|
||||
|
||||
/* send to socket */
|
||||
ret = isobusfs_srv_sendto(priv, msg, &res, sizeof(res));
|
||||
if (ret < 0) {
|
||||
pr_warn("can't send current directory response");
|
||||
goto free_abs_path;
|
||||
}
|
||||
|
||||
pr_debug("> tx: ccd response. Error code: %d", error_code);
|
||||
free_abs_path:
|
||||
free(abs_path);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* current directory response function */
|
||||
/* Command group: directory handling */
|
||||
int isobusfs_srv_rx_cg_dh(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg)
|
||||
{
|
||||
int func = isobusfs_buf_to_function(msg->buf);
|
||||
int ret = 0;
|
||||
|
||||
switch (func) {
|
||||
case ISOBUSFS_DH_F_GET_CURRENT_DIR_REQ:
|
||||
return isobusfs_srv_dh_current_dir_res(priv, msg);
|
||||
case ISOBUSFS_DH_F_CHANGE_CURRENT_DIR_REQ:
|
||||
return isobusfs_srv_dh_ccd_res(priv, msg);
|
||||
default:
|
||||
goto not_supported;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
not_supported:
|
||||
isobusfs_srv_send_error(priv, msg, ISOBUSFS_ERR_FUNC_NOT_SUPPORTED);
|
||||
|
||||
pr_warn("%s: unsupported function: %i", __func__, func);
|
||||
|
||||
/* Not a critical error */
|
||||
return 0;
|
||||
}
|
||||
915
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_srv_fa.c
Executable file
915
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_srv_fa.c
Executable file
@@ -0,0 +1,915 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "isobusfs_srv.h"
|
||||
#include "isobusfs_cmn_fa.h"
|
||||
|
||||
static struct isobusfs_srv_handles *
|
||||
isobusfs_srv_walk_handles(struct isobusfs_srv_priv *priv, const char *path)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(priv->handles); i++) {
|
||||
if (priv->handles[i].path == NULL)
|
||||
continue;
|
||||
|
||||
if (!strcmp(priv->handles[i].path, path))
|
||||
return &priv->handles[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int isobusfs_srv_add_file(struct isobusfs_srv_priv *priv,
|
||||
const char *path, int fd, DIR *dir)
|
||||
{
|
||||
unsigned int j;
|
||||
|
||||
if (priv->handles_count >= (int)ARRAY_SIZE(priv->handles)) {
|
||||
pr_err("too many handles");
|
||||
return -ENOSPC;
|
||||
}
|
||||
|
||||
for (j = 0; j < ARRAY_SIZE(priv->handles); j++) {
|
||||
if (priv->handles[j].path == NULL)
|
||||
break;
|
||||
}
|
||||
|
||||
priv->handles[j].path = strdup(path);
|
||||
priv->handles[j].fd = fd;
|
||||
priv->handles[j].dir = dir;
|
||||
|
||||
priv->handles_count++;
|
||||
return j;
|
||||
}
|
||||
|
||||
static int isobusfs_srv_add_client_to_file(struct isobusfs_srv_handles *file,
|
||||
struct isobusfs_srv_client *client)
|
||||
{
|
||||
unsigned int j;
|
||||
|
||||
for (j = 0; j < ARRAY_SIZE(file->clients); j++) {
|
||||
if (file->clients[j] == client)
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (j = 0; j < ARRAY_SIZE(file->clients); j++) {
|
||||
if (file->clients[j] == NULL) {
|
||||
file->clients[j] = client;
|
||||
file->refcount++;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
pr_err("%s: can't add client to file", __func__);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static int isobusfs_srv_request_file(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_srv_client *client,
|
||||
const char *path, int fd, DIR *dir)
|
||||
{
|
||||
struct isobusfs_srv_handles *file;
|
||||
int handle, ret;
|
||||
|
||||
file = isobusfs_srv_walk_handles(priv, path);
|
||||
if (!file) {
|
||||
handle = isobusfs_srv_add_file(priv, path, fd, dir);
|
||||
if (handle < 0)
|
||||
return handle;
|
||||
|
||||
file = &priv->handles[handle];
|
||||
} else {
|
||||
handle = file - priv->handles;
|
||||
}
|
||||
|
||||
ret = isobusfs_srv_add_client_to_file(file, client);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
static struct isobusfs_srv_handles *
|
||||
isobusfs_srv_get_handle(struct isobusfs_srv_priv *priv, int handle)
|
||||
{
|
||||
if (handle < 0 || handle >= (int)ARRAY_SIZE(priv->handles))
|
||||
return NULL;
|
||||
|
||||
return &priv->handles[handle];
|
||||
}
|
||||
|
||||
static int isobusfs_srv_release_handle(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_srv_client *client,
|
||||
int handle)
|
||||
{
|
||||
struct isobusfs_srv_handles *hdl = isobusfs_srv_get_handle(priv, handle);
|
||||
unsigned int client_index;
|
||||
|
||||
if (!hdl) {
|
||||
pr_warn("%s: invalid handle %d", __func__, handle);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
/* Find the client in the hdl's client list and remove it */
|
||||
for (client_index = 0; client_index < ARRAY_SIZE(hdl->clients); client_index++) {
|
||||
if (hdl->clients[client_index] == client) {
|
||||
hdl->clients[client_index] = NULL;
|
||||
hdl->refcount--;
|
||||
|
||||
pr_debug("%s: client %p removed from handle %d", __func__, client, handle);
|
||||
/* If refcount is 0, close the hdl and remove it from the list */
|
||||
if (hdl->refcount == 0) {
|
||||
pr_debug("%s: closing handle %d", __func__, handle);
|
||||
/* fd will be automatically closed when
|
||||
* closedir(3) is called.
|
||||
*/
|
||||
if (hdl->dir)
|
||||
closedir(hdl->dir);
|
||||
else
|
||||
close(hdl->fd);
|
||||
memset(hdl, 0, sizeof(*hdl));
|
||||
priv->handles_count--;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
pr_err("%s: client %p not found in handle %d", __func__, client, handle);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
void isobusfs_srv_remove_client_from_handles(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_srv_client *client)
|
||||
{
|
||||
unsigned int handle;
|
||||
unsigned int client_index;
|
||||
|
||||
for (handle = 0; handle < ARRAY_SIZE(priv->handles); handle++) {
|
||||
struct isobusfs_srv_handles *hdl = &priv->handles[handle];
|
||||
|
||||
if (hdl->path == NULL)
|
||||
continue;
|
||||
|
||||
for (client_index = 0; client_index < ARRAY_SIZE(hdl->clients); client_index++) {
|
||||
if (hdl->clients[client_index] == client) {
|
||||
hdl->clients[client_index] = NULL;
|
||||
hdl->refcount--;
|
||||
|
||||
if (hdl->refcount == 0) {
|
||||
close(hdl->fd);
|
||||
memset(hdl, 0, sizeof(*hdl));
|
||||
priv->handles_count--;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int isobusfs_srv_fa_open_directory(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_srv_client *client,
|
||||
const char *path, size_t path_len,
|
||||
uint8_t *handle)
|
||||
{
|
||||
char linux_path[ISOBUSFS_SRV_MAX_PATH_LEN];
|
||||
struct isobusfs_srv_handles *hdl;
|
||||
struct stat file_stat;
|
||||
int file_index, fd;
|
||||
DIR *dir;
|
||||
int ret;
|
||||
|
||||
ret = isobusfs_path_to_linux_path(priv, path, path_len, linux_path, sizeof(linux_path));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
hdl = isobusfs_srv_walk_handles(priv, linux_path);
|
||||
if (hdl) {
|
||||
pr_err("%s: Path %s is already opened\n", __func__, linux_path);
|
||||
return ISOBUSFS_ERR_OTHER;
|
||||
}
|
||||
|
||||
dir = opendir(linux_path);
|
||||
if (!dir) {
|
||||
pr_err("%s: Error opening directory %s. Error %d (%s)\n",
|
||||
__func__, linux_path, errno, strerror(errno));
|
||||
switch (errno) {
|
||||
case EACCES:
|
||||
return ISOBUSFS_ERR_ACCESS_DENIED;
|
||||
case ENOENT:
|
||||
return ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
|
||||
case ENOMEM:
|
||||
return ISOBUSFS_ERR_OUT_OF_MEM;
|
||||
default:
|
||||
return ISOBUSFS_ERR_OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
fd = dirfd(dir);
|
||||
if (fd < 0) {
|
||||
pr_err("%s: Error getting file descriptor for directory %s. Error %d (%s)\n",
|
||||
__func__, linux_path, errno, strerror(errno));
|
||||
closedir(dir);
|
||||
return ISOBUSFS_ERR_OTHER;
|
||||
}
|
||||
|
||||
if (fstat(fd, &file_stat) < 0 || !S_ISDIR(file_stat.st_mode)) {
|
||||
pr_err("%s: Path %s is not a directory\n", __func__,
|
||||
linux_path);
|
||||
closedir(dir);
|
||||
return ISOBUSFS_ERR_INVALID_ACCESS;
|
||||
}
|
||||
|
||||
file_index = isobusfs_srv_request_file(priv, client, linux_path, fd,
|
||||
dir);
|
||||
if (file_index < 0) {
|
||||
closedir(dir);
|
||||
return file_index;
|
||||
}
|
||||
|
||||
*handle = (uint8_t)file_index;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isobusfs_srv_fa_open_file(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_srv_client *client,
|
||||
const char *path, size_t path_len,
|
||||
uint8_t flags, uint8_t *handle)
|
||||
{
|
||||
char linux_path[ISOBUSFS_SRV_MAX_PATH_LEN];
|
||||
struct isobusfs_srv_handles *hdl;
|
||||
struct stat file_stat;
|
||||
int open_flags = 0;
|
||||
int file_index;
|
||||
int ret, fd;
|
||||
|
||||
ret = isobusfs_path_to_linux_path(priv, path, path_len,
|
||||
linux_path, sizeof(linux_path));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
pr_debug("convert ISOBUS FS path to linux path: %.*s -> %s",
|
||||
path_len, path, linux_path);
|
||||
|
||||
/* Determine open flags based on the requested access type */
|
||||
switch (flags & ISOBUSFS_FA_OPEN_MASK) {
|
||||
case ISOBUSFS_FA_OPEN_FILE_RO:
|
||||
open_flags |= O_RDONLY;
|
||||
break;
|
||||
case ISOBUSFS_FA_OPEN_FILE_WO:
|
||||
open_flags |= O_WRONLY;
|
||||
break;
|
||||
case ISOBUSFS_FA_OPEN_FILE_WR:
|
||||
open_flags |= O_RDWR;
|
||||
if (!(flags & ISOBUSFS_FA_OPEN_APPEND))
|
||||
open_flags |= O_TRUNC;
|
||||
break;
|
||||
default:
|
||||
return ISOBUSFS_ERR_INVALID_ACCESS;
|
||||
}
|
||||
|
||||
if (flags & ISOBUSFS_FA_OPEN_APPEND)
|
||||
open_flags |= O_APPEND;
|
||||
|
||||
/* Check if the file is already opened */
|
||||
hdl = isobusfs_srv_walk_handles(priv, linux_path);
|
||||
if (hdl) {
|
||||
pr_warn("Handle: %s is already opened by client: %x\n",
|
||||
linux_path, client->addr);
|
||||
fd = hdl->fd;
|
||||
} else {
|
||||
/* Open the file if not already opened */
|
||||
fd = open(linux_path, open_flags);
|
||||
if (fd < 0) {
|
||||
switch (errno) {
|
||||
case EACCES:
|
||||
return ISOBUSFS_ERR_ACCESS_DENIED;
|
||||
case EINVAL:
|
||||
return ISOBUSFS_ERR_INVALID_ACCESS;
|
||||
case EMFILE:
|
||||
case ENFILE:
|
||||
return ISOBUSFS_ERR_TOO_MANY_FILES_OPEN;
|
||||
case ENOENT:
|
||||
return ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
|
||||
case ENOMEM:
|
||||
return ISOBUSFS_ERR_OUT_OF_MEM;
|
||||
default:
|
||||
return ISOBUSFS_ERR_OTHER;
|
||||
}
|
||||
}
|
||||
/* Check if the opened path is a regular file */
|
||||
if (fstat(fd, &file_stat) < 0) {
|
||||
close(fd);
|
||||
return ISOBUSFS_ERR_OTHER;
|
||||
}
|
||||
|
||||
if (!S_ISREG(file_stat.st_mode)) {
|
||||
close(fd);
|
||||
/* Invalid access (not a regular file) */
|
||||
return ISOBUSFS_ERR_INVALID_ACCESS;
|
||||
}
|
||||
}
|
||||
|
||||
/* Request the file, which also handles refcount and client list
|
||||
* updates
|
||||
*/
|
||||
file_index = isobusfs_srv_request_file(priv, client, linux_path, fd,
|
||||
NULL);
|
||||
if (file_index < 0) {
|
||||
close(fd);
|
||||
return file_index;
|
||||
}
|
||||
|
||||
*handle = (uint8_t)file_index;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isobusfs_srv_fa_open_file_req(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg)
|
||||
{
|
||||
struct isobusfs_fa_openf_req *req =
|
||||
(struct isobusfs_fa_openf_req *)msg->buf;
|
||||
uint16_t name_len = le16toh(req->name_len);
|
||||
struct isobusfs_srv_client *client;
|
||||
struct isobusfs_fa_openf_res res;
|
||||
uint8_t error_code = 0;
|
||||
uint8_t access_type;
|
||||
size_t abs_path_len;
|
||||
uint8_t handle = 0;
|
||||
char *abs_path;
|
||||
int ret = 0;
|
||||
|
||||
client = isobusfs_srv_get_client_by_msg(priv, msg);
|
||||
if (!client) {
|
||||
pr_warn("client not found");
|
||||
error_code = ISOBUSFS_ERR_OTHER;
|
||||
goto send_response;
|
||||
}
|
||||
|
||||
if (name_len > msg->len - sizeof(*req)) {
|
||||
error_code = ISOBUSFS_ERR_INVALID_ACCESS;
|
||||
goto send_response;
|
||||
}
|
||||
|
||||
/* Perform checks on the received request, e.g., validate path length */
|
||||
if (name_len > ISOBUSFS_MAX_PATH_NAME_LENGTH) {
|
||||
error_code = ISOBUSFS_ERR_INVALID_ACCESS;
|
||||
goto send_response;
|
||||
}
|
||||
|
||||
abs_path_len = ISOBUSFS_SRV_MAX_PATH_LEN;
|
||||
abs_path = malloc(abs_path_len);
|
||||
if (!abs_path) {
|
||||
pr_warn("failed to allocate memory");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (client->current_dir[0] == '\0')
|
||||
isobusfs_srv_set_default_current_dir(priv, client);
|
||||
|
||||
/* Normalize provided string and convert it to absolute ISOBUS FS path */
|
||||
ret = isobusfs_convert_relative_to_absolute(priv, client->current_dir,
|
||||
(char *)req->name, req->name_len,
|
||||
abs_path, abs_path_len);
|
||||
if (ret < 0) {
|
||||
error_code = ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
|
||||
goto send_response;
|
||||
}
|
||||
|
||||
pr_debug("< rx: Open File Request. from client 0x%2x: %.*s. Current directory: %s",
|
||||
client->addr, req->name_len, req->name, client->current_dir);
|
||||
|
||||
access_type = FIELD_GET(ISOBUSFS_FA_OPEN_MASK, req->flags);
|
||||
if (access_type == ISOBUSFS_FA_OPEN_DIR) {
|
||||
error_code = isobusfs_srv_fa_open_directory(priv, client, abs_path,
|
||||
abs_path_len, &handle);
|
||||
} else {
|
||||
error_code = isobusfs_srv_fa_open_file(priv, client, abs_path,
|
||||
abs_path_len, req->flags,
|
||||
&handle);
|
||||
}
|
||||
|
||||
send_response:
|
||||
res.fs_function =
|
||||
isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
|
||||
ISOBUSFS_FA_F_OPEN_FILE_RES);
|
||||
res.tan = req->tan;
|
||||
res.error_code = error_code;
|
||||
res.handle = handle;
|
||||
memset(&res.reserved[0], 0xff, sizeof(res.reserved));
|
||||
|
||||
/* send to socket */
|
||||
ret = isobusfs_srv_sendto(priv, msg, &res, sizeof(res));
|
||||
if (ret < 0) {
|
||||
pr_warn("can't send current directory response");
|
||||
goto err;
|
||||
}
|
||||
|
||||
pr_debug("> tx: Open File Response. Error code: %d (%s).", error_code,
|
||||
isobusfs_error_to_str(error_code));
|
||||
err:
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int isobusfs_srv_read_file(struct isobusfs_srv_handles *handle,
|
||||
uint8_t *buffer, size_t count,
|
||||
ssize_t *readed_size)
|
||||
{
|
||||
*readed_size = read(handle->fd, buffer, count);
|
||||
if (*readed_size < 0) {
|
||||
switch (errno) {
|
||||
case EBADF:
|
||||
return ISOBUSFS_ERR_INVALID_HANDLE;
|
||||
case EFAULT:
|
||||
return ISOBUSFS_ERR_OUT_OF_MEM;
|
||||
case EIO:
|
||||
return ISOBUSFS_ERR_ON_READ;
|
||||
default:
|
||||
return ISOBUSFS_ERR_OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint16_t convert_to_file_date(time_t time_val)
|
||||
{
|
||||
struct tm *timeinfo = localtime(&time_val);
|
||||
int year, month, day;
|
||||
|
||||
if (!timeinfo)
|
||||
return 0;
|
||||
|
||||
year = timeinfo->tm_year + 1900 - 1980;
|
||||
month = timeinfo->tm_mon + 1;
|
||||
day = timeinfo->tm_mday;
|
||||
|
||||
if (year < 0 || year > 127)
|
||||
return 0;
|
||||
|
||||
return (year << 9) | (month << 5) | day;
|
||||
}
|
||||
|
||||
static uint16_t convert_to_file_time(time_t time_val)
|
||||
{
|
||||
struct tm *timeinfo = localtime(&time_val);
|
||||
int hours, minutes, seconds;
|
||||
uint16_t time;
|
||||
|
||||
if (!timeinfo)
|
||||
return 0;
|
||||
|
||||
hours = timeinfo->tm_hour;
|
||||
minutes = timeinfo->tm_min;
|
||||
seconds = timeinfo->tm_sec / 2;
|
||||
|
||||
time = (hours << 11) | (minutes << 5) | seconds;
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
static int check_access_with_base(const char *base_dir,
|
||||
const char *relative_path, int mode)
|
||||
{
|
||||
char full_path[ISOBUSFS_SRV_MAX_PATH_LEN];
|
||||
|
||||
if (snprintf(full_path, sizeof(full_path), "%s/%s", base_dir,
|
||||
relative_path) >= (int)sizeof(full_path)) {
|
||||
return -ENAMETOOLONG;
|
||||
}
|
||||
|
||||
return access(full_path, mode);
|
||||
}
|
||||
|
||||
static int isobusfs_srv_read_directory(struct isobusfs_srv_handles *handle,
|
||||
uint8_t *buffer, size_t count,
|
||||
ssize_t *readed_size)
|
||||
{
|
||||
DIR *dir = handle->dir;
|
||||
struct dirent *entry;
|
||||
size_t pos = 0;
|
||||
|
||||
/*
|
||||
* Position the directory stream to the previously stored offset (handle->dir_pos).
|
||||
*
|
||||
* Handling Changes in Directory Contents:
|
||||
* - If the directory contents change between reads (e.g., files/directories added or deleted),
|
||||
* handle->dir_pos may not point to the expected entry.
|
||||
* - To ensure consistency, implement checks (e.g., compare inode numbers) to verify the correct
|
||||
* entry position.
|
||||
*
|
||||
* Detecting End of Directory:
|
||||
* - If readdir() returns NULL before reaching handle->dir_pos, it indicates the end of the
|
||||
* directory with no more entries to read.
|
||||
* - This situation should be handled appropriately, such as by resetting handle->dir_pos and
|
||||
* either returning an error or restarting from the beginning of the directory, depending
|
||||
* on the application's requirements.
|
||||
*/
|
||||
for (int i = 0; i < handle->dir_pos &&
|
||||
(entry = readdir(dir)) != NULL; i++) {
|
||||
/* Iterating to the desired position */
|
||||
}
|
||||
|
||||
/*
|
||||
* Directory Entry Layout:
|
||||
* This loop reads directory entries and encodes them into a buffer.
|
||||
* Each entry in the buffer follows the format specified in ISO 11783-13:2021.
|
||||
*
|
||||
* The layout of each directory entry in the buffer is as follows:
|
||||
* - Byte 1: Filename Length (as per ISO 11783-13:2021 B.22).
|
||||
* Represents the length of the filename that follows.
|
||||
*
|
||||
* - Byte 2–n: Filename (as per ISO 11783-13:2021 B.23).
|
||||
* The actual name of the file or directory.
|
||||
*
|
||||
* - Byte n + 1: Attributes (as per ISO 11783-13:2021 B.15).
|
||||
*
|
||||
* - Bytes n + 2, n + 3: File Date (as per ISO 11783-13:2021 B.24).
|
||||
* Encoded file date, using a 16-bit format derived from file_stat.st_mtime.
|
||||
*
|
||||
* - Bytes n + 4, n + 5: File Time (as per ISO 11783-13:2021 B.25).
|
||||
* Encoded file time, using a 16-bit format derived from file_stat.st_mtime.
|
||||
*
|
||||
* - Bytes n + 6 … n + 9: Size (as per ISO 11783-13:2021 B.26).
|
||||
* The size of the file in bytes, encoded in a 32-bit little-endian format.
|
||||
*
|
||||
* The handle->dir_pos is incremented after processing each entry, marking
|
||||
* the current position in the directory stream for subsequent reads.
|
||||
*/
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
size_t entry_name_len, entry_total_len;
|
||||
__le16 file_date, file_time;
|
||||
uint8_t attributes = 0;
|
||||
struct stat file_stat;
|
||||
__le32 size;
|
||||
|
||||
if (check_access_with_base(handle->path, entry->d_name, R_OK) != 0)
|
||||
continue; /* Skip this entry if it's not readable */
|
||||
|
||||
if (fstatat(handle->fd, entry->d_name, &file_stat, 0) < 0)
|
||||
continue; /* Skip this entry on error */
|
||||
|
||||
entry_name_len = strlen(entry->d_name);
|
||||
if (entry_name_len > ISOBUSFS_MAX_DIR_ENTRY_NAME_LENGTH)
|
||||
continue;
|
||||
|
||||
entry_total_len = 1 + entry_name_len + 1 + 2 + 2 + 4;
|
||||
|
||||
if (pos + entry_total_len > count)
|
||||
break;
|
||||
|
||||
buffer[pos++] = (uint8_t)entry_name_len;
|
||||
|
||||
memcpy(buffer + pos, entry->d_name, entry_name_len);
|
||||
pos += entry_name_len;
|
||||
|
||||
if (S_ISDIR(file_stat.st_mode))
|
||||
attributes |= ISOBUSFS_ATTR_DIRECTORY;
|
||||
if (check_access_with_base(handle->path, entry->d_name, W_OK) != 0)
|
||||
attributes |= ISOBUSFS_ATTR_READ_ONLY;
|
||||
buffer[pos++] = attributes;
|
||||
|
||||
file_date = htole16(convert_to_file_date(file_stat.st_mtime));
|
||||
memcpy(buffer + pos, &file_date, sizeof(file_date));
|
||||
pos += sizeof(file_date);
|
||||
|
||||
file_time = htole16(convert_to_file_time(file_stat.st_mtime));
|
||||
memcpy(buffer + pos, &file_time, sizeof(file_time));
|
||||
pos += sizeof(file_time);
|
||||
|
||||
size = htole32(file_stat.st_size);
|
||||
memcpy(buffer + pos, &size, sizeof(size));
|
||||
pos += sizeof(size);
|
||||
}
|
||||
|
||||
*readed_size = pos;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isobusfs_srv_fa_rf_req(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg)
|
||||
{
|
||||
uint8_t res_fail[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
|
||||
struct isobusfs_read_file_response *res;
|
||||
struct isobusfs_srv_handles *handle;
|
||||
struct isobusfs_srv_client *client;
|
||||
struct isobusfs_fa_readf_req *req;
|
||||
ssize_t readed_size = 0;
|
||||
uint8_t error_code = 0;
|
||||
ssize_t send_size;
|
||||
int ret = 0;
|
||||
int count;
|
||||
|
||||
req = (struct isobusfs_fa_readf_req *)msg->buf;
|
||||
count = le16toh(req->count);
|
||||
|
||||
pr_debug("< rx: Read File Request. tan: %d, handle: %d, count: %d",
|
||||
req->tan, req->handle, count);
|
||||
/* C.3.5.1 Read File, General:
|
||||
* The requested data (excluding the other parameters) is sent in
|
||||
* the response (up to 1 780 bytes when TP is used, up to 65 530 bytes
|
||||
* when ETP is used). The number of data bytes read can be less than
|
||||
* requested if the end of the file is reached.
|
||||
* TODO: currently we are not able to detect support transport mode,
|
||||
* so ETP is assumed.
|
||||
*/
|
||||
if (count > ISOBUSFS_MAX_DATA_LENGH)
|
||||
count = ISOBUSFS_MAX_DATA_LENGH;
|
||||
|
||||
res = malloc(sizeof(*res) + count);
|
||||
if (!res) {
|
||||
pr_warn("failed to allocate memory");
|
||||
res = (struct isobusfs_read_file_response *)&res_fail[0];
|
||||
error_code = ISOBUSFS_ERR_OUT_OF_MEM;
|
||||
goto send_response;
|
||||
}
|
||||
|
||||
client = isobusfs_srv_get_client_by_msg(priv, msg);
|
||||
if (!client) {
|
||||
pr_warn("client not found");
|
||||
error_code = ISOBUSFS_ERR_OTHER;
|
||||
goto send_response;
|
||||
}
|
||||
|
||||
handle = isobusfs_srv_get_handle(priv, req->handle);
|
||||
if (!handle) {
|
||||
pr_warn("failed to find file with handle: %x", req->handle);
|
||||
error_code = ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
|
||||
}
|
||||
|
||||
/* Determine whether to read a file or a directory */
|
||||
if (handle->dir) {
|
||||
ret = isobusfs_srv_read_directory(handle, res->data, count,
|
||||
&readed_size);
|
||||
} else {
|
||||
ret = isobusfs_srv_read_file(handle, res->data, count,
|
||||
&readed_size);
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
error_code = ret;
|
||||
readed_size = 0;
|
||||
} else if (count != 0 && readed_size == 0) {
|
||||
error_code = ISOBUSFS_ERR_END_OF_FILE;
|
||||
}
|
||||
|
||||
send_response:
|
||||
res->fs_function =
|
||||
isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
|
||||
ISOBUSFS_FA_F_READ_FILE_RES);
|
||||
res->tan = req->tan;
|
||||
res->error_code = error_code;
|
||||
res->count = htole16(readed_size);
|
||||
|
||||
send_size = sizeof(*res) + readed_size;
|
||||
if (send_size < ISOBUSFS_MIN_TRANSFER_LENGH)
|
||||
send_size = ISOBUSFS_MIN_TRANSFER_LENGH;
|
||||
|
||||
/* send to socket */
|
||||
ret = isobusfs_srv_sendto(priv, msg, res, send_size);
|
||||
if (ret < 0) {
|
||||
pr_warn("can't send Read File Response");
|
||||
goto free_res;
|
||||
}
|
||||
|
||||
pr_debug("> tx: Read File Response. Error code: %d (%s), readed size: %d",
|
||||
error_code, isobusfs_error_to_str(error_code), readed_size);
|
||||
|
||||
free_res:
|
||||
free(res);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int isobusfs_srv_seek(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_srv_handles *handle, int32_t offset,
|
||||
uint8_t position_mode)
|
||||
{
|
||||
int whence;
|
||||
off_t offs;
|
||||
|
||||
switch (position_mode) {
|
||||
case ISOBUSFS_FA_SEEK_SET:
|
||||
whence = SEEK_SET;
|
||||
if (offset < 0) {
|
||||
pr_warn("Invalid offset. Offset must be positive.");
|
||||
return ISOBUSFS_ERR_INVALID_REQUESTED_LENGHT;
|
||||
}
|
||||
break;
|
||||
case ISOBUSFS_FA_SEEK_CUR:
|
||||
whence = SEEK_CUR;
|
||||
if (offset < 0 && handle->offset < -offset) {
|
||||
pr_warn("Invalid offset. Negative offset is too big.");
|
||||
return ISOBUSFS_ERR_INVALID_REQUESTED_LENGHT;
|
||||
}
|
||||
break;
|
||||
case ISOBUSFS_FA_SEEK_END:
|
||||
whence = SEEK_END;
|
||||
if (offset > 0) {
|
||||
pr_warn("Invalid offset. Offset must be negative");
|
||||
return ISOBUSFS_ERR_INVALID_REQUESTED_LENGHT;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
pr_warn("invalid position mode");
|
||||
return ISOBUSFS_ERR_OTHER;
|
||||
}
|
||||
|
||||
/* seek file */
|
||||
offs = lseek(handle->fd, offset, whence);
|
||||
if (offs < 0) {
|
||||
pr_warn("Failed to seek file");
|
||||
|
||||
switch (offs) {
|
||||
case EBADF:
|
||||
return ISOBUSFS_ERR_INVALID_HANDLE;
|
||||
case EINVAL:
|
||||
return ISOBUSFS_ERR_INVALID_REQUESTED_LENGHT;
|
||||
case ENXIO:
|
||||
return ISOBUSFS_ERR_END_OF_FILE;
|
||||
case EOVERFLOW:
|
||||
return ISOBUSFS_ERR_OUT_OF_MEM;
|
||||
case ESPIPE:
|
||||
return ISOBUSFS_ERR_ACCESS_DENIED;
|
||||
default:
|
||||
return ISOBUSFS_ERR_OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
handle->offset = offs;
|
||||
|
||||
return ISOBUSFS_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
static int isobusfs_srv_seek_directory(struct isobusfs_srv_handles *handle,
|
||||
int32_t offset)
|
||||
{
|
||||
DIR *dir = fdopendir(handle->fd);
|
||||
|
||||
if (!dir)
|
||||
return ISOBUSFS_ERR_OTHER;
|
||||
|
||||
rewinddir(dir);
|
||||
|
||||
for (int32_t i = 0; i < offset; i++) {
|
||||
if (readdir(dir) == NULL)
|
||||
return ISOBUSFS_ERR_END_OF_FILE;
|
||||
}
|
||||
|
||||
handle->dir_pos = offset;
|
||||
|
||||
return ISOBUSFS_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
static int isobusfs_srv_fa_sf_req(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg)
|
||||
{
|
||||
struct isobusfs_fa_seekf_res res = {0};
|
||||
struct isobusfs_srv_client *client;
|
||||
struct isobusfs_fa_seekf_req *req;
|
||||
struct isobusfs_srv_handles *handle;
|
||||
int32_t offset_out = 0;
|
||||
uint8_t error_code = 0;
|
||||
int ret;
|
||||
|
||||
req = (struct isobusfs_fa_seekf_req *)msg->buf;
|
||||
pr_debug("< rx: Seek File Request. Handle: %x, offset: %d, position mode: %d",
|
||||
req->handle, le32toh(req->offset), req->position_mode);
|
||||
|
||||
client = isobusfs_srv_get_client_by_msg(priv, msg);
|
||||
if (!client) {
|
||||
pr_warn("client not found");
|
||||
error_code = ISOBUSFS_ERR_OTHER;
|
||||
goto send_response;
|
||||
}
|
||||
|
||||
handle = isobusfs_srv_get_handle(priv, req->handle);
|
||||
if (!handle) {
|
||||
pr_warn("failed to find handle: %x", req->handle);
|
||||
error_code = ISOBUSFS_ERR_INVALID_HANDLE;
|
||||
goto send_response;
|
||||
}
|
||||
|
||||
if (handle->dir) {
|
||||
error_code = isobusfs_srv_seek_directory(handle,
|
||||
le32toh(req->offset));
|
||||
res.position = htole32(handle->dir_pos);
|
||||
} else {
|
||||
error_code = isobusfs_srv_seek(priv, handle, le32toh(req->offset),
|
||||
req->position_mode);
|
||||
res.position = htole32(handle->offset);
|
||||
}
|
||||
|
||||
send_response:
|
||||
res.fs_function =
|
||||
isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
|
||||
ISOBUSFS_FA_F_SEEK_FILE_RES);
|
||||
res.tan = req->tan;
|
||||
res.error_code = error_code;
|
||||
|
||||
/* send to socket */
|
||||
ret = isobusfs_srv_sendto(priv, msg, &res, sizeof(res));
|
||||
if (ret < 0) {
|
||||
pr_warn("can't send seek file response");
|
||||
return ret;
|
||||
}
|
||||
|
||||
pr_debug("> tx: Seek File Response. Error code: %d, offset: %d",
|
||||
error_code, offset_out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int isobusfs_srv_fa_cf_req(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg)
|
||||
{
|
||||
struct isobusfs_close_file_request *req;
|
||||
struct isobusfs_close_file_res res;
|
||||
struct isobusfs_srv_client *client;
|
||||
uint8_t error_code = 0;
|
||||
int ret;
|
||||
|
||||
req = (struct isobusfs_close_file_request *)msg->buf;
|
||||
|
||||
client = isobusfs_srv_get_client_by_msg(priv, msg);
|
||||
if (!client) {
|
||||
pr_warn("client not found");
|
||||
error_code = ISOBUSFS_ERR_OTHER;
|
||||
goto send_response;
|
||||
}
|
||||
|
||||
ret = isobusfs_srv_release_handle(priv, client, req->handle);
|
||||
if (ret < 0) {
|
||||
pr_warn("failed to release handle: %x", req->handle);
|
||||
switch (ret) {
|
||||
case -ENOENT:
|
||||
error_code = ISOBUSFS_ERR_FILE_ORPATH_NOT_FOUND;
|
||||
break;
|
||||
default:
|
||||
error_code = ISOBUSFS_ERR_OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
send_response:
|
||||
res.fs_function =
|
||||
isobusfs_cg_function_to_buf(ISOBUSFS_CG_FILE_ACCESS,
|
||||
ISOBUSFS_FA_F_CLOSE_FILE_RES);
|
||||
res.tan = req->tan;
|
||||
res.error_code = error_code;
|
||||
memset(&res.reserved[0], 0xff, sizeof(res.reserved));
|
||||
|
||||
/* send to socket */
|
||||
ret = isobusfs_srv_sendto(priv, msg, &res, sizeof(res));
|
||||
if (ret < 0) {
|
||||
pr_warn("can't send current directory response");
|
||||
goto err;
|
||||
}
|
||||
|
||||
pr_debug("> tx: Close File Response. Error code: %d", error_code);
|
||||
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Command group: file access */
|
||||
int isobusfs_srv_rx_cg_fa(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg)
|
||||
{
|
||||
int func = isobusfs_buf_to_function(msg->buf);
|
||||
int ret = 0;
|
||||
|
||||
switch (func) {
|
||||
case ISOBUSFS_FA_F_OPEN_FILE_REQ:
|
||||
ret = isobusfs_srv_fa_open_file_req(priv, msg);
|
||||
break;
|
||||
case ISOBUSFS_FA_F_CLOSE_FILE_REQ:
|
||||
ret = isobusfs_srv_fa_cf_req(priv, msg);
|
||||
break;
|
||||
case ISOBUSFS_FA_F_READ_FILE_REQ:
|
||||
ret = isobusfs_srv_fa_rf_req(priv, msg);
|
||||
break;
|
||||
case ISOBUSFS_FA_F_SEEK_FILE_REQ:
|
||||
ret = isobusfs_srv_fa_sf_req(priv, msg);
|
||||
break;
|
||||
case ISOBUSFS_FA_F_WRITE_FILE_REQ: /* fall through */
|
||||
default:
|
||||
pr_warn("%s: unsupported function: %i", __func__, func);
|
||||
isobusfs_srv_send_error(priv, msg,
|
||||
ISOBUSFS_ERR_FUNC_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
30
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_srv_fh.c
Executable file
30
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_srv_fh.c
Executable file
@@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "isobusfs_srv.h"
|
||||
|
||||
/* Command group: file handling */
|
||||
int isobusfs_srv_rx_cg_fh(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg)
|
||||
{
|
||||
int func = isobusfs_buf_to_function(msg->buf);
|
||||
int ret = 0;
|
||||
|
||||
switch (func) {
|
||||
case ISOBUSFS_FH_F_MOVE_FILE_REQ:
|
||||
case ISOBUSFS_FH_F_DELETE_FILE_REQ:
|
||||
case ISOBUSFS_FH_F_GET_FILE_ATTR_REQ:
|
||||
case ISOBUSFS_FH_F_SET_FILE_ATTR_REQ:
|
||||
case ISOBUSFS_FH_F_GET_FILE_DATETIME_REQ:
|
||||
default:
|
||||
isobusfs_srv_send_error(priv, msg,
|
||||
ISOBUSFS_ERR_FUNC_NOT_SUPPORTED);
|
||||
pr_warn("%s: unsupported function: %i", __func__, func);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
27
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_srv_vh.c
Executable file
27
Devices/Libraries/Systems/can-utils/isobusfs/isobusfs_srv_vh.c
Executable file
@@ -0,0 +1,27 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "isobusfs_srv.h"
|
||||
|
||||
/* Command group: volume hnadling */
|
||||
int isobusfs_srv_rx_cg_vh(struct isobusfs_srv_priv *priv,
|
||||
struct isobusfs_msg *msg)
|
||||
{
|
||||
int func = isobusfs_buf_to_function(msg->buf);
|
||||
int ret = 0;
|
||||
|
||||
switch (func) {
|
||||
/* TODO: currently not implemented */
|
||||
case ISOBUSFS_VA_F_INITIALIZE_VOLUME_REQ: /* fall through */
|
||||
default:
|
||||
isobusfs_srv_send_error(priv, msg,
|
||||
ISOBUSFS_ERR_FUNC_NOT_SUPPORTED);
|
||||
pr_warn("%s: unsupported function: %i", __func__, func);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
515
Devices/Libraries/Systems/can-utils/isotpdump.c
Executable file
515
Devices/Libraries/Systems/can-utils/isotpdump.c
Executable file
@@ -0,0 +1,515 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
|
||||
/*
|
||||
* isotpdump.c - dump and explain ISO15765-2 protocol CAN frames
|
||||
*
|
||||
* Copyright (c) 2008 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* Send feedback to <linux-can@vger.kernel.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <libgen.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/raw.h>
|
||||
#include <linux/sockios.h>
|
||||
|
||||
#include "terminal.h"
|
||||
|
||||
#define NO_CAN_ID 0xFFFFFFFFU
|
||||
|
||||
const char fc_info [4][9] = { "CTS", "WT", "OVFLW", "reserved" };
|
||||
const int canfd_on = 1;
|
||||
|
||||
void print_usage(char *prg)
|
||||
{
|
||||
fprintf(stderr, "\nUsage: %s [options] <CAN interface>\n", prg);
|
||||
fprintf(stderr, "Options:\n");
|
||||
fprintf(stderr, " -s <can_id> (source can_id. Use 8 digits for extended IDs)\n");
|
||||
fprintf(stderr, " -d <can_id> (destination can_id. Use 8 digits for extended IDs)\n");
|
||||
fprintf(stderr, " -x <addr> (extended addressing mode. Use 'any' for all addresses)\n");
|
||||
fprintf(stderr, " -X <addr> (extended addressing mode (rx addr). Use 'any' for all)\n");
|
||||
fprintf(stderr, " -c (color mode)\n");
|
||||
fprintf(stderr, " -a (print data also in ASCII-chars)\n");
|
||||
fprintf(stderr, " -t <type> (timestamp: (a)bsolute/(d)elta/(z)ero/(A)bsolute w date)\n");
|
||||
fprintf(stderr, " -u (print uds messages)\n");
|
||||
fprintf(stderr, "\nCAN IDs and addresses are given and expected in hexadecimal values.\n");
|
||||
fprintf(stderr, "\nUDS output contains a flag which provides information about the type of the \n");
|
||||
fprintf(stderr, "message.\n\n");
|
||||
fprintf(stderr, "Flags:\n");
|
||||
fprintf(stderr, " [SRQ] = Service Request\n");
|
||||
fprintf(stderr, " [PSR] = Positive Service Response\n");
|
||||
fprintf(stderr, " [NRC] = Negative Response Code\n");
|
||||
fprintf(stderr, " [???] = Unknown (not specified)\n");
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
void print_uds_message(int service, int nrc)
|
||||
{
|
||||
char *service_name;
|
||||
char *flag = "[???]";
|
||||
|
||||
if ((service >= 0x50 && service <= 0x7E) || (service >= 0xC3 && service <= 0xC8)) {
|
||||
flag = "[PSR]";
|
||||
service = service - 0x40;
|
||||
} else if ((service >= 0x10 && service <= 0x3E) ||
|
||||
(service >= 0x83 && service <= 0x88) ||
|
||||
(service >= 0xBA && service <= 0xBE))
|
||||
flag = "[SRQ]";
|
||||
|
||||
switch(service) {
|
||||
case 0x10: service_name = "DiagnosticSessionControl"; break;
|
||||
case 0x11: service_name = "ECUReset"; break;
|
||||
case 0x14: service_name = "ClearDiagnosticInformation"; break;
|
||||
case 0x19: service_name = "ReadDTCInformation"; break;
|
||||
case 0x22: service_name = "ReadDataByIdentifier"; break;
|
||||
case 0x23: service_name = "ReadMemoryByAddress"; break;
|
||||
case 0x24: service_name = "ReadScalingDataByIdentifier"; break;
|
||||
case 0x27: service_name = "SecurityAccess"; break;
|
||||
case 0x28: service_name = "CommunicationControl"; break;
|
||||
case 0x2A: service_name = "ReadDataByPeriodicIdentifier"; break;
|
||||
case 0x2C: service_name = "DynamicallyDefineDataIdentifier"; break;
|
||||
case 0x2E: service_name = "WriteDataByIdentifier"; break;
|
||||
case 0x2F: service_name = "InputOutputControlByIdentifier"; break;
|
||||
case 0x31: service_name = "RoutineControl"; break;
|
||||
case 0x34: service_name = "RequestDownload"; break;
|
||||
case 0x35: service_name = "RequestUpload"; break;
|
||||
case 0x36: service_name = "TransferData"; break;
|
||||
case 0x37: service_name = "RequestTransferExit"; break;
|
||||
case 0x38: service_name = "RequestFileTransfer"; break;
|
||||
case 0x3D: service_name = "WriteMemoryByAddress"; break;
|
||||
case 0x3E: service_name = "TesterPresent"; break;
|
||||
case 0x83: service_name = "AccessTimingParameter"; break;
|
||||
case 0x84: service_name = "SecuredDataTransmission"; break;
|
||||
case 0x85: service_name = "ControlDTCSetting"; break;
|
||||
case 0x86: service_name = "ResponseOnEvent"; break;
|
||||
case 0x87: service_name = "LinkControl"; break;
|
||||
case 0x7F: flag = "[NRC]";
|
||||
switch (nrc) {
|
||||
case 0x00: service_name = "positiveResponse"; break;
|
||||
case 0x10: service_name = "generalReject"; break;
|
||||
case 0x11: service_name = "serviceNotSupported"; break;
|
||||
case 0x12: service_name = "sub-functionNotSupported"; break;
|
||||
case 0x13: service_name = "incorrectMessageLengthOrInvalidFormat"; break;
|
||||
case 0x14: service_name = "responseTooLong"; break;
|
||||
case 0x21: service_name = "busyRepeatRequest"; break;
|
||||
case 0x22: service_name = "conditionsNotCorrect"; break;
|
||||
case 0x24: service_name = "requestSequenceError"; break;
|
||||
case 0x25: service_name = "noResponseFromSubnetComponent"; break;
|
||||
case 0x26: service_name = "FailurePreventsExecutionOfRequestedAction"; break;
|
||||
case 0x31: service_name = "requestOutOfRange"; break;
|
||||
case 0x33: service_name = "securityAccessDenied"; break;
|
||||
case 0x35: service_name = "invalidKey"; break;
|
||||
case 0x36: service_name = "exceedNumberOfAttempts"; break;
|
||||
case 0x37: service_name = "requiredTimeDelayNotExpired"; break;
|
||||
case 0x70: service_name = "uploadDownloadNotAccepted"; break;
|
||||
case 0x71: service_name = "transferDataSuspended"; break;
|
||||
case 0x72: service_name = "generalProgrammingFailure"; break;
|
||||
case 0x73: service_name = "wrongBlockSequenceCounter"; break;
|
||||
case 0x78: service_name = "requestCorrectlyReceived-ResponsePending"; break;
|
||||
case 0x7E: service_name = "sub-functionNotSupportedInActiveSession"; break;
|
||||
case 0x7F: service_name = "serviceNotSupportedInActiveSession"; break;
|
||||
case 0x81: service_name = "rpmTooHigh"; break;
|
||||
case 0x82: service_name = "rpmTooLow"; break;
|
||||
case 0x83: service_name = "engineIsRunning"; break;
|
||||
case 0x84: service_name = "engineIsNotRunning"; break;
|
||||
case 0x85: service_name = "engineRunTimeTooLow"; break;
|
||||
case 0x86: service_name = "temperatureTooHigh"; break;
|
||||
case 0x87: service_name = "temperatureTooLow"; break;
|
||||
case 0x88: service_name = "vehicleSpeedTooHigh"; break;
|
||||
case 0x89: service_name = "vehicleSpeedTooLow"; break;
|
||||
case 0x8A: service_name = "throttle/PedalTooHigh"; break;
|
||||
case 0x8B: service_name = "throttle/PedalTooLow"; break;
|
||||
case 0x8C: service_name = "transmissionRangeNotInNeutral"; break;
|
||||
case 0x8D: service_name = "transmissionRangeNotInGear"; break;
|
||||
case 0x8F: service_name = "brakeSwitch(es)NotClosed (Brake Pedal not pressed or not applied)"; break;
|
||||
case 0x90: service_name = "shifterLeverNotInPark"; break;
|
||||
case 0x91: service_name = "torqueConverterClutchLocked"; break;
|
||||
case 0x92: service_name = "voltageTooHigh"; break;
|
||||
case 0x93: service_name = "voltageTooLow"; break;
|
||||
|
||||
default:
|
||||
if (nrc > 0x37 && nrc < 0x50) {
|
||||
service_name = "reservedByExtendedDataLinkSecurityDocument"; break;
|
||||
}
|
||||
else if (nrc > 0x93 && nrc < 0xF0) {
|
||||
service_name = "reservedForSpecificConditionsNotCorrect"; break;
|
||||
}
|
||||
else if (nrc > 0xEF && nrc < 0xFE) {
|
||||
service_name = "vehicleManufacturerSpecificConditionsNotCorrect"; break;
|
||||
}
|
||||
else {
|
||||
service_name = "ISOSAEReserved"; break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default: service_name = "Unknown";
|
||||
}
|
||||
printf("%s %s", flag, service_name);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_can addr;
|
||||
struct can_filter rfilter[2];
|
||||
struct canfd_frame frame;
|
||||
int nbytes, i;
|
||||
canid_t src = NO_CAN_ID;
|
||||
canid_t dst = NO_CAN_ID;
|
||||
int ext = 0;
|
||||
int extaddr = 0;
|
||||
int extany = 0;
|
||||
int rx_ext = 0;
|
||||
int rx_extaddr = 0;
|
||||
int rx_extany = 0;
|
||||
int asc = 0;
|
||||
int color = 0;
|
||||
int uds_output = 0;
|
||||
int is_ff = 0;
|
||||
int timestamp = 0;
|
||||
int datidx = 0;
|
||||
unsigned long fflen = 0;
|
||||
struct timeval tv, last_tv;
|
||||
unsigned int n_pci;
|
||||
int opt;
|
||||
|
||||
last_tv.tv_sec = 0;
|
||||
last_tv.tv_usec = 0;
|
||||
|
||||
while ((opt = getopt(argc, argv, "s:d:ax:X:ct:u?")) != -1) {
|
||||
switch (opt) {
|
||||
case 's':
|
||||
src = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
src |= CAN_EFF_FLAG;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
dst = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
dst |= CAN_EFF_FLAG;
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
color = 1;
|
||||
break;
|
||||
|
||||
case 'a':
|
||||
asc = 1;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
ext = 1;
|
||||
if (!strncmp(optarg, "any", 3))
|
||||
extany = 1;
|
||||
else
|
||||
extaddr = strtoul(optarg, NULL, 16) & 0xFF;
|
||||
break;
|
||||
|
||||
case 'X':
|
||||
rx_ext = 1;
|
||||
if (!strncmp(optarg, "any", 3))
|
||||
rx_extany = 1;
|
||||
else
|
||||
rx_extaddr = strtoul(optarg, NULL, 16) & 0xFF;
|
||||
break;
|
||||
|
||||
case 't':
|
||||
timestamp = optarg[0];
|
||||
if ((timestamp != 'a') && (timestamp != 'A') &&
|
||||
(timestamp != 'd') && (timestamp != 'z')) {
|
||||
printf("%s: unknown timestamp mode '%c' - ignored\n",
|
||||
basename(argv[0]), optarg[0]);
|
||||
timestamp = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
uds_output = 1;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unknown option %c\n", opt);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (rx_ext && !ext) {
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if ((argc - optind) != 1 || src == NO_CAN_ID || dst == NO_CAN_ID) {
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if ((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
|
||||
perror("socket");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* try to switch the socket into CAN FD mode */
|
||||
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &canfd_on, sizeof(canfd_on));
|
||||
|
||||
if (src & CAN_EFF_FLAG) {
|
||||
rfilter[0].can_id = src & (CAN_EFF_MASK | CAN_EFF_FLAG);
|
||||
rfilter[0].can_mask = (CAN_EFF_MASK|CAN_EFF_FLAG|CAN_RTR_FLAG);
|
||||
} else {
|
||||
rfilter[0].can_id = src & CAN_SFF_MASK;
|
||||
rfilter[0].can_mask = (CAN_SFF_MASK|CAN_EFF_FLAG|CAN_RTR_FLAG);
|
||||
}
|
||||
|
||||
if (dst & CAN_EFF_FLAG) {
|
||||
rfilter[1].can_id = dst & (CAN_EFF_MASK | CAN_EFF_FLAG);
|
||||
rfilter[1].can_mask = (CAN_EFF_MASK|CAN_EFF_FLAG|CAN_RTR_FLAG);
|
||||
} else {
|
||||
rfilter[1].can_id = dst & CAN_SFF_MASK;
|
||||
rfilter[1].can_mask = (CAN_SFF_MASK|CAN_EFF_FLAG|CAN_RTR_FLAG);
|
||||
}
|
||||
|
||||
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
|
||||
|
||||
addr.can_family = AF_CAN;
|
||||
addr.can_ifindex = if_nametoindex(argv[optind]);
|
||||
if (!addr.can_ifindex) {
|
||||
perror("if_nametoindex");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror("bind");
|
||||
return 1;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
nbytes = read(s, &frame, sizeof(frame));
|
||||
if (nbytes < 0) {
|
||||
perror("read");
|
||||
return 1;
|
||||
}
|
||||
if (nbytes != CAN_MTU && nbytes != CANFD_MTU) {
|
||||
fprintf(stderr, "read: incomplete CAN frame %zu %d\n", sizeof(frame), nbytes);
|
||||
return 1;
|
||||
}
|
||||
if (frame.can_id == src && ext && !extany &&
|
||||
extaddr != frame.data[0])
|
||||
continue;
|
||||
|
||||
if (frame.can_id == dst && rx_ext && !rx_extany &&
|
||||
rx_extaddr != frame.data[0])
|
||||
continue;
|
||||
|
||||
if (color)
|
||||
printf("%s", (frame.can_id == src) ? FGRED : FGBLUE);
|
||||
|
||||
if (timestamp) {
|
||||
ioctl(s, SIOCGSTAMP, &tv);
|
||||
|
||||
switch (timestamp) {
|
||||
case 'a': /* absolute with timestamp */
|
||||
printf("(%llu.%06llu) ", (unsigned long long)tv.tv_sec, (unsigned long long)tv.tv_usec);
|
||||
break;
|
||||
|
||||
case 'A': /* absolute with date */
|
||||
{
|
||||
struct tm tm;
|
||||
char timestring[25];
|
||||
|
||||
tm = *localtime(&tv.tv_sec);
|
||||
strftime(timestring, 24, "%Y-%m-%d %H:%M:%S",
|
||||
&tm);
|
||||
printf("(%s.%06llu) ", timestring, (unsigned long long)tv.tv_usec);
|
||||
} break;
|
||||
|
||||
case 'd': /* delta */
|
||||
case 'z': /* starting with zero */
|
||||
{
|
||||
struct timeval diff;
|
||||
|
||||
if (last_tv.tv_sec == 0) /* first init */
|
||||
last_tv = tv;
|
||||
diff.tv_sec = tv.tv_sec - last_tv.tv_sec;
|
||||
diff.tv_usec = tv.tv_usec - last_tv.tv_usec;
|
||||
if (diff.tv_usec < 0)
|
||||
diff.tv_sec--, diff.tv_usec += 1000000;
|
||||
if (diff.tv_sec < 0)
|
||||
diff.tv_sec = diff.tv_usec = 0;
|
||||
printf("(%llu.%06llu) ", (unsigned long long)diff.tv_sec,
|
||||
(unsigned long long)diff.tv_usec);
|
||||
|
||||
if (timestamp == 'd')
|
||||
last_tv =
|
||||
tv; /* update for delta calculation */
|
||||
} break;
|
||||
|
||||
default: /* no timestamp output */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (frame.can_id & CAN_EFF_FLAG)
|
||||
printf(" %s %8X", argv[optind], frame.can_id & CAN_EFF_MASK);
|
||||
else
|
||||
printf(" %s %3X", argv[optind], frame.can_id & CAN_SFF_MASK);
|
||||
|
||||
if (ext)
|
||||
printf("{%02X}", frame.data[0]);
|
||||
|
||||
if (nbytes == CAN_MTU)
|
||||
printf(" [%d] ", frame.len);
|
||||
else
|
||||
printf(" [%02d] ", frame.len);
|
||||
|
||||
datidx = 0;
|
||||
n_pci = frame.data[ext];
|
||||
|
||||
switch (n_pci & 0xF0) {
|
||||
case 0x00:
|
||||
is_ff = 1;
|
||||
if (n_pci & 0xF) {
|
||||
printf("[SF] ln: %-4d data:", n_pci & 0xF);
|
||||
datidx = ext+1;
|
||||
} else {
|
||||
printf("[SF] ln: %-4d data:", frame.data[ext + 1]);
|
||||
datidx = ext+2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x10:
|
||||
is_ff = 1;
|
||||
fflen = ((n_pci & 0x0F)<<8) + frame.data[ext+1];
|
||||
if (fflen)
|
||||
datidx = ext+2;
|
||||
else {
|
||||
fflen = (frame.data[ext+2]<<24) +
|
||||
(frame.data[ext+3]<<16) +
|
||||
(frame.data[ext+4]<<8) +
|
||||
frame.data[ext+5];
|
||||
datidx = ext+6;
|
||||
}
|
||||
printf("[FF] ln: %-4lu data:", fflen);
|
||||
break;
|
||||
|
||||
case 0x20:
|
||||
printf("[CF] sn: %X data:", n_pci & 0x0F);
|
||||
datidx = ext+1;
|
||||
break;
|
||||
|
||||
case 0x30:
|
||||
n_pci &= 0x0F;
|
||||
printf("[FC] FC: %d ", n_pci);
|
||||
|
||||
if (n_pci > 3)
|
||||
n_pci = 3;
|
||||
|
||||
printf("= %s # ", fc_info[n_pci]);
|
||||
|
||||
printf("BS: %d %s# ", frame.data[ext+1],
|
||||
(frame.data[ext+1])? "":"= off ");
|
||||
|
||||
i = frame.data[ext+2];
|
||||
printf("STmin: 0x%02X = ", i);
|
||||
|
||||
if (i < 0x80)
|
||||
printf("%d ms", i);
|
||||
else if (i > 0xF0 && i < 0xFA)
|
||||
printf("%d us", (i & 0x0F) * 100);
|
||||
else
|
||||
printf("reserved");
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("[??]");
|
||||
}
|
||||
|
||||
if (datidx && frame.len > datidx) {
|
||||
printf(" ");
|
||||
for (i = datidx; i < frame.len; i++) {
|
||||
printf("%02X ", frame.data[i]);
|
||||
}
|
||||
|
||||
if (asc) {
|
||||
printf("%*s", ((7-ext) - (frame.len-datidx))*3 + 5 ,
|
||||
"- '");
|
||||
for (i = datidx; i < frame.len; i++) {
|
||||
printf("%c",((frame.data[i] > 0x1F) &&
|
||||
(frame.data[i] < 0x7F))?
|
||||
frame.data[i] : '.');
|
||||
}
|
||||
printf("'");
|
||||
}
|
||||
if (uds_output && is_ff) {
|
||||
int offset = 3;
|
||||
if (asc)
|
||||
offset = 1;
|
||||
printf("%*s", ((7-ext) - (frame.len-datidx))*offset + 3,
|
||||
" - ");
|
||||
print_uds_message(frame.data[datidx], frame.data[datidx+2]);
|
||||
is_ff = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (color)
|
||||
printf("%s", ATTRESET);
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
close(s);
|
||||
|
||||
return 0;
|
||||
}
|
||||
422
Devices/Libraries/Systems/can-utils/isotpperf.c
Executable file
422
Devices/Libraries/Systems/can-utils/isotpperf.c
Executable file
@@ -0,0 +1,422 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
|
||||
/*
|
||||
* isotpperf.c - ISO15765-2 protocol performance visualisation
|
||||
*
|
||||
* Copyright (c) 2014 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* Send feedback to <linux-can@vger.kernel.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <libgen.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/raw.h>
|
||||
#include <linux/sockios.h>
|
||||
|
||||
#define NO_CAN_ID 0xFFFFFFFFU
|
||||
#define PERCENTRES 2 /* resolution in percent for bargraph */
|
||||
#define NUMBAR (100/PERCENTRES) /* number of bargraph elements */
|
||||
|
||||
void print_usage(char *prg)
|
||||
{
|
||||
fprintf(stderr, "%s - ISO15765-2 protocol performance visualisation.\n", prg);
|
||||
fprintf(stderr, "\nUsage: %s [options] <CAN interface>\n", prg);
|
||||
fprintf(stderr, "Options:\n");
|
||||
fprintf(stderr, " -s <can_id> (source can_id. Use 8 digits for extended IDs)\n");
|
||||
fprintf(stderr, " -d <can_id> (destination can_id. Use 8 digits for extended IDs)\n");
|
||||
fprintf(stderr, " -x <addr> (extended addressing mode)\n");
|
||||
fprintf(stderr, " -X <addr> (extended addressing mode (rx addr))\n");
|
||||
fprintf(stderr, "\nCAN IDs and addresses are given and expected in hexadecimal values.\n");
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
/* substitute math.h function log10(value)+1 */
|
||||
unsigned int getdigits(unsigned int value)
|
||||
{
|
||||
int digits = 1;
|
||||
|
||||
while (value > 9) {
|
||||
digits++;
|
||||
value /= 10;
|
||||
}
|
||||
return digits;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
fd_set rdfs;
|
||||
int s;
|
||||
int running = 1;
|
||||
struct sockaddr_can addr;
|
||||
struct can_filter rfilter[2];
|
||||
struct canfd_frame frame;
|
||||
int canfd_on = 1;
|
||||
int nbytes, i, ret;
|
||||
canid_t src = NO_CAN_ID;
|
||||
canid_t dst = NO_CAN_ID;
|
||||
int ext = 0;
|
||||
int extaddr = 0;
|
||||
int rx_ext = 0;
|
||||
int rx_extaddr = 0;
|
||||
int datidx = 0;
|
||||
unsigned char bs = 0;
|
||||
unsigned char stmin = 0;
|
||||
unsigned char brs = 0;
|
||||
unsigned char ll_dl = 0;
|
||||
unsigned long fflen = 0;
|
||||
unsigned fflen_digits = 0;
|
||||
unsigned long rcvlen = 0;
|
||||
unsigned long percent = 0;
|
||||
struct timeval start_tv, end_tv, diff_tv, timeo;
|
||||
unsigned int n_pci;
|
||||
unsigned int sn, last_sn = 0;
|
||||
int opt;
|
||||
|
||||
while ((opt = getopt(argc, argv, "s:d:x:X:?")) != -1) {
|
||||
switch (opt) {
|
||||
case 's':
|
||||
src = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
src |= CAN_EFF_FLAG;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
dst = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
dst |= CAN_EFF_FLAG;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
ext = 1;
|
||||
extaddr = strtoul(optarg, NULL, 16) & 0xFF;
|
||||
break;
|
||||
|
||||
case 'X':
|
||||
rx_ext = 1;
|
||||
rx_extaddr = strtoul(optarg, NULL, 16) & 0xFF;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unknown option %c\n", opt);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((argc - optind) != 1 || src == NO_CAN_ID || dst == NO_CAN_ID) {
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if ((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
|
||||
perror("socket");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* try to switch the socket into CAN FD mode */
|
||||
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &canfd_on, sizeof(canfd_on));
|
||||
|
||||
/* set single CAN ID raw filters for src and dst frames */
|
||||
if (src & CAN_EFF_FLAG) {
|
||||
rfilter[0].can_id = src & (CAN_EFF_MASK | CAN_EFF_FLAG);
|
||||
rfilter[0].can_mask = (CAN_EFF_MASK|CAN_EFF_FLAG|CAN_RTR_FLAG);
|
||||
} else {
|
||||
rfilter[0].can_id = src & CAN_SFF_MASK;
|
||||
rfilter[0].can_mask = (CAN_SFF_MASK|CAN_EFF_FLAG|CAN_RTR_FLAG);
|
||||
}
|
||||
|
||||
if (dst & CAN_EFF_FLAG) {
|
||||
rfilter[1].can_id = dst & (CAN_EFF_MASK | CAN_EFF_FLAG);
|
||||
rfilter[1].can_mask = (CAN_EFF_MASK|CAN_EFF_FLAG|CAN_RTR_FLAG);
|
||||
} else {
|
||||
rfilter[1].can_id = dst & CAN_SFF_MASK;
|
||||
rfilter[1].can_mask = (CAN_SFF_MASK|CAN_EFF_FLAG|CAN_RTR_FLAG);
|
||||
}
|
||||
|
||||
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));
|
||||
|
||||
addr.can_family = AF_CAN;
|
||||
addr.can_ifindex = if_nametoindex(argv[optind]);
|
||||
if (!addr.can_ifindex) {
|
||||
perror("if_nametoindex");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror("bind");
|
||||
return 1;
|
||||
}
|
||||
|
||||
while (running) {
|
||||
|
||||
FD_ZERO(&rdfs);
|
||||
FD_SET(s, &rdfs);
|
||||
|
||||
/* timeout for ISO TP transmissions */
|
||||
timeo.tv_sec = 1;
|
||||
timeo.tv_usec = 0;
|
||||
|
||||
if ((ret = select(s+1, &rdfs, NULL, NULL, &timeo)) < 0) {
|
||||
running = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* detected timeout of already started transmission */
|
||||
if (rcvlen && !(FD_ISSET(s, &rdfs))) {
|
||||
printf("\r%-*s",78, " (transmission timed out)");
|
||||
fflush(stdout);
|
||||
fflen = rcvlen = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
nbytes = read(s, &frame, sizeof(frame));
|
||||
if (nbytes < 0) {
|
||||
perror("read");
|
||||
ret = nbytes;
|
||||
running = 0;
|
||||
continue;
|
||||
}
|
||||
if (nbytes != CAN_MTU && nbytes != CANFD_MTU) {
|
||||
fprintf(stderr, "read: incomplete CAN frame %zu %d\n", sizeof(frame), nbytes);
|
||||
ret = nbytes;
|
||||
running = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rcvlen) {
|
||||
/* make sure to process only the detected PDU CAN frame type */
|
||||
if (canfd_on && (nbytes != CANFD_MTU))
|
||||
continue;
|
||||
if (!canfd_on && (nbytes != CAN_MTU))
|
||||
continue;
|
||||
}
|
||||
|
||||
/* check extended address if provided */
|
||||
if (ext && extaddr != frame.data[0])
|
||||
continue;
|
||||
|
||||
/* only get flow control information from dst CAN ID */
|
||||
if (frame.can_id == dst) {
|
||||
/* check extended address if provided */
|
||||
if (rx_ext && frame.data[0] != rx_extaddr)
|
||||
continue;
|
||||
|
||||
n_pci = frame.data[rx_ext];
|
||||
/* check flow control PCI only */
|
||||
if ((n_pci & 0xF0) != 0x30)
|
||||
continue;
|
||||
|
||||
bs = frame.data[rx_ext + 1];
|
||||
stmin = frame.data[rx_ext + 2];
|
||||
}
|
||||
|
||||
/* data content starts and index datidx */
|
||||
datidx = 0;
|
||||
|
||||
n_pci = frame.data[ext];
|
||||
switch (n_pci & 0xF0) {
|
||||
|
||||
case 0x00:
|
||||
/* SF */
|
||||
if (n_pci & 0xF) {
|
||||
fflen = rcvlen = n_pci & 0xF;
|
||||
datidx = ext+1;
|
||||
} else {
|
||||
fflen = rcvlen = frame.data[ext + 1];
|
||||
datidx = ext+2;
|
||||
}
|
||||
|
||||
/* ignore incorrect SF PDUs */
|
||||
if (frame.len < rcvlen + datidx)
|
||||
fflen = rcvlen = 0;
|
||||
|
||||
/* get number of digits for printing */
|
||||
fflen_digits = getdigits(fflen);
|
||||
|
||||
/* get CAN FD bitrate & LL_DL setting information */
|
||||
brs = frame.flags & CANFD_BRS;
|
||||
ll_dl = frame.len;
|
||||
if (ll_dl < 8)
|
||||
ll_dl = 8;
|
||||
|
||||
ioctl(s, SIOCGSTAMP, &start_tv);
|
||||
|
||||
/* determine CAN frame mode for this PDU */
|
||||
if (nbytes == CAN_MTU)
|
||||
canfd_on = 0;
|
||||
else
|
||||
canfd_on = 1;
|
||||
|
||||
break;
|
||||
|
||||
case 0x10:
|
||||
/* FF */
|
||||
fflen = ((n_pci & 0x0F)<<8) + frame.data[ext+1];
|
||||
if (fflen)
|
||||
datidx = ext+2;
|
||||
else {
|
||||
fflen = (frame.data[ext+2]<<24) +
|
||||
(frame.data[ext+3]<<16) +
|
||||
(frame.data[ext+4]<<8) +
|
||||
frame.data[ext+5];
|
||||
datidx = ext+6;
|
||||
}
|
||||
|
||||
/* to increase the time resolution we multiply fflen with 1000 later */
|
||||
if (fflen >= (UINT32_MAX / 1000)) {
|
||||
printf("fflen %lu is more than ~4.2 MB - ignoring PDU\n", fflen);
|
||||
fflush(stdout);
|
||||
fflen = rcvlen = 0;
|
||||
continue;
|
||||
}
|
||||
rcvlen = frame.len - datidx;
|
||||
last_sn = 0;
|
||||
|
||||
/* get number of digits for printing */
|
||||
fflen_digits = getdigits(fflen);
|
||||
|
||||
/* get CAN FD bitrate & LL_DL setting information */
|
||||
brs = frame.flags & CANFD_BRS;
|
||||
ll_dl = frame.len;
|
||||
|
||||
ioctl(s, SIOCGSTAMP, &start_tv);
|
||||
|
||||
/* determine CAN frame mode for this PDU */
|
||||
if (nbytes == CAN_MTU)
|
||||
canfd_on = 0;
|
||||
else
|
||||
canfd_on = 1;
|
||||
|
||||
break;
|
||||
|
||||
case 0x20:
|
||||
/* CF */
|
||||
if (rcvlen) {
|
||||
sn = n_pci & 0x0F;
|
||||
if (sn == ((last_sn + 1) & 0xF)) {
|
||||
last_sn = sn;
|
||||
datidx = ext+1;
|
||||
rcvlen += frame.len - datidx;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* PDU reception in process */
|
||||
if (rcvlen) {
|
||||
if (rcvlen > fflen)
|
||||
rcvlen = fflen;
|
||||
|
||||
percent = (rcvlen * 100 / fflen);
|
||||
printf("\r %3lu%% ", percent);
|
||||
|
||||
printf("|");
|
||||
|
||||
if (percent > 100)
|
||||
percent = 100;
|
||||
|
||||
for (i=0; i < NUMBAR; i++){
|
||||
if (i < (int)(percent/PERCENTRES))
|
||||
printf("X");
|
||||
else
|
||||
printf(".");
|
||||
}
|
||||
printf("| %*lu/%lu ", fflen_digits, rcvlen, fflen);
|
||||
}
|
||||
|
||||
/* PDU complete */
|
||||
if (rcvlen && rcvlen >= fflen) {
|
||||
|
||||
printf("\r%s %02d%c (BS:%2hhu # ", canfd_on?"CAN-FD":"CAN2.0", ll_dl, brs?'*':' ', bs);
|
||||
if (stmin < 0x80)
|
||||
printf("STmin:%3hhu msec)", stmin);
|
||||
else if (stmin > 0xF0 && stmin < 0xFA)
|
||||
printf("STmin:%3u usec)", (stmin & 0xF) * 100);
|
||||
else
|
||||
printf("STmin: invalid )");
|
||||
|
||||
printf(" : %lu byte in ", fflen);
|
||||
|
||||
/* calculate time */
|
||||
ioctl(s, SIOCGSTAMP, &end_tv);
|
||||
diff_tv.tv_sec = end_tv.tv_sec - start_tv.tv_sec;
|
||||
diff_tv.tv_usec = end_tv.tv_usec - start_tv.tv_usec;
|
||||
if (diff_tv.tv_usec < 0)
|
||||
diff_tv.tv_sec--, diff_tv.tv_usec += 1000000;
|
||||
if (diff_tv.tv_sec < 0)
|
||||
diff_tv.tv_sec = diff_tv.tv_usec = 0;
|
||||
|
||||
/* check devisor to be not zero */
|
||||
if (diff_tv.tv_sec * 1000 + diff_tv.tv_usec / 1000){
|
||||
printf("%llu.%06llus ", (unsigned long long)diff_tv.tv_sec, (unsigned long long)diff_tv.tv_usec);
|
||||
printf("=> %lu byte/s", (fflen * 1000) /
|
||||
(unsigned long)(diff_tv.tv_sec * 1000 + diff_tv.tv_usec / 1000));
|
||||
} else
|
||||
printf("(no time available) ");
|
||||
|
||||
printf("\n");
|
||||
/* wait for next PDU */
|
||||
fflen = rcvlen = 0;
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
close(s);
|
||||
|
||||
return ret;
|
||||
}
|
||||
264
Devices/Libraries/Systems/can-utils/isotprecv.c
Executable file
264
Devices/Libraries/Systems/can-utils/isotprecv.c
Executable file
@@ -0,0 +1,264 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
|
||||
/*
|
||||
* isotprecv.c
|
||||
*
|
||||
* Copyright (c) 2008 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* Send feedback to <linux-can@vger.kernel.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <libgen.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/isotp.h>
|
||||
|
||||
#define NO_CAN_ID 0xFFFFFFFFU
|
||||
#define BUFSIZE 67000 /* size > 66000 to check socket API internal checks */
|
||||
|
||||
void print_usage(char *prg)
|
||||
{
|
||||
fprintf(stderr, "\nUsage: %s [options] <CAN interface>\n", prg);
|
||||
fprintf(stderr, "Options:\n");
|
||||
fprintf(stderr, " -s <can_id> (source can_id. Use 8 digits for extended IDs)\n");
|
||||
fprintf(stderr, " -d <can_id> (destination can_id. Use 8 digits for extended IDs)\n");
|
||||
fprintf(stderr, " -x <addr>[:<rxaddr>] (extended addressing / opt. separate rxaddr)\n");
|
||||
fprintf(stderr, " -p [tx]:[rx] (set and enable tx/rx padding bytes)\n");
|
||||
fprintf(stderr, " -P <mode> (check rx padding for (l)ength (c)ontent (a)ll)\n");
|
||||
fprintf(stderr, " -b <bs> (blocksize. 0 = off)\n");
|
||||
fprintf(stderr, " -m <val> (STmin in ms/ns. See spec.)\n");
|
||||
fprintf(stderr, " -f <time ns> (force rx stmin value in nanosecs)\n");
|
||||
fprintf(stderr, " -w <num> (max. wait frame transmissions.)\n");
|
||||
fprintf(stderr, " -l (loop: do not exit after pdu reception.)\n");
|
||||
fprintf(stderr, " -F (enable dynamic flow control parameters)\n");
|
||||
fprintf(stderr, " -L <mtu>:<tx_dl>:<tx_flags> (link layer options for CAN FD)\n");
|
||||
fprintf(stderr, "\nCAN IDs and addresses are given and expected in hexadecimal values.\n");
|
||||
fprintf(stderr, "The pdu data is written on STDOUT in space separated ASCII hex values.\n");
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_can addr;
|
||||
static struct can_isotp_options opts;
|
||||
static struct can_isotp_fc_options fcopts;
|
||||
static struct can_isotp_ll_options llopts;
|
||||
int opt, i;
|
||||
extern int optind, opterr, optopt;
|
||||
__u32 force_rx_stmin = 0;
|
||||
int loop = 0;
|
||||
|
||||
unsigned char msg[BUFSIZE];
|
||||
int nbytes;
|
||||
|
||||
addr.can_addr.tp.tx_id = addr.can_addr.tp.rx_id = NO_CAN_ID;
|
||||
|
||||
while ((opt = getopt(argc, argv, "s:d:x:p:P:b:m:w:f:lFL:?")) != -1) {
|
||||
switch (opt) {
|
||||
case 's':
|
||||
addr.can_addr.tp.tx_id = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
addr.can_addr.tp.tx_id |= CAN_EFF_FLAG;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
addr.can_addr.tp.rx_id = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
addr.can_addr.tp.rx_id |= CAN_EFF_FLAG;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
{
|
||||
int elements = sscanf(optarg, "%hhx:%hhx",
|
||||
&opts.ext_address,
|
||||
&opts.rx_ext_address);
|
||||
|
||||
if (elements == 1)
|
||||
opts.flags |= CAN_ISOTP_EXTEND_ADDR;
|
||||
else if (elements == 2)
|
||||
opts.flags |= (CAN_ISOTP_EXTEND_ADDR | CAN_ISOTP_RX_EXT_ADDR);
|
||||
else {
|
||||
printf("incorrect extended addr values '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'p':
|
||||
{
|
||||
int elements = sscanf(optarg, "%hhx:%hhx",
|
||||
&opts.txpad_content,
|
||||
&opts.rxpad_content);
|
||||
|
||||
if (elements == 1)
|
||||
opts.flags |= CAN_ISOTP_TX_PADDING;
|
||||
else if (elements == 2)
|
||||
opts.flags |= (CAN_ISOTP_TX_PADDING | CAN_ISOTP_RX_PADDING);
|
||||
else if (sscanf(optarg, ":%hhx", &opts.rxpad_content) == 1)
|
||||
opts.flags |= CAN_ISOTP_RX_PADDING;
|
||||
else {
|
||||
printf("incorrect padding values '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'P':
|
||||
if (optarg[0] == 'l')
|
||||
opts.flags |= CAN_ISOTP_CHK_PAD_LEN;
|
||||
else if (optarg[0] == 'c')
|
||||
opts.flags |= CAN_ISOTP_CHK_PAD_DATA;
|
||||
else if (optarg[0] == 'a')
|
||||
opts.flags |= (CAN_ISOTP_CHK_PAD_LEN | CAN_ISOTP_CHK_PAD_DATA);
|
||||
else {
|
||||
printf("unknown padding check option '%c'.\n", optarg[0]);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
fcopts.bs = strtoul(optarg, NULL, 16) & 0xFF;
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
fcopts.stmin = strtoul(optarg, NULL, 16) & 0xFF;
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
fcopts.wftmax = strtoul(optarg, NULL, 16) & 0xFF;
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
opts.flags |= CAN_ISOTP_FORCE_RXSTMIN;
|
||||
force_rx_stmin = strtoul(optarg, NULL, 10);
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
loop = 1;
|
||||
break;
|
||||
|
||||
case 'F':
|
||||
opts.flags |= CAN_ISOTP_DYN_FC_PARMS;
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
if (sscanf(optarg, "%hhu:%hhu:%hhu",
|
||||
&llopts.mtu,
|
||||
&llopts.tx_dl,
|
||||
&llopts.tx_flags) != 3) {
|
||||
printf("unknown link layer options '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
|
||||
case '?':
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unknown option %c\n", opt);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((argc - optind != 1) ||
|
||||
(addr.can_addr.tp.tx_id == NO_CAN_ID) ||
|
||||
(addr.can_addr.tp.rx_id == NO_CAN_ID)) {
|
||||
print_usage(basename(argv[0]));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((s = socket(PF_CAN, SOCK_DGRAM, CAN_ISOTP)) < 0) {
|
||||
perror("socket");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_OPTS, &opts, sizeof(opts));
|
||||
setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_RECV_FC, &fcopts, sizeof(fcopts));
|
||||
|
||||
if (llopts.tx_dl) {
|
||||
if (setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_LL_OPTS, &llopts, sizeof(llopts)) < 0) {
|
||||
perror("link layer sockopt");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.flags & CAN_ISOTP_FORCE_RXSTMIN)
|
||||
setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_RX_STMIN, &force_rx_stmin, sizeof(force_rx_stmin));
|
||||
|
||||
addr.can_family = AF_CAN;
|
||||
addr.can_ifindex = if_nametoindex(argv[optind]);
|
||||
if (!addr.can_ifindex) {
|
||||
perror("if_nametoindex");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror("bind");
|
||||
close(s);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
do {
|
||||
nbytes = read(s, msg, BUFSIZE);
|
||||
if (nbytes > 0 && nbytes < BUFSIZE)
|
||||
for (i=0; i < nbytes; i++)
|
||||
printf("%02X ", msg[i]);
|
||||
printf("\n");
|
||||
} while (loop);
|
||||
|
||||
close(s);
|
||||
|
||||
return 0;
|
||||
}
|
||||
323
Devices/Libraries/Systems/can-utils/isotpsend.c
Executable file
323
Devices/Libraries/Systems/can-utils/isotpsend.c
Executable file
@@ -0,0 +1,323 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
|
||||
/*
|
||||
* isotpsend.c
|
||||
*
|
||||
* Copyright (c) 2008 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* Send feedback to <linux-can@vger.kernel.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <libgen.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/isotp.h>
|
||||
|
||||
#define NO_CAN_ID 0xFFFFFFFFU
|
||||
#define BUFSIZE 67000 /* size > 66000 kernel buf to test socket API internal checks */
|
||||
#define ZERO_STRING "ZERO"
|
||||
|
||||
void print_usage(char *prg)
|
||||
{
|
||||
fprintf(stderr, "\nUsage: %s [options] <CAN interface>\n", prg);
|
||||
fprintf(stderr, "Options:\n");
|
||||
fprintf(stderr, " -s <can_id> (source can_id. Use 8 digits for extended IDs)\n");
|
||||
fprintf(stderr, " -d <can_id> (destination can_id. Use 8 digits for extended IDs)\n");
|
||||
fprintf(stderr, " -x <addr>[:<rxaddr>] (extended addressing / opt. separate rxaddr)\n");
|
||||
fprintf(stderr, " -p [tx]:[rx] (set and enable tx/rx padding bytes)\n");
|
||||
fprintf(stderr, " -P <mode> (check rx padding for (l)ength (c)ontent (a)ll)\n");
|
||||
fprintf(stderr, " -t <time ns> (frame transmit time (N_As) in nanosecs) (*)\n");
|
||||
fprintf(stderr, " -f <time ns> (ignore FC and force local tx stmin value in nanosecs)\n");
|
||||
fprintf(stderr, " -D <len> (send a fixed PDU with len bytes - no STDIN data)\n");
|
||||
fprintf(stderr, " -l <num> (send num PDUs - use 'i' for infinite loop)\n");
|
||||
fprintf(stderr, " -g <usecs> (wait given usecs before sending a PDU)\n");
|
||||
fprintf(stderr, " -b (block until the PDU transmission is completed)\n");
|
||||
fprintf(stderr, " -S (SF broadcast mode - for functional addressing)\n");
|
||||
fprintf(stderr, " -C (CF broadcast mode - no wait for flow controls)\n");
|
||||
fprintf(stderr, " -L <mtu>:<tx_dl>:<tx_flags> (link layer options for CAN FD)\n");
|
||||
fprintf(stderr, "\nCAN IDs and addresses are given and expected in hexadecimal values.\n");
|
||||
fprintf(stderr, "The pdu data is expected on STDIN in space separated ASCII hex values.\n");
|
||||
fprintf(stderr, "(*) = Use '-t %s' to set N_As to zero for Linux version 5.18+\n", ZERO_STRING);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int s;
|
||||
struct sockaddr_can addr;
|
||||
static struct can_isotp_options opts;
|
||||
static struct can_isotp_ll_options llopts;
|
||||
int opt;
|
||||
extern int optind, opterr, optopt;
|
||||
unsigned int loops = 1; /* one (== no) loop by default */
|
||||
useconds_t usecs = 0; /* wait before sending the PDU */
|
||||
__u32 force_tx_stmin = 0;
|
||||
unsigned char buf[BUFSIZE];
|
||||
int buflen = 0;
|
||||
int datalen = 0;
|
||||
int retval = 0;
|
||||
|
||||
addr.can_addr.tp.tx_id = addr.can_addr.tp.rx_id = NO_CAN_ID;
|
||||
|
||||
while ((opt = getopt(argc, argv, "s:d:x:p:P:t:f:D:l:g:bSCL:?")) != -1) {
|
||||
switch (opt) {
|
||||
case 's':
|
||||
addr.can_addr.tp.tx_id = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
addr.can_addr.tp.tx_id |= CAN_EFF_FLAG;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
addr.can_addr.tp.rx_id = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
addr.can_addr.tp.rx_id |= CAN_EFF_FLAG;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
{
|
||||
int elements = sscanf(optarg, "%hhx:%hhx",
|
||||
&opts.ext_address,
|
||||
&opts.rx_ext_address);
|
||||
|
||||
if (elements == 1)
|
||||
opts.flags |= CAN_ISOTP_EXTEND_ADDR;
|
||||
else if (elements == 2)
|
||||
opts.flags |= (CAN_ISOTP_EXTEND_ADDR | CAN_ISOTP_RX_EXT_ADDR);
|
||||
else {
|
||||
printf("incorrect extended addr values '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'p':
|
||||
{
|
||||
int elements = sscanf(optarg, "%hhx:%hhx",
|
||||
&opts.txpad_content,
|
||||
&opts.rxpad_content);
|
||||
|
||||
if (elements == 1)
|
||||
opts.flags |= CAN_ISOTP_TX_PADDING;
|
||||
else if (elements == 2)
|
||||
opts.flags |= (CAN_ISOTP_TX_PADDING | CAN_ISOTP_RX_PADDING);
|
||||
else if (sscanf(optarg, ":%hhx", &opts.rxpad_content) == 1)
|
||||
opts.flags |= CAN_ISOTP_RX_PADDING;
|
||||
else {
|
||||
printf("incorrect padding values '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'P':
|
||||
if (optarg[0] == 'l')
|
||||
opts.flags |= CAN_ISOTP_CHK_PAD_LEN;
|
||||
else if (optarg[0] == 'c')
|
||||
opts.flags |= CAN_ISOTP_CHK_PAD_DATA;
|
||||
else if (optarg[0] == 'a')
|
||||
opts.flags |= (CAN_ISOTP_CHK_PAD_LEN | CAN_ISOTP_CHK_PAD_DATA);
|
||||
else {
|
||||
printf("unknown padding check option '%c'.\n", optarg[0]);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
|
||||
case 't':
|
||||
if (!strncmp(optarg, ZERO_STRING, strlen(ZERO_STRING)))
|
||||
opts.frame_txtime = CAN_ISOTP_FRAME_TXTIME_ZERO;
|
||||
else
|
||||
opts.frame_txtime = strtoul(optarg, NULL, 10);
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
opts.flags |= CAN_ISOTP_FORCE_TXSTMIN;
|
||||
force_tx_stmin = strtoul(optarg, NULL, 10);
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
datalen = strtoul(optarg, NULL, 10);
|
||||
if (!datalen || datalen >= BUFSIZE) {
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
if (optarg[0] == 'i') {
|
||||
loops = 0; /* infinite loop */
|
||||
} else {
|
||||
loops = strtoul(optarg, NULL, 10);
|
||||
if (!loops) {
|
||||
fprintf(stderr, "Invalid argument for option -l!\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
usecs = strtoul(optarg, NULL, 10);
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
opts.flags |= CAN_ISOTP_WAIT_TX_DONE;
|
||||
break;
|
||||
|
||||
case 'S':
|
||||
opts.flags |= CAN_ISOTP_SF_BROADCAST;
|
||||
break;
|
||||
|
||||
case 'C':
|
||||
opts.flags |= CAN_ISOTP_CF_BROADCAST;
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
if (sscanf(optarg, "%hhu:%hhu:%hhu",
|
||||
&llopts.mtu,
|
||||
&llopts.tx_dl,
|
||||
&llopts.tx_flags) != 3) {
|
||||
printf("unknown link layer options '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
|
||||
case '?':
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unknown option %c\n", opt);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#define BC_FLAGS (CAN_ISOTP_SF_BROADCAST | CAN_ISOTP_CF_BROADCAST)
|
||||
|
||||
if ((argc - optind != 1) ||
|
||||
(addr.can_addr.tp.tx_id == NO_CAN_ID) ||
|
||||
((opts.flags & BC_FLAGS) == BC_FLAGS) ||
|
||||
((addr.can_addr.tp.rx_id == NO_CAN_ID) &&
|
||||
(!(opts.flags & BC_FLAGS)))) {
|
||||
print_usage(basename(argv[0]));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((s = socket(PF_CAN, SOCK_DGRAM, CAN_ISOTP)) < 0) {
|
||||
perror("socket");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_OPTS, &opts, sizeof(opts)) < 0) {
|
||||
perror("sockopt");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
if (llopts.tx_dl) {
|
||||
if (setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_LL_OPTS, &llopts, sizeof(llopts)) < 0) {
|
||||
perror("link layer sockopt");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.flags & CAN_ISOTP_FORCE_TXSTMIN)
|
||||
setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_TX_STMIN, &force_tx_stmin, sizeof(force_tx_stmin));
|
||||
|
||||
addr.can_family = AF_CAN;
|
||||
addr.can_ifindex = if_nametoindex(argv[optind]);
|
||||
if (!addr.can_ifindex) {
|
||||
perror("if_nametoindex");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror("bind");
|
||||
close(s);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!datalen) {
|
||||
while (buflen < BUFSIZE && scanf("%hhx", &buf[buflen]) == 1)
|
||||
buflen++;
|
||||
} else {
|
||||
for (buflen = 0; buflen < datalen; buflen++)
|
||||
buf[buflen] = ((buflen % 0xFF) + 1) & 0xFF;
|
||||
}
|
||||
|
||||
loop:
|
||||
if (usecs)
|
||||
usleep(usecs);
|
||||
|
||||
retval = write(s, buf, buflen);
|
||||
if (retval < 0) {
|
||||
perror("write");
|
||||
return retval;
|
||||
}
|
||||
|
||||
if (retval != buflen)
|
||||
fprintf(stderr, "wrote only %d from %d byte\n", retval, buflen);
|
||||
|
||||
if (loops) {
|
||||
if (--loops)
|
||||
goto loop;
|
||||
} else {
|
||||
goto loop;
|
||||
}
|
||||
|
||||
/*
|
||||
* due to a Kernel internal wait queue the PDU is sent completely
|
||||
* before close() returns.
|
||||
*/
|
||||
close(s);
|
||||
|
||||
return 0;
|
||||
}
|
||||
441
Devices/Libraries/Systems/can-utils/isotpserver.c
Executable file
441
Devices/Libraries/Systems/can-utils/isotpserver.c
Executable file
@@ -0,0 +1,441 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
|
||||
/*
|
||||
* isotpserver.c
|
||||
*
|
||||
* Implements a socket server which understands ASCII HEX
|
||||
* messages for simple TCP/IP <-> ISO 15765-2 bridging.
|
||||
*
|
||||
* General message format: <[data]+>
|
||||
*
|
||||
* e.g. for an eight bytes PDU
|
||||
*
|
||||
* <1122334455667788>
|
||||
*
|
||||
* Valid ISO 15625-2 PDUs have a length from 1-4095 bytes.
|
||||
*
|
||||
* Authors:
|
||||
* Andre Naujoks (the socket server stuff)
|
||||
* Oliver Hartkopp (the rest)
|
||||
*
|
||||
* Copyright (c) 2002-2010 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* Send feedback to <linux-can@vger.kernel.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <libgen.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/isotp.h>
|
||||
|
||||
#define NO_CAN_ID 0xFFFFFFFFU
|
||||
|
||||
/* allow PDUs greater 4095 bytes according ISO 15765-2:2015 */
|
||||
#define MAX_PDU_LENGTH 6000
|
||||
|
||||
int b64hex(char *asc, unsigned char *bin, int len)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
if (!sscanf(asc+(i*2), "%2hhx", bin+i))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void childdied(int i)
|
||||
{
|
||||
wait(NULL);
|
||||
}
|
||||
|
||||
void print_usage(char *prg)
|
||||
{
|
||||
fprintf(stderr, "\nUsage: %s -l <port> -s <can_id> -d <can_id> [options] <CAN interface>\n", prg);
|
||||
fprintf(stderr, "Options:\n");
|
||||
fprintf(stderr, "ip addressing:\n");
|
||||
fprintf(stderr, " -l <port> * (local port for the server)\n");
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "isotp addressing:\n");
|
||||
fprintf(stderr, " -s <can_id> * (source can_id. Use 8 digits for extended IDs)\n");
|
||||
fprintf(stderr, " -d <can_id> * (destination can_id. Use 8 digits for extended IDs)\n");
|
||||
fprintf(stderr, " -x <addr>[:<rxaddr>] (extended addressing / opt. separate rxaddr)\n");
|
||||
fprintf(stderr, " -L <mtu>:<tx_dl>:<tx_flags> (link layer options for CAN FD)\n");
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "padding:\n");
|
||||
fprintf(stderr, " -p [tx]:[rx] (set and enable tx/rx padding bytes)\n");
|
||||
fprintf(stderr, " -P <mode> (check rx padding for (l)ength (c)ontent (a)ll)\n");
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "rx path:\n (config, which is sent to the sender / data source)\n");
|
||||
fprintf(stderr, " -b <bs> (blocksize. 0 = off)\n");
|
||||
fprintf(stderr, " -m <val> (STmin in ms/ns. See spec.)\n");
|
||||
fprintf(stderr, " -w <num> (max. wait frame transmissions)\n");
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "tx path:\n (config, which changes local tx settings)\n");
|
||||
fprintf(stderr, " -t <time ns> (transmit time in nanosecs)\n");
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "(* = mandatory option)\n");
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, "All values except for '-l' and '-t' are expected in hexadecimal values.\n");
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
extern int optind, opterr, optopt;
|
||||
int opt;
|
||||
|
||||
int sl, sa, sc; /* (L)isten, (A)ccept, (C)AN sockets */
|
||||
struct sockaddr_in saddr, clientaddr;
|
||||
struct sockaddr_can caddr;
|
||||
static struct can_isotp_options opts;
|
||||
static struct can_isotp_fc_options fcopts;
|
||||
static struct can_isotp_ll_options llopts;
|
||||
socklen_t sin_size = sizeof(clientaddr);
|
||||
socklen_t caddrlen = sizeof(caddr);
|
||||
|
||||
struct sigaction signalaction;
|
||||
sigset_t sigset;
|
||||
|
||||
fd_set readfds;
|
||||
|
||||
int i;
|
||||
int nbytes;
|
||||
|
||||
int local_port = 0;
|
||||
int verbose = 0;
|
||||
|
||||
int idx = 0; /* index in txmsg[] */
|
||||
|
||||
unsigned char msg[MAX_PDU_LENGTH + 1]; /* isotp socket message buffer (4095 + test_for_too_long_byte)*/
|
||||
char rxmsg[MAX_PDU_LENGTH * 2 + 4]; /* isotp->tcp ASCII message buffer (4095*2 + < > \n null) */
|
||||
char txmsg[MAX_PDU_LENGTH * 2 + 3]; /* tcp->isotp ASCII message buffer (4095*2 + < > null) */
|
||||
|
||||
/* mark missing mandatory commandline options as missing */
|
||||
caddr.can_addr.tp.tx_id = caddr.can_addr.tp.rx_id = NO_CAN_ID;
|
||||
|
||||
while ((opt = getopt(argc, argv, "l:s:d:x:p:P:b:m:w:t:L:v?")) != -1) {
|
||||
switch (opt) {
|
||||
case 'l':
|
||||
local_port = strtoul(optarg, NULL, 10);
|
||||
break;
|
||||
|
||||
case 's':
|
||||
caddr.can_addr.tp.tx_id = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
caddr.can_addr.tp.tx_id |= CAN_EFF_FLAG;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
caddr.can_addr.tp.rx_id = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
caddr.can_addr.tp.rx_id |= CAN_EFF_FLAG;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
{
|
||||
int elements = sscanf(optarg, "%hhx:%hhx",
|
||||
&opts.ext_address,
|
||||
&opts.rx_ext_address);
|
||||
|
||||
if (elements == 1)
|
||||
opts.flags |= CAN_ISOTP_EXTEND_ADDR;
|
||||
else if (elements == 2)
|
||||
opts.flags |= (CAN_ISOTP_EXTEND_ADDR | CAN_ISOTP_RX_EXT_ADDR);
|
||||
else {
|
||||
printf("incorrect extended addr values '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'p':
|
||||
{
|
||||
int elements = sscanf(optarg, "%hhx:%hhx",
|
||||
&opts.txpad_content,
|
||||
&opts.rxpad_content);
|
||||
|
||||
if (elements == 1)
|
||||
opts.flags |= CAN_ISOTP_TX_PADDING;
|
||||
else if (elements == 2)
|
||||
opts.flags |= (CAN_ISOTP_TX_PADDING | CAN_ISOTP_RX_PADDING);
|
||||
else if (sscanf(optarg, ":%hhx", &opts.rxpad_content) == 1)
|
||||
opts.flags |= CAN_ISOTP_RX_PADDING;
|
||||
else {
|
||||
printf("incorrect padding values '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'P':
|
||||
if (optarg[0] == 'l')
|
||||
opts.flags |= CAN_ISOTP_CHK_PAD_LEN;
|
||||
else if (optarg[0] == 'c')
|
||||
opts.flags |= CAN_ISOTP_CHK_PAD_DATA;
|
||||
else if (optarg[0] == 'a')
|
||||
opts.flags |= (CAN_ISOTP_CHK_PAD_LEN | CAN_ISOTP_CHK_PAD_DATA);
|
||||
else {
|
||||
printf("unknown padding check option '%c'.\n", optarg[0]);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
fcopts.bs = strtoul(optarg, NULL, 16) & 0xFF;
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
fcopts.stmin = strtoul(optarg, NULL, 16) & 0xFF;
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
fcopts.wftmax = strtoul(optarg, NULL, 16) & 0xFF;
|
||||
break;
|
||||
|
||||
case 't':
|
||||
opts.frame_txtime = strtoul(optarg, NULL, 10);
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
if (sscanf(optarg, "%hhu:%hhu:%hhu",
|
||||
&llopts.mtu,
|
||||
&llopts.tx_dl,
|
||||
&llopts.tx_flags) != 3) {
|
||||
printf("unknown link layer options '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
verbose = 1;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
print_usage(basename(argv[0]));
|
||||
exit(0);
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unknown option %c\n", opt);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((argc - optind != 1) || (local_port == 0) ||
|
||||
(caddr.can_addr.tp.tx_id == NO_CAN_ID) ||
|
||||
(caddr.can_addr.tp.rx_id == NO_CAN_ID)) {
|
||||
print_usage(basename(argv[0]));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
sigemptyset(&sigset);
|
||||
signalaction.sa_handler = &childdied;
|
||||
signalaction.sa_mask = sigset;
|
||||
signalaction.sa_flags = 0;
|
||||
sigaction(SIGCHLD, &signalaction, NULL); /* signal for dying child */
|
||||
|
||||
if((sl = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
|
||||
perror("inetsocket");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
saddr.sin_family = AF_INET;
|
||||
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
saddr.sin_port = htons(local_port);
|
||||
|
||||
while(bind(sl,(struct sockaddr*)&saddr, sizeof(saddr)) < 0) {
|
||||
struct timespec f = {
|
||||
.tv_nsec = 100 * 1000 * 1000,
|
||||
};
|
||||
|
||||
printf(".");
|
||||
fflush(NULL);
|
||||
nanosleep(&f, NULL);
|
||||
}
|
||||
|
||||
if (listen(sl, 3) != 0) {
|
||||
perror("listen");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
while (1) {
|
||||
sa = accept(sl,(struct sockaddr *)&clientaddr, &sin_size);
|
||||
if (sa > 0 ){
|
||||
if (!fork())
|
||||
break;
|
||||
close(sa);
|
||||
}
|
||||
else {
|
||||
if (errno != EINTR) {
|
||||
/*
|
||||
* If the cause for the error was NOT the
|
||||
* signal from a dying child => give an error
|
||||
*/
|
||||
perror("accept");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((sc = socket(PF_CAN, SOCK_DGRAM, CAN_ISOTP)) < 0) {
|
||||
perror("socket");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
setsockopt(sc, SOL_CAN_ISOTP, CAN_ISOTP_OPTS, &opts, sizeof(opts));
|
||||
setsockopt(sc, SOL_CAN_ISOTP, CAN_ISOTP_RECV_FC, &fcopts, sizeof(fcopts));
|
||||
|
||||
if (llopts.tx_dl) {
|
||||
if (setsockopt(sc, SOL_CAN_ISOTP, CAN_ISOTP_LL_OPTS, &llopts, sizeof(llopts)) < 0) {
|
||||
perror("link layer sockopt");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
caddr.can_family = AF_CAN;
|
||||
caddr.can_ifindex = if_nametoindex(argv[optind]);
|
||||
if (!caddr.can_ifindex) {
|
||||
perror("if_nametoindex");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (bind(sc, (struct sockaddr *)&caddr, caddrlen) < 0) {
|
||||
perror("bind");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
while (1) {
|
||||
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(sc, &readfds);
|
||||
FD_SET(sa, &readfds);
|
||||
|
||||
select((sc > sa)?sc+1:sa+1, &readfds, NULL, NULL, NULL);
|
||||
|
||||
if (FD_ISSET(sc, &readfds)) {
|
||||
|
||||
|
||||
nbytes = read(sc, &msg, MAX_PDU_LENGTH + 1);
|
||||
|
||||
if (nbytes < 1 || nbytes > MAX_PDU_LENGTH) {
|
||||
perror("read from isotp socket");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
rxmsg[0] = '<';
|
||||
|
||||
for ( i = 0; i < nbytes; i++)
|
||||
sprintf(rxmsg + 1 + 2*i, "%02X", msg[i]);
|
||||
|
||||
/* finalize string for sending */
|
||||
strcat(rxmsg, ">\n");
|
||||
|
||||
if (verbose)
|
||||
printf("CAN>TCP %s", rxmsg);
|
||||
|
||||
send(sa, rxmsg, strlen(rxmsg), 0);
|
||||
}
|
||||
|
||||
|
||||
if (FD_ISSET(sa, &readfds)) {
|
||||
|
||||
if (read(sa, txmsg+idx, 1) < 1) {
|
||||
perror("read from tcp/ip socket");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!idx) {
|
||||
if (txmsg[0] == '<')
|
||||
idx = 1;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/* max len is 4095*2 + '<' + '>' = 8192. The buffer index starts with 0 */
|
||||
if (idx > MAX_PDU_LENGTH * 2 + 1) {
|
||||
idx = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (txmsg[idx] != '>') {
|
||||
idx++;
|
||||
continue;
|
||||
}
|
||||
|
||||
txmsg[idx+1] = 0;
|
||||
idx = 0;
|
||||
|
||||
/* must be an even number of bytes and at least one data byte <XX> */
|
||||
if (strlen(txmsg) < 4 || strlen(txmsg) % 2)
|
||||
continue;
|
||||
|
||||
if (verbose)
|
||||
printf("TCP>CAN %s\n", txmsg);
|
||||
|
||||
nbytes = (strlen(txmsg)-2)/2;
|
||||
if (b64hex(txmsg+1, msg, nbytes) == 0)
|
||||
send(sc, msg, nbytes, 0);
|
||||
}
|
||||
}
|
||||
|
||||
close(sc);
|
||||
close(sa);
|
||||
|
||||
return 0;
|
||||
}
|
||||
407
Devices/Libraries/Systems/can-utils/isotpsniffer.c
Executable file
407
Devices/Libraries/Systems/can-utils/isotpsniffer.c
Executable file
@@ -0,0 +1,407 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
|
||||
/*
|
||||
* isotpsniffer.c - dump ISO15765-2 datagrams using PF_CAN isotp protocol
|
||||
*
|
||||
* Copyright (c) 2008 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* Send feedback to <linux-can@vger.kernel.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <libgen.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "terminal.h"
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/isotp.h>
|
||||
#include <linux/sockios.h>
|
||||
|
||||
#define NO_CAN_ID 0xFFFFFFFFU
|
||||
|
||||
#define FORMAT_HEX 1
|
||||
#define FORMAT_ASCII 2
|
||||
#define FORMAT_DEFAULT (FORMAT_ASCII | FORMAT_HEX)
|
||||
|
||||
void print_usage(char *prg)
|
||||
{
|
||||
fprintf(stderr, "\nUsage: %s [options] <CAN interface>\n", prg);
|
||||
fprintf(stderr, "Options:\n");
|
||||
fprintf(stderr, " -s <can_id> (source can_id. Use 8 digits for extended IDs)\n");
|
||||
fprintf(stderr, " -d <can_id> (destination can_id. Use 8 digits for extended IDs)\n");
|
||||
fprintf(stderr, " -x <addr> (extended addressing mode)\n");
|
||||
fprintf(stderr, " -X <addr> (extended addressing mode - rx addr)\n");
|
||||
fprintf(stderr, " -c (color mode)\n");
|
||||
fprintf(stderr, " -t <type> (timestamp: (a)bsolute/(d)elta/(z)ero/(A)bsolute w date)\n");
|
||||
fprintf(stderr, " -f <format> (1 = HEX, 2 = ASCII, 3 = HEX & ASCII - default: %d)\n", FORMAT_DEFAULT);
|
||||
fprintf(stderr, " -L (set link layer options for CAN FD)\n");
|
||||
fprintf(stderr, " -h <len> (head: print only first <len> bytes)\n");
|
||||
fprintf(stderr, "\nCAN IDs and addresses are given and expected in hexadecimal values.\n");
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
void printbuf(unsigned char *buffer, int nbytes, int color, int timestamp,
|
||||
int format, struct timeval *tv, struct timeval *last_tv,
|
||||
canid_t src, int socket, char *candevice, int head)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (color == 1)
|
||||
printf("%s", FGRED);
|
||||
|
||||
if (color == 2)
|
||||
printf("%s", FGBLUE);
|
||||
|
||||
if (timestamp) {
|
||||
ioctl(socket, SIOCGSTAMP, tv);
|
||||
|
||||
switch (timestamp) {
|
||||
|
||||
case 'a': /* absolute with timestamp */
|
||||
printf("(%llu.%06llu) ", (unsigned long long)tv->tv_sec, (unsigned long long)tv->tv_usec);
|
||||
break;
|
||||
|
||||
case 'A': /* absolute with date */
|
||||
{
|
||||
struct tm tm;
|
||||
char timestring[25];
|
||||
|
||||
tm = *localtime(&tv->tv_sec);
|
||||
strftime(timestring, 24, "%Y-%m-%d %H:%M:%S", &tm);
|
||||
printf("(%s.%06llu) ", timestring, (unsigned long long)tv->tv_usec);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'd': /* delta */
|
||||
case 'z': /* starting with zero */
|
||||
{
|
||||
struct timeval diff;
|
||||
|
||||
if (last_tv->tv_sec == 0) /* first init */
|
||||
*last_tv = *tv;
|
||||
diff.tv_sec = tv->tv_sec - last_tv->tv_sec;
|
||||
diff.tv_usec = tv->tv_usec - last_tv->tv_usec;
|
||||
if (diff.tv_usec < 0)
|
||||
diff.tv_sec--, diff.tv_usec += 1000000;
|
||||
if (diff.tv_sec < 0)
|
||||
diff.tv_sec = diff.tv_usec = 0;
|
||||
printf("(%llu.%06llu) ", (unsigned long long)diff.tv_sec, (unsigned long long)diff.tv_usec);
|
||||
|
||||
if (timestamp == 'd')
|
||||
*last_tv = *tv; /* update for delta calculation */
|
||||
}
|
||||
break;
|
||||
|
||||
default: /* no timestamp output */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* the source socket gets pdu data from the destination id */
|
||||
printf(" %s %03X [%d] ", candevice, src & CAN_EFF_MASK, nbytes);
|
||||
if (format & FORMAT_HEX) {
|
||||
for (i=0; i<nbytes; i++) {
|
||||
printf("%02X ", buffer[i]);
|
||||
if (head && i+1 >= head) {
|
||||
printf("... ");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (format & FORMAT_ASCII)
|
||||
printf(" - ");
|
||||
}
|
||||
if (format & FORMAT_ASCII) {
|
||||
printf("'");
|
||||
for (i=0; i<nbytes; i++) {
|
||||
if (isprint(buffer[i]))
|
||||
printf("%c", buffer[i]);
|
||||
else
|
||||
printf(".");
|
||||
if (head && i+1 >= head)
|
||||
break;
|
||||
}
|
||||
printf("'");
|
||||
if (head && i+1 >= head)
|
||||
printf(" ... ");
|
||||
}
|
||||
|
||||
if (color)
|
||||
printf("%s", ATTRESET);
|
||||
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
fd_set rdfs;
|
||||
int s = -1, t = -1;
|
||||
struct sockaddr_can addr;
|
||||
char if_name[IFNAMSIZ];
|
||||
static struct can_isotp_options opts;
|
||||
static struct can_isotp_ll_options llopts;
|
||||
int r = 0;
|
||||
int opt, quit = 0;
|
||||
int color = 0;
|
||||
int head = 0;
|
||||
int timestamp = 0;
|
||||
int format = FORMAT_DEFAULT;
|
||||
canid_t src = NO_CAN_ID;
|
||||
canid_t dst = NO_CAN_ID;
|
||||
extern int optind, opterr, optopt;
|
||||
static struct timeval tv, last_tv;
|
||||
|
||||
unsigned char buffer[4096];
|
||||
int nbytes;
|
||||
|
||||
while ((opt = getopt(argc, argv, "s:d:x:X:h:ct:f:L?")) != -1) {
|
||||
switch (opt) {
|
||||
case 's':
|
||||
src = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
src |= CAN_EFF_FLAG;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
dst = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
dst |= CAN_EFF_FLAG;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
opts.flags |= CAN_ISOTP_EXTEND_ADDR;
|
||||
opts.ext_address = strtoul(optarg, NULL, 16) & 0xFF;
|
||||
break;
|
||||
|
||||
case 'X':
|
||||
opts.flags |= CAN_ISOTP_RX_EXT_ADDR;
|
||||
opts.rx_ext_address = strtoul(optarg, NULL, 16) & 0xFF;
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
format = (atoi(optarg) & (FORMAT_ASCII | FORMAT_HEX));
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
llopts.mtu = CANFD_MTU;
|
||||
llopts.tx_dl = CANFD_MAX_DLEN;
|
||||
llopts.tx_flags = CANFD_BRS;
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
head = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
color = 1;
|
||||
break;
|
||||
|
||||
case 't':
|
||||
timestamp = optarg[0];
|
||||
if ((timestamp != 'a') && (timestamp != 'A') &&
|
||||
(timestamp != 'd') && (timestamp != 'z')) {
|
||||
printf("%s: unknown timestamp mode '%c' - ignored\n",
|
||||
basename(argv[0]), optarg[0]);
|
||||
timestamp = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case '?':
|
||||
print_usage(basename(argv[0]));
|
||||
goto out;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unknown option %c\n", opt);
|
||||
print_usage(basename(argv[0]));
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if ((argc - optind) != 1 || src == NO_CAN_ID || dst == NO_CAN_ID) {
|
||||
print_usage(basename(argv[0]));
|
||||
r = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((opts.flags & CAN_ISOTP_RX_EXT_ADDR) && (!(opts.flags & CAN_ISOTP_EXTEND_ADDR))) {
|
||||
print_usage(basename(argv[0]));
|
||||
r = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((s = socket(PF_CAN, SOCK_DGRAM, CAN_ISOTP)) < 0) {
|
||||
perror("socket");
|
||||
r = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((t = socket(PF_CAN, SOCK_DGRAM, CAN_ISOTP)) < 0) {
|
||||
perror("socket");
|
||||
r = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
opts.flags |= CAN_ISOTP_LISTEN_MODE;
|
||||
|
||||
strncpy(if_name, argv[optind], IFNAMSIZ - 1);
|
||||
if_name[IFNAMSIZ - 1] = '\0';
|
||||
|
||||
addr.can_family = AF_CAN;
|
||||
addr.can_ifindex = if_nametoindex(if_name);
|
||||
if (!addr.can_ifindex) {
|
||||
perror("if_nametoindex");
|
||||
r = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_OPTS, &opts, sizeof(opts)) < 0) {
|
||||
perror("setsockopt");
|
||||
r = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((llopts.mtu) && (setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_LL_OPTS, &llopts, sizeof(llopts))) < 0) {
|
||||
perror("setsockopt");
|
||||
r = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
addr.can_addr.tp.tx_id = src;
|
||||
addr.can_addr.tp.rx_id = dst;
|
||||
|
||||
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror("bind");
|
||||
r = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (opts.flags & CAN_ISOTP_RX_EXT_ADDR) {
|
||||
/* flip extended address info due to separate rx ext addr */
|
||||
__u8 tmpext;
|
||||
|
||||
tmpext = opts.ext_address;
|
||||
opts.ext_address = opts.rx_ext_address;
|
||||
opts.rx_ext_address = tmpext;
|
||||
}
|
||||
|
||||
if (setsockopt(t, SOL_CAN_ISOTP, CAN_ISOTP_OPTS, &opts, sizeof(opts)) < 0) {
|
||||
perror("setsockopt");
|
||||
r = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((llopts.mtu) && (setsockopt(t, SOL_CAN_ISOTP, CAN_ISOTP_LL_OPTS, &llopts, sizeof(llopts))) < 0) {
|
||||
perror("setsockopt");
|
||||
r = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
addr.can_addr.tp.tx_id = dst;
|
||||
addr.can_addr.tp.rx_id = src;
|
||||
|
||||
if (bind(t, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror("bind");
|
||||
r = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
while (!quit) {
|
||||
|
||||
FD_ZERO(&rdfs);
|
||||
FD_SET(s, &rdfs);
|
||||
FD_SET(t, &rdfs);
|
||||
FD_SET(0, &rdfs);
|
||||
|
||||
if ((nbytes = select(t+1, &rdfs, NULL, NULL, NULL)) < 0) {
|
||||
perror("select");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (FD_ISSET(0, &rdfs)) {
|
||||
getchar();
|
||||
quit = 1;
|
||||
printf("quit due to keyboard input.\n");
|
||||
}
|
||||
|
||||
if (FD_ISSET(s, &rdfs)) {
|
||||
nbytes = read(s, buffer, 4096);
|
||||
if (nbytes < 0) {
|
||||
perror("read socket s");
|
||||
r = 1;
|
||||
goto out;
|
||||
}
|
||||
if (nbytes > 4095) {
|
||||
r = 1;
|
||||
goto out;
|
||||
}
|
||||
printbuf(buffer, nbytes, color?2:0, timestamp, format,
|
||||
&tv, &last_tv, dst, s, if_name, head);
|
||||
}
|
||||
|
||||
if (FD_ISSET(t, &rdfs)) {
|
||||
nbytes = read(t, buffer, 4096);
|
||||
if (nbytes < 0) {
|
||||
perror("read socket t");
|
||||
r = 1;
|
||||
goto out;
|
||||
}
|
||||
if (nbytes > 4095) {
|
||||
r = 1;
|
||||
goto out;
|
||||
}
|
||||
printbuf(buffer, nbytes, color?1:0, timestamp, format,
|
||||
&tv, &last_tv, src, t, if_name, head);
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
if (s != -1)
|
||||
close(s);
|
||||
if (t != -1)
|
||||
close(t);
|
||||
|
||||
return r;
|
||||
}
|
||||
413
Devices/Libraries/Systems/can-utils/isotptun.c
Executable file
413
Devices/Libraries/Systems/can-utils/isotptun.c
Executable file
@@ -0,0 +1,413 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
|
||||
/*
|
||||
* isotptun.c - IP over CAN ISO-TP (ISO15765-2) tunnel / proof-of-concept
|
||||
*
|
||||
* This program creates a Linux tunnel netdevice 'ctunX' and transfers the
|
||||
* ethernet frames inside ISO15765-2 (unreliable) datagrams on CAN.
|
||||
*
|
||||
* Use e.g. "ifconfig ctun0 123.123.123.1 pointopoint 123.123.123.2 up"
|
||||
* to create a point-to-point IP connection on CAN.
|
||||
*
|
||||
* Copyright (c) 2008 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* Send feedback to <linux-can@vger.kernel.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <libgen.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/isotp.h>
|
||||
#include <linux/if_tun.h>
|
||||
|
||||
/* Change this to whatever your daemon is called */
|
||||
#define DAEMON_NAME "isotptun"
|
||||
|
||||
#define NO_CAN_ID 0xFFFFFFFFU
|
||||
#define DEFAULT_NAME "ctun%d"
|
||||
|
||||
/* stay on 4095 bytes for the max. PDU length which is still much more than the standard ethernet MTU */
|
||||
#define MAX_PDU_LENGTH 4095
|
||||
#define BUF_LEN (MAX_PDU_LENGTH + 1)
|
||||
|
||||
static volatile int running = 1;
|
||||
static volatile sig_atomic_t signal_num;
|
||||
|
||||
static void fake_syslog(int priority, const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
fprintf(stderr, "[%d] ", priority);
|
||||
va_start(ap, format);
|
||||
vfprintf(stderr, format, ap);
|
||||
va_end(ap);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
typedef void (*syslog_t)(int priority, const char *format, ...);
|
||||
static syslog_t syslogger = syslog;
|
||||
|
||||
void perror_syslog(const char *s)
|
||||
{
|
||||
const char *colon = s ? ": " : "";
|
||||
syslogger(LOG_ERR, "%s%s%s", s, colon, strerror(errno));
|
||||
}
|
||||
|
||||
void print_usage(char *prg)
|
||||
{
|
||||
fprintf(stderr, "%s - IP over CAN ISO-TP (ISO15765-2) tunnel / proof-of-concept.\n", prg);
|
||||
fprintf(stderr, "\nUsage: %s [options] <CAN interface>\n\n", prg);
|
||||
fprintf(stderr, "This program creates a Linux tunnel netdevice 'ctunX' and transfers the\n");
|
||||
fprintf(stderr, "ethernet frames inside ISO15765-2 (unreliable) datagrams on CAN.\n\n");
|
||||
fprintf(stderr, "Options:\n");
|
||||
fprintf(stderr, " -s <can_id> (source can_id. Use 8 digits for extended IDs)\n");
|
||||
fprintf(stderr, " -d <can_id> (destination can_id. Use 8 digits for extended IDs)\n");
|
||||
fprintf(stderr, " -n <name> (name of created IP netdevice. Default: '%s')\n", DEFAULT_NAME);
|
||||
fprintf(stderr, " -x <addr>[:<rxaddr>] (extended addressing / opt. separate rxaddr)\n");
|
||||
fprintf(stderr, " -L <mtu>:<tx_dl>:<tx_flags> (link layer options for CAN FD)\n");
|
||||
fprintf(stderr, " -p [tx]:[rx] (set and enable tx/rx padding bytes)\n");
|
||||
fprintf(stderr, " -P <mode> (check rx padding for (l)ength (c)ontent (a)ll)\n");
|
||||
fprintf(stderr, " -t <time ns> (transmit time in nanosecs)\n");
|
||||
fprintf(stderr, " -b <bs> (blocksize. 0 = off)\n");
|
||||
fprintf(stderr, " -m <val> (STmin in ms/ns. See spec.)\n");
|
||||
fprintf(stderr, " -w <num> (max. wait frame transmissions.)\n");
|
||||
fprintf(stderr, " -D (daemonize to background when tun device created)\n");
|
||||
fprintf(stderr, " -h (half duplex mode.)\n");
|
||||
fprintf(stderr, " -v (verbose mode. Print symbols for tunneled msgs.)\n");
|
||||
fprintf(stderr, "\nCAN IDs and addresses are given and expected in hexadecimal values.\n");
|
||||
fprintf(stderr, "Use e.g. 'ifconfig ctun0 123.123.123.1 pointopoint 123.123.123.2 up'\n");
|
||||
fprintf(stderr, "to create a point-to-point IP connection on CAN.\n");
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
void sigterm(int signo)
|
||||
{
|
||||
running = 0;
|
||||
signal_num = signo;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
fd_set rdfs;
|
||||
int s, t;
|
||||
struct sockaddr_can addr;
|
||||
struct ifreq ifr;
|
||||
static struct can_isotp_options opts;
|
||||
static struct can_isotp_fc_options fcopts;
|
||||
static struct can_isotp_ll_options llopts;
|
||||
int opt, ret;
|
||||
extern int optind, opterr, optopt;
|
||||
static int verbose;
|
||||
unsigned char buffer[BUF_LEN];
|
||||
static char name[sizeof(ifr.ifr_name)] = DEFAULT_NAME;
|
||||
int nbytes;
|
||||
int run_as_daemon = 0;
|
||||
|
||||
addr.can_addr.tp.tx_id = addr.can_addr.tp.rx_id = NO_CAN_ID;
|
||||
|
||||
while ((opt = getopt(argc, argv, "s:d:n:x:p:P:t:b:m:whL:vD?")) != -1) {
|
||||
switch (opt) {
|
||||
case 's':
|
||||
addr.can_addr.tp.tx_id = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
addr.can_addr.tp.tx_id |= CAN_EFF_FLAG;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
addr.can_addr.tp.rx_id = strtoul(optarg, NULL, 16);
|
||||
if (strlen(optarg) > 7)
|
||||
addr.can_addr.tp.rx_id |= CAN_EFF_FLAG;
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
if (strlen(optarg) > sizeof(name) - 1) {
|
||||
print_usage(basename(argv[0]));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
/* ensure string termination */
|
||||
memset(name, 0, sizeof(name));
|
||||
strncpy(name, optarg, sizeof(name) - 1);
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
{
|
||||
int elements = sscanf(optarg, "%hhx:%hhx",
|
||||
&opts.ext_address,
|
||||
&opts.rx_ext_address);
|
||||
|
||||
if (elements == 1)
|
||||
opts.flags |= CAN_ISOTP_EXTEND_ADDR;
|
||||
else if (elements == 2)
|
||||
opts.flags |= (CAN_ISOTP_EXTEND_ADDR | CAN_ISOTP_RX_EXT_ADDR);
|
||||
else {
|
||||
fprintf(stderr, "incorrect extended addr values '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'p':
|
||||
{
|
||||
int elements = sscanf(optarg, "%hhx:%hhx",
|
||||
&opts.txpad_content,
|
||||
&opts.rxpad_content);
|
||||
|
||||
if (elements == 1)
|
||||
opts.flags |= CAN_ISOTP_TX_PADDING;
|
||||
else if (elements == 2)
|
||||
opts.flags |= (CAN_ISOTP_TX_PADDING | CAN_ISOTP_RX_PADDING);
|
||||
else if (sscanf(optarg, ":%hhx", &opts.rxpad_content) == 1)
|
||||
opts.flags |= CAN_ISOTP_RX_PADDING;
|
||||
else {
|
||||
fprintf(stderr, "incorrect padding values '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'P':
|
||||
if (optarg[0] == 'l')
|
||||
opts.flags |= CAN_ISOTP_CHK_PAD_LEN;
|
||||
else if (optarg[0] == 'c')
|
||||
opts.flags |= CAN_ISOTP_CHK_PAD_DATA;
|
||||
else if (optarg[0] == 'a')
|
||||
opts.flags |= (CAN_ISOTP_CHK_PAD_LEN | CAN_ISOTP_CHK_PAD_DATA);
|
||||
else {
|
||||
fprintf(stderr, "unknown padding check option '%c'.\n", optarg[0]);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
|
||||
case 't':
|
||||
opts.frame_txtime = strtoul(optarg, NULL, 10);
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
fcopts.bs = strtoul(optarg, NULL, 16) & 0xFF;
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
fcopts.stmin = strtoul(optarg, NULL, 16) & 0xFF;
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
fcopts.wftmax = strtoul(optarg, NULL, 16) & 0xFF;
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
opts.flags |= CAN_ISOTP_HALF_DUPLEX;
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
if (sscanf(optarg, "%hhu:%hhu:%hhu",
|
||||
&llopts.mtu,
|
||||
&llopts.tx_dl,
|
||||
&llopts.tx_flags) != 3) {
|
||||
fprintf(stderr, "unknown link layer options '%s'.\n", optarg);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
verbose = 1;
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
run_as_daemon = 1;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
print_usage(basename(argv[0]));
|
||||
exit(EXIT_SUCCESS);
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unknown option %c\n", opt);
|
||||
print_usage(basename(argv[0]));
|
||||
exit(EXIT_FAILURE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((argc - optind != 1) ||
|
||||
(addr.can_addr.tp.tx_id == NO_CAN_ID) ||
|
||||
(addr.can_addr.tp.rx_id == NO_CAN_ID)) {
|
||||
print_usage(basename(argv[0]));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (!run_as_daemon)
|
||||
syslogger = fake_syslog;
|
||||
|
||||
/* Initialize the logging interface */
|
||||
openlog(DAEMON_NAME, LOG_PID, LOG_LOCAL5);
|
||||
|
||||
if ((s = socket(PF_CAN, SOCK_DGRAM, CAN_ISOTP)) < 0) {
|
||||
perror_syslog("socket");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_OPTS, &opts, sizeof(opts));
|
||||
setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_RECV_FC, &fcopts, sizeof(fcopts));
|
||||
|
||||
if (llopts.tx_dl) {
|
||||
if (setsockopt(s, SOL_CAN_ISOTP, CAN_ISOTP_LL_OPTS, &llopts, sizeof(llopts)) < 0) {
|
||||
perror_syslog("link layer sockopt");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
addr.can_family = AF_CAN;
|
||||
addr.can_ifindex = if_nametoindex(argv[optind]);
|
||||
if (!addr.can_ifindex) {
|
||||
perror_syslog("if_nametoindex");
|
||||
close(s);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror_syslog("bind");
|
||||
close(s);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if ((t = open("/dev/net/tun", O_RDWR)) < 0) {
|
||||
perror_syslog("open tunfd");
|
||||
close(s);
|
||||
close(t);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
|
||||
/* string termination is ensured at commandline option handling */
|
||||
strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
|
||||
|
||||
if (ioctl(t, TUNSETIFF, (void *) &ifr) < 0) {
|
||||
perror_syslog("ioctl tunfd");
|
||||
close(s);
|
||||
close(t);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Now the tun device exists. We can daemonize to let the
|
||||
* parent continue and use the network interface. */
|
||||
if (run_as_daemon) {
|
||||
if (daemon(0, 0)) {
|
||||
syslogger(LOG_ERR, "failed to daemonize");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
signal(SIGTERM, sigterm);
|
||||
signal(SIGHUP, sigterm);
|
||||
signal(SIGINT, sigterm);
|
||||
|
||||
while (running) {
|
||||
|
||||
FD_ZERO(&rdfs);
|
||||
FD_SET(s, &rdfs);
|
||||
FD_SET(t, &rdfs);
|
||||
|
||||
if ((ret = select(t+1, &rdfs, NULL, NULL, NULL)) < 0) {
|
||||
perror_syslog("select");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (FD_ISSET(s, &rdfs)) {
|
||||
nbytes = read(s, buffer, BUF_LEN);
|
||||
if (nbytes < 0) {
|
||||
perror_syslog("read isotp socket");
|
||||
return -1;
|
||||
}
|
||||
if (nbytes > MAX_PDU_LENGTH)
|
||||
return -1;
|
||||
ret = write(t, buffer, nbytes);
|
||||
if (verbose) {
|
||||
if (ret < 0 && errno == EAGAIN)
|
||||
printf(";");
|
||||
else
|
||||
printf(",");
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
if (FD_ISSET(t, &rdfs)) {
|
||||
nbytes = read(t, buffer, BUF_LEN);
|
||||
if (nbytes < 0) {
|
||||
perror_syslog("read tunfd");
|
||||
return -1;
|
||||
}
|
||||
if (nbytes > MAX_PDU_LENGTH)
|
||||
return -1;
|
||||
ret = write(s, buffer, nbytes);
|
||||
if (verbose) {
|
||||
if (ret < 0 && errno == EAGAIN)
|
||||
printf(":");
|
||||
else
|
||||
printf(".");
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(s);
|
||||
close(t);
|
||||
|
||||
if (signal_num)
|
||||
return 128 + signal_num;
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
500
Devices/Libraries/Systems/can-utils/j1939_timedate/j1939_timedate_cli.c
Executable file
500
Devices/Libraries/Systems/can-utils/j1939_timedate/j1939_timedate_cli.c
Executable file
@@ -0,0 +1,500 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2024 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <net/if.h>
|
||||
#include <poll.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "j1939_timedate_cmn.h"
|
||||
|
||||
#define J1939_TIMEDATE_CLI_MAX_EPOLL_EVENTS 10
|
||||
|
||||
struct j1939_timedate_cli_priv {
|
||||
int sock_nack;
|
||||
int sock_main;
|
||||
|
||||
struct sockaddr_can sockname;
|
||||
struct sockaddr_can peername;
|
||||
|
||||
struct j1939_timedate_stats stats;
|
||||
|
||||
struct libj1939_cmn cmn;
|
||||
struct timespec wait_until_time;
|
||||
|
||||
bool utc;
|
||||
bool broadcast;
|
||||
bool done;
|
||||
};
|
||||
|
||||
static void print_time_date_packet(struct j1939_timedate_cli_priv *priv,
|
||||
const struct j1939_timedate_msg *msg)
|
||||
{
|
||||
const struct j1939_time_date_packet *tdp =
|
||||
(const struct j1939_time_date_packet *)msg->buf;
|
||||
char timezone_offset[] = "+00:00 (Local Time)";
|
||||
char time_buffer[64];
|
||||
int actual_hour, actual_minute;
|
||||
int actual_year, actual_month;
|
||||
double actual_seconds;
|
||||
double actual_day;
|
||||
|
||||
if (msg->len < (int)sizeof(*tdp)) {
|
||||
pr_warn("received too short time and date packet: %zi",
|
||||
msg->len);
|
||||
return;
|
||||
}
|
||||
|
||||
actual_year = 1985 + tdp->year;
|
||||
actual_month = tdp->month;
|
||||
actual_day = tdp->day * 0.25;
|
||||
actual_hour = tdp->hours;
|
||||
actual_minute = tdp->minutes;
|
||||
actual_seconds = tdp->seconds * 0.25;
|
||||
|
||||
if (tdp->local_hour_offset == 125) {
|
||||
snprintf(timezone_offset, sizeof(timezone_offset),
|
||||
"+00:00 (Local Time)");
|
||||
} else if (!priv->utc) {
|
||||
actual_hour += tdp->local_hour_offset;
|
||||
actual_minute += tdp->local_minute_offset;
|
||||
|
||||
/* Wraparound for hours and minutes if needed */
|
||||
if (actual_minute >= 60) {
|
||||
actual_minute -= 60;
|
||||
actual_hour++;
|
||||
} else if (actual_minute < 0) {
|
||||
actual_minute += 60;
|
||||
actual_hour--;
|
||||
}
|
||||
|
||||
if (actual_hour >= 24) {
|
||||
actual_hour -= 24;
|
||||
actual_day++;
|
||||
} else if (actual_hour < 0) {
|
||||
actual_hour += 24;
|
||||
actual_day--;
|
||||
}
|
||||
|
||||
snprintf(timezone_offset, sizeof(timezone_offset), "%+03d:%02d",
|
||||
tdp->local_hour_offset, abs(tdp->local_minute_offset));
|
||||
} else {
|
||||
snprintf(timezone_offset, sizeof(timezone_offset),
|
||||
"+00:00 (UTC)");
|
||||
}
|
||||
|
||||
snprintf(time_buffer, sizeof(time_buffer),
|
||||
"%d-%02d-%02.0f %02d:%02d:%05.2f%.20s",
|
||||
actual_year, actual_month, actual_day, actual_hour,
|
||||
actual_minute, actual_seconds, timezone_offset);
|
||||
|
||||
printf("SA: 0x%02X, NAME: 0x%016llX, Time: %s\n",
|
||||
msg->peername.can_addr.j1939.addr,
|
||||
msg->peername.can_addr.j1939.name, time_buffer);
|
||||
|
||||
if (!priv->broadcast)
|
||||
priv->done = true;
|
||||
}
|
||||
|
||||
static int j1939_timedate_cli_rx_buf(struct j1939_timedate_cli_priv *priv,
|
||||
struct j1939_timedate_msg *msg)
|
||||
{
|
||||
pgn_t pgn = msg->peername.can_addr.j1939.pgn;
|
||||
int ret = 0;
|
||||
|
||||
switch (pgn) {
|
||||
case J1939_PGN_TD:
|
||||
print_time_date_packet(priv, msg);
|
||||
break;
|
||||
default:
|
||||
pr_warn("%s: unsupported PGN: %x", __func__, pgn);
|
||||
/* Not a critical error */
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int j1939_timedate_cli_rx_one(struct j1939_timedate_cli_priv *priv,
|
||||
int sock)
|
||||
{
|
||||
struct j1939_timedate_msg *msg;
|
||||
int flags = 0;
|
||||
int ret;
|
||||
|
||||
msg = malloc(sizeof(*msg));
|
||||
if (!msg) {
|
||||
pr_err("can't allocate rx msg struct\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
msg->buf_size = J1939_TIMEDATE_MAX_TRANSFER_LENGH;
|
||||
msg->peer_addr_len = sizeof(msg->peername);
|
||||
msg->sock = sock;
|
||||
|
||||
ret = recvfrom(sock, &msg->buf[0], msg->buf_size, flags,
|
||||
(struct sockaddr *)&msg->peername, &msg->peer_addr_len);
|
||||
|
||||
if (ret < 0) {
|
||||
ret = -errno;
|
||||
pr_warn("recvfrom() failed: %i %s", ret, strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ret < 3) {
|
||||
pr_warn("received too short message: %i", ret);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
msg->len = ret;
|
||||
|
||||
ret = j1939_timedate_cli_rx_buf(priv, msg);
|
||||
if (ret < 0) {
|
||||
pr_warn("failed to process rx buf: %i (%s)\n", ret,
|
||||
strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int j1939_timedate_cli_handle_events(struct j1939_timedate_cli_priv *priv,
|
||||
unsigned int nfds)
|
||||
{
|
||||
int ret;
|
||||
unsigned int n;
|
||||
|
||||
for (n = 0; n < nfds && n < priv->cmn.epoll_events_size; ++n) {
|
||||
struct epoll_event *ev = &priv->cmn.epoll_events[n];
|
||||
|
||||
if (!ev->events) {
|
||||
warn("no events");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ev->events & POLLIN) {
|
||||
ret = j1939_timedate_cli_rx_one(priv, ev->data.fd);
|
||||
if (ret) {
|
||||
warn("recv one");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int j1939_timedate_cli_process_events_and_tasks(struct j1939_timedate_cli_priv *priv)
|
||||
{
|
||||
int ret, nfds;
|
||||
|
||||
ret = libj1939_prepare_for_events(&priv->cmn, &nfds, false);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (nfds > 0) {
|
||||
ret = j1939_timedate_cli_handle_events(priv, nfds);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int j1939_timedate_cli_send_req(struct j1939_timedate_cli_priv *priv)
|
||||
{
|
||||
struct sockaddr_can addr = priv->peername;
|
||||
uint8_t data[3] = {0};
|
||||
int ret;
|
||||
|
||||
addr.can_addr.j1939.pgn = J1939_PGN_REQUEST_PGN;
|
||||
|
||||
data[0] = J1939_PGN_TD & 0xff;
|
||||
data[1] = (J1939_PGN_TD >> 8) & 0xff;
|
||||
data[2] = (J1939_PGN_TD >> 16) & 0xff;
|
||||
|
||||
ret = sendto(priv->sock_main, data, sizeof(data), 0,
|
||||
(struct sockaddr *)&addr, sizeof(addr));
|
||||
if (ret == -1) {
|
||||
ret = -errno;
|
||||
pr_warn("failed to send data: %i (%s)", ret, strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int j1939_timedate_cli_sock_main_prepare(struct j1939_timedate_cli_priv *priv)
|
||||
{
|
||||
struct sockaddr_can addr = priv->sockname;
|
||||
int ret;
|
||||
|
||||
ret = libj1939_open_socket();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
priv->sock_main = ret;
|
||||
|
||||
ret = libj1939_bind_socket(priv->sock_main, &addr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = libj1939_socket_prio(priv->sock_main,
|
||||
J1939_TIMEDATE_PRIO_DEFAULT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = libj1939_set_broadcast(priv->sock_main);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return libj1939_add_socket_to_epoll(priv->cmn.epoll_fd,
|
||||
priv->sock_main, EPOLLIN);
|
||||
}
|
||||
|
||||
static int j1939_timedate_cli_sock_nack_prepare(struct j1939_timedate_cli_priv *priv)
|
||||
{
|
||||
struct sockaddr_can addr = priv->sockname;
|
||||
int ret;
|
||||
|
||||
ret = libj1939_open_socket();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
priv->sock_nack = ret;
|
||||
|
||||
addr.can_addr.j1939.pgn = ISOBUS_PGN_ACK;
|
||||
ret = libj1939_bind_socket(priv->sock_nack, &addr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return libj1939_add_socket_to_epoll(priv->cmn.epoll_fd,
|
||||
priv->sock_nack, EPOLLIN);
|
||||
}
|
||||
|
||||
static int j1939_timedate_cli_sock_prepare(struct j1939_timedate_cli_priv *priv)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = libj1939_create_epoll();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
priv->cmn.epoll_fd = ret;
|
||||
|
||||
priv->cmn.epoll_events = calloc(J1939_TIMEDATE_CLI_MAX_EPOLL_EVENTS,
|
||||
sizeof(struct epoll_event));
|
||||
if (!priv->cmn.epoll_events)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->cmn.epoll_events_size = J1939_TIMEDATE_CLI_MAX_EPOLL_EVENTS;
|
||||
|
||||
ret = j1939_timedate_cli_sock_main_prepare(priv);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return j1939_timedate_cli_sock_nack_prepare(priv);
|
||||
}
|
||||
|
||||
static void j1939_timedate_cli_print_help(void)
|
||||
{
|
||||
printf("Usage: j1939_timedate-cli [options]\n");
|
||||
printf("Options:\n");
|
||||
printf(" --interface <interface_name> or -i <interface_name>\n");
|
||||
printf(" Specifies the CAN interface to use (mandatory).\n");
|
||||
printf(" --local-address <local_address_hex> or -a <local_address_hex>\n");
|
||||
printf(" Specifies the local address in hexadecimal (mandatory if local name is not provided).\n");
|
||||
printf(" --local-name <local_name_hex> or -n <local_name_hex>\n");
|
||||
printf(" Specifies the local NAME in hexadecimal (mandatory if local address is not provided).\n");
|
||||
printf(" --remote-address <remote_address_hex> or -r <remote_address_hex>\n");
|
||||
printf(" Specifies the remote address in hexadecimal (optional).\n");
|
||||
printf(" --remote-name <remote_name_hex> or -m <remote_name_hex>\n");
|
||||
printf(" Specifies the remote NAME in hexadecimal (optional).\n");
|
||||
printf(" --utc or -u\n");
|
||||
printf(" Outputs the time in UTC format.\n");
|
||||
printf("\n");
|
||||
printf("Note:\n");
|
||||
printf(" Local address and local name are mutually exclusive and one must be provided.\n");
|
||||
printf(" Remote address and remote name are mutually exclusive. \n");
|
||||
printf(" If no remote property is provided, the broadcast address will be used.\n");
|
||||
printf("\n");
|
||||
printf("Behavior:\n");
|
||||
printf(" In unicast mode (remote address or remote name provided),\n");
|
||||
printf(" the client will send a request and wait for the first response, then exit.\n");
|
||||
printf(" In broadcast mode (no remote address or remote name provided),\n");
|
||||
printf(" the program will wait 1000 milliseconds to collect responses, then exit.\n");
|
||||
printf("\n");
|
||||
printf("Time Output Formats:\n");
|
||||
printf(" YYYY-MM-DD HH:MM:SS.SS+00:00 (Local Time) - when no time zone information is received.\n");
|
||||
printf(" YYYY-MM-DD HH:MM:SS.SS+00:00 (UTC) - when the --utc option is used.\n");
|
||||
printf(" YYYY-MM-DD HH:MM:SS.SS+00:00 - default response with time zone offset automatically calculated.\n");
|
||||
printf("\n");
|
||||
printf("Complete Message Format:\n");
|
||||
printf(" The message will include the Source Address (SA) and J1939 NAME, formatted as follows:\n");
|
||||
printf(" SA: 0x60, NAME: 0x0000000000000000, Time: 2024-05-16 20:23:40.00+02:00\n");
|
||||
printf(" If the NAME is known, it will have a non-zero value.\n");
|
||||
printf("\n");
|
||||
printf("Usage Examples:\n");
|
||||
printf(" j1939acd -r 64-95 -c /tmp/1122334455667788.jacd 1122334455667788 vcan0 &\n");
|
||||
printf("\n");
|
||||
printf(" Broadcast mode:\n");
|
||||
printf(" j1939-timedate-cli -i vcan0 -a 0x80\n");
|
||||
printf("\n");
|
||||
printf(" Unicast mode:\n");
|
||||
printf(" j1939-timedate-cli -i vcan0 -a 0x80 -r 0x90\n");
|
||||
printf("\n");
|
||||
printf(" Using NAMEs instead of addresses:\n");
|
||||
printf(" j1939acd -r 64-95 -c /tmp/1122334455667788.jacd 1122334455667788 vcan0 &\n");
|
||||
printf(" j1939-timedate-cli -i vcan0 -n 0x1122334455667788\n");
|
||||
}
|
||||
|
||||
static int j1939_timedate_cli_parse_args(struct j1939_timedate_cli_priv *priv, int argc, char *argv[])
|
||||
{
|
||||
struct sockaddr_can *remote = &priv->peername;
|
||||
struct sockaddr_can *local = &priv->sockname;
|
||||
bool local_address_set = false;
|
||||
bool local_name_set = false;
|
||||
bool remote_address_set = false;
|
||||
bool remote_name_set = false;
|
||||
bool interface_set = false;
|
||||
int long_index = 0;
|
||||
int opt;
|
||||
|
||||
static struct option long_options[] = {
|
||||
{"interface", required_argument, 0, 'i'},
|
||||
{"local-address", required_argument, 0, 'a'},
|
||||
{"local-name", required_argument, 0, 'n'},
|
||||
{"remote-address", required_argument, 0, 'r'},
|
||||
{"remote-name", required_argument, 0, 'm'},
|
||||
{"utc", no_argument, 0, 'u'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
while ((opt = getopt_long(argc, argv, "a:n:r:m:i:u", long_options, &long_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 'a':
|
||||
local->can_addr.j1939.addr = strtoul(optarg, NULL, 16);
|
||||
local_address_set = true;
|
||||
break;
|
||||
case 'n':
|
||||
local->can_addr.j1939.name = strtoull(optarg, NULL, 16);
|
||||
local_name_set = true;
|
||||
break;
|
||||
case 'r':
|
||||
remote->can_addr.j1939.addr = strtoul(optarg, NULL, 16);
|
||||
remote_address_set = true;
|
||||
break;
|
||||
case 'm':
|
||||
remote->can_addr.j1939.name = strtoull(optarg, NULL, 16);
|
||||
remote_name_set = true;
|
||||
break;
|
||||
case 'i':
|
||||
local->can_ifindex = if_nametoindex(optarg);
|
||||
if (!local->can_ifindex) {
|
||||
pr_err("Interface %s not found. Error: %d (%s)\n",
|
||||
optarg, errno, strerror(errno));
|
||||
return -EINVAL;
|
||||
}
|
||||
remote->can_ifindex = local->can_ifindex;
|
||||
interface_set = true;
|
||||
break;
|
||||
case 'u':
|
||||
priv->utc = true;
|
||||
break;
|
||||
default:
|
||||
j1939_timedate_cli_print_help();
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!interface_set) {
|
||||
pr_err("interface not specified");
|
||||
j1939_timedate_cli_print_help();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((local_address_set && local_name_set) ||
|
||||
(remote_address_set && remote_name_set)) {
|
||||
pr_err("Local address and local name or remote address and remote name are mutually exclusive\n");
|
||||
j1939_timedate_cli_print_help();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!local_address_set && !local_name_set) {
|
||||
pr_err("Local address and local name not specified. One of them is required\n");
|
||||
j1939_timedate_cli_print_help();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* If no remote address is set, set it to broadcast */
|
||||
if (!remote_address_set && !remote_name_set) {
|
||||
remote->can_addr.j1939.addr = J1939_NO_ADDR;
|
||||
priv->broadcast = true;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct j1939_timedate_cli_priv *priv;
|
||||
struct timespec ts;
|
||||
int64_t time_diff;
|
||||
int ret;
|
||||
|
||||
priv = malloc(sizeof(*priv));
|
||||
if (!priv)
|
||||
err(EXIT_FAILURE, "can't allocate priv");
|
||||
|
||||
bzero(priv, sizeof(*priv));
|
||||
|
||||
libj1939_init_sockaddr_can(&priv->sockname, J1939_PGN_TD);
|
||||
libj1939_init_sockaddr_can(&priv->peername, J1939_PGN_REQUEST_PGN);
|
||||
|
||||
ret = j1939_timedate_cli_parse_args(priv, argc, argv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = j1939_timedate_cli_sock_prepare(priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
priv->cmn.next_send_time = ts;
|
||||
priv->wait_until_time = priv->cmn.next_send_time;
|
||||
/* Wait one second to collect all responses by default */
|
||||
timespec_add_ms(&priv->wait_until_time, 1000);
|
||||
|
||||
ret = j1939_timedate_cli_send_req(priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
while (1) {
|
||||
ret = j1939_timedate_cli_process_events_and_tasks(priv);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
if (priv->done)
|
||||
break;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
time_diff = timespec_diff_ms(&priv->wait_until_time, &ts);
|
||||
if (time_diff < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
close(priv->cmn.epoll_fd);
|
||||
free(priv->cmn.epoll_events);
|
||||
|
||||
close(priv->sock_main);
|
||||
close(priv->sock_nack);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
84
Devices/Libraries/Systems/can-utils/j1939_timedate/j1939_timedate_cmn.h
Executable file
84
Devices/Libraries/Systems/can-utils/j1939_timedate/j1939_timedate_cmn.h
Executable file
@@ -0,0 +1,84 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2024 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#ifndef _J1939_TIMEDATE_H_
|
||||
#define _J1939_TIMEDATE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <endian.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/epoll.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/kernel.h>
|
||||
#include "../libj1939.h"
|
||||
#include "../lib.h"
|
||||
|
||||
/* SAE J1939-71:2002 - 5.3 pgn54528 - Time/Date Adjust - TDA - */
|
||||
#define J1939_PGN_TDA 0x0d500 /* 54528 */
|
||||
/* SAE J1939-71:2002 - 5.3 pgn65254 - Time/Date - TD - */
|
||||
#define J1939_PGN_TD 0x0fee6 /* 65254 */
|
||||
|
||||
#define J1939_PGN_REQUEST_PGN 0x0ea00 /* 59904 */
|
||||
|
||||
/* ISO 11783-3:2018 - 5.4.5 Acknowledgment */
|
||||
#define ISOBUS_PGN_ACK 0x0e800 /* 59392 */
|
||||
|
||||
#define J1939_TIMEDATE_PRIO_DEFAULT 6
|
||||
|
||||
#define J1939_TIMEDATE_MAX_TRANSFER_LENGH 8
|
||||
|
||||
struct j1939_timedate_stats {
|
||||
int err;
|
||||
uint32_t tskey_sch;
|
||||
uint32_t tskey_ack;
|
||||
uint32_t send;
|
||||
};
|
||||
|
||||
struct j1939_timedate_msg {
|
||||
uint8_t buf[J1939_TIMEDATE_MAX_TRANSFER_LENGH];
|
||||
size_t buf_size;
|
||||
ssize_t len; /* length of received message */
|
||||
struct sockaddr_can peername;
|
||||
socklen_t peer_addr_len;
|
||||
int sock;
|
||||
};
|
||||
|
||||
struct j1939_timedate_err_msg {
|
||||
struct sock_extended_err *serr;
|
||||
struct scm_timestamping *tss;
|
||||
struct j1939_timedate_stats *stats;
|
||||
};
|
||||
|
||||
/*
|
||||
* struct time_date_packet - Represents the PGN 65254 Time/Date packet
|
||||
*
|
||||
* @seconds: Seconds since the last minute (0-59) with a scaling factor,
|
||||
* meaning each increment represents 0.25 seconds.
|
||||
* @minutes: Minutes since the last hour (0-59) with no scaling.
|
||||
* @hours: Hours since midnight (0-23) with no scaling.
|
||||
* @month: Current month (1-12) with no scaling.
|
||||
* @day: Day of the month with a scaling factor, each increment represents 0.25
|
||||
* day.
|
||||
* @year: Year offset since 1985, each increment represents one year.
|
||||
* @local_minute_offset: Offset in minutes from UTC, can range from -125 to 125
|
||||
* minutes.
|
||||
* @local_hour_offset: Offset in hours from UTC, can range from -125 to 125
|
||||
* hours.
|
||||
*
|
||||
* This structure defines each component of the Time/Date as described in
|
||||
* PGN 65254, using each byte to represent different components of the standard
|
||||
* UTC time and optionally adjusted local time based on offsets.
|
||||
*/
|
||||
struct j1939_time_date_packet {
|
||||
uint8_t seconds;
|
||||
uint8_t minutes;
|
||||
uint8_t hours;
|
||||
uint8_t month;
|
||||
uint8_t day;
|
||||
uint8_t year;
|
||||
int8_t local_minute_offset;
|
||||
int8_t local_hour_offset;
|
||||
};
|
||||
|
||||
#endif /* !_J1939_TIMEDATE_H_ */
|
||||
450
Devices/Libraries/Systems/can-utils/j1939_timedate/j1939_timedate_srv.c
Executable file
450
Devices/Libraries/Systems/can-utils/j1939_timedate/j1939_timedate_srv.c
Executable file
@@ -0,0 +1,450 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-only
|
||||
// SPDX-FileCopyrightText: 2024 Oleksij Rempel <linux@rempel-privat.de>
|
||||
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <net/if.h>
|
||||
#include <poll.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "j1939_timedate_cmn.h"
|
||||
|
||||
#define J1939_TIMEDATE_SRV_MAX_EPOLL_EVENTS 10
|
||||
|
||||
struct j1939_timedate_srv_priv {
|
||||
int sock_nack;
|
||||
int sock_main;
|
||||
|
||||
struct sockaddr_can sockname;
|
||||
|
||||
struct j1939_timedate_stats stats;
|
||||
|
||||
struct libj1939_cmn cmn;
|
||||
};
|
||||
|
||||
static void gmtime_to_j1939_pgn_65254_td(struct j1939_time_date_packet *tdp)
|
||||
{
|
||||
struct tm utc_tm_buf, local_tm_buf;
|
||||
int hour_offset, minute_offset;
|
||||
struct tm *utc_tm, *local_tm;
|
||||
int year_since_1985;
|
||||
time_t now;
|
||||
|
||||
now = time(NULL);
|
||||
utc_tm = gmtime_r(&now, &utc_tm_buf);
|
||||
local_tm = localtime_r(&now, &local_tm_buf);
|
||||
|
||||
/* Calculate the offsets */
|
||||
hour_offset = local_tm->tm_hour - utc_tm->tm_hour;
|
||||
minute_offset = local_tm->tm_min - utc_tm->tm_min;
|
||||
|
||||
/* Handle date rollover */
|
||||
if (local_tm->tm_mday != utc_tm->tm_mday) {
|
||||
if (local_tm->tm_hour < 12)
|
||||
hour_offset += 24; /* past midnight */
|
||||
else
|
||||
hour_offset -= 24; /* before midnight */
|
||||
}
|
||||
|
||||
/*
|
||||
* Seconds (spn959):
|
||||
* Resolution: 0.25 /bit, 0 offset
|
||||
* Data Range: 0 to 250 (0 to 62.5 seconds)
|
||||
* Operational Range: 0 to 239 (0 to 59.75 seconds)
|
||||
*/
|
||||
tdp->seconds = (uint8_t)(utc_tm->tm_sec / 0.25);
|
||||
if (tdp->seconds > 239)
|
||||
tdp->seconds = 239;
|
||||
|
||||
/*
|
||||
* Minutes (spn960):
|
||||
* Resolution: 1 min/bit, 0 offset
|
||||
* Data Range: 0 to 250 (0 to 250 minutes)
|
||||
* Operational Range: 0 to 59 (0 to 59 minutes)
|
||||
*/
|
||||
tdp->minutes = (uint8_t)utc_tm->tm_min;
|
||||
if (tdp->minutes > 59)
|
||||
tdp->minutes = 59;
|
||||
|
||||
/*
|
||||
* Hours (spn961):
|
||||
* Resolution: 1 hr/bit, 0 offset
|
||||
* Data Range: 0 to 250 (0 to 250 hours)
|
||||
* Operational Range: 0 to 23 (0 to 23 hours)
|
||||
*/
|
||||
tdp->hours = (uint8_t)utc_tm->tm_hour;
|
||||
if (tdp->hours > 23)
|
||||
tdp->hours = 23;
|
||||
|
||||
/*
|
||||
* Day (spn962):
|
||||
* Resolution: 0.25 /bit, 0 offset
|
||||
* Data Range: 0 to 250 (0 to 62.5 days)
|
||||
* Operational Range: 1 to 127 (0.25 to 31.75 days)
|
||||
*/
|
||||
tdp->day = (uint8_t)(utc_tm->tm_mday / 0.25);
|
||||
if (tdp->day < 1 || tdp->day > 127)
|
||||
tdp->day = 1;
|
||||
|
||||
/*
|
||||
* Month (spn963):
|
||||
* Resolution: 1 month/bit, 0 offset
|
||||
* Data Range: 0 to 250 (0 to 250 months)
|
||||
* Operational Range: 1 to 12 (1 to 12 months)
|
||||
*/
|
||||
tdp->month = (uint8_t)(utc_tm->tm_mon + 1);
|
||||
if (tdp->month < 1 || tdp->month > 12)
|
||||
tdp->month = 1;
|
||||
|
||||
/*
|
||||
* Year (spn964):
|
||||
* Resolution: 1 year/bit, 1985 offset
|
||||
* Data Range: 0 to 250 (1985 to 2235)
|
||||
* Operational Range: 0 to 250 (1985 to 2235)
|
||||
*/
|
||||
year_since_1985 = utc_tm->tm_year - 85;
|
||||
if (year_since_1985 < 0) {
|
||||
/* Fallback to year 1985 if year is before 1985 */
|
||||
tdp->year = 0;
|
||||
} else if (year_since_1985 > 250) {
|
||||
/* Fallback to year 2235 if year is beyond 2235 */
|
||||
tdp->year = 250;
|
||||
} else {
|
||||
tdp->year = (uint8_t)year_since_1985;
|
||||
}
|
||||
|
||||
/*
|
||||
* Local minute offset (spn1601):
|
||||
* Resolution: 1 min/bit, -125 offset
|
||||
* Data Range: -125 to 125 minutes
|
||||
* Operational Range: -59 to 59 minutes
|
||||
*/
|
||||
tdp->local_minute_offset = (int8_t)minute_offset;
|
||||
|
||||
/*
|
||||
* Local hour offset (spn1602):
|
||||
* Resolution: 1 hr/bit, -125 offset
|
||||
* Data Range: -125 to +125 hours
|
||||
* Operational Range: -24 to +23 hours
|
||||
* Note: If the local hour offset parameter is equal to 125 (0xFA),
|
||||
* the local time then the time parameter is the local time instead of
|
||||
* UTC.
|
||||
*/
|
||||
tdp->local_hour_offset = (int8_t)hour_offset;
|
||||
}
|
||||
|
||||
static int j1939_timedate_srv_send_res(struct j1939_timedate_srv_priv *priv,
|
||||
struct sockaddr_can *addr)
|
||||
{
|
||||
struct sockaddr_can peername = *addr;
|
||||
struct j1939_time_date_packet tdp;
|
||||
int ret;
|
||||
|
||||
gmtime_to_j1939_pgn_65254_td(&tdp);
|
||||
|
||||
peername.can_addr.j1939.pgn = J1939_PGN_TD;
|
||||
ret = sendto(priv->sock_main, &tdp, sizeof(tdp), 0,
|
||||
(struct sockaddr *)&peername, sizeof(peername));
|
||||
if (ret == -1) {
|
||||
ret = -errno;
|
||||
pr_warn("failed to send data: %i (%s)", ret, strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// check if the received message is a request for the time and date
|
||||
static int j1939_timedate_srv_process_request(struct j1939_timedate_srv_priv *priv,
|
||||
struct j1939_timedate_msg *msg)
|
||||
{
|
||||
|
||||
if (msg->buf[0] != (J1939_PGN_TD & 0xff) ||
|
||||
msg->buf[1] != ((J1939_PGN_TD >> 8) & 0xff) ||
|
||||
msg->buf[2] != ((J1939_PGN_TD >> 16) & 0xff)) {
|
||||
/* Don't care, not a time and date request */
|
||||
return 0;
|
||||
}
|
||||
|
||||
return j1939_timedate_srv_send_res(priv, &msg->peername);
|
||||
}
|
||||
|
||||
static int j1939_timedate_srv_rx_buf(struct j1939_timedate_srv_priv *priv, struct j1939_timedate_msg *msg)
|
||||
{
|
||||
pgn_t pgn = msg->peername.can_addr.j1939.pgn;
|
||||
int ret = 0;
|
||||
|
||||
switch (pgn) {
|
||||
case J1939_PGN_REQUEST_PGN:
|
||||
ret = j1939_timedate_srv_process_request(priv, msg);
|
||||
break;
|
||||
default:
|
||||
pr_warn("%s: unsupported PGN: %x", __func__, pgn);
|
||||
/* Not a critical error */
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int j1939_timedate_srv_rx_one(struct j1939_timedate_srv_priv *priv, int sock)
|
||||
{
|
||||
struct j1939_timedate_msg *msg;
|
||||
int flags = 0;
|
||||
int ret;
|
||||
|
||||
msg = malloc(sizeof(*msg));
|
||||
if (!msg) {
|
||||
pr_err("can't allocate rx msg struct\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
msg->buf_size = J1939_TIMEDATE_MAX_TRANSFER_LENGH;
|
||||
msg->peer_addr_len = sizeof(msg->peername);
|
||||
msg->sock = sock;
|
||||
|
||||
ret = recvfrom(sock, &msg->buf[0], msg->buf_size, flags,
|
||||
(struct sockaddr *)&msg->peername, &msg->peer_addr_len);
|
||||
|
||||
if (ret < 0) {
|
||||
ret = -errno;
|
||||
pr_warn("recvfrom() failed: %i %s", ret, strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ret < 3) {
|
||||
pr_warn("received too short message: %i", ret);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
msg->len = ret;
|
||||
|
||||
ret = j1939_timedate_srv_rx_buf(priv, msg);
|
||||
if (ret < 0) {
|
||||
pr_warn("failed to process rx buf: %i (%s)\n", ret,
|
||||
strerror(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int j1939_timedate_srv_handle_events(struct j1939_timedate_srv_priv *priv,
|
||||
unsigned int nfds)
|
||||
{
|
||||
int ret;
|
||||
unsigned int n;
|
||||
|
||||
for (n = 0; n < nfds && n < priv->cmn.epoll_events_size; ++n) {
|
||||
struct epoll_event *ev = &priv->cmn.epoll_events[n];
|
||||
|
||||
if (!ev->events) {
|
||||
warn("no events");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ev->events & POLLIN) {
|
||||
ret = j1939_timedate_srv_rx_one(priv, ev->data.fd);
|
||||
if (ret) {
|
||||
warn("recv one");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int j1939_timedate_srv_process_events_and_tasks(struct j1939_timedate_srv_priv *priv)
|
||||
{
|
||||
int ret, nfds;
|
||||
|
||||
ret = libj1939_prepare_for_events(&priv->cmn, &nfds, false);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (nfds > 0) {
|
||||
ret = j1939_timedate_srv_handle_events(priv, nfds);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int j1939_timedate_srv_sock_main_prepare(struct j1939_timedate_srv_priv *priv)
|
||||
{
|
||||
struct sockaddr_can addr = priv->sockname;
|
||||
int ret;
|
||||
|
||||
ret = libj1939_open_socket();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
priv->sock_main = ret;
|
||||
|
||||
ret = libj1939_bind_socket(priv->sock_main, &addr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = libj1939_socket_prio(priv->sock_main,
|
||||
J1939_TIMEDATE_PRIO_DEFAULT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = libj1939_set_broadcast(priv->sock_main);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
|
||||
return libj1939_add_socket_to_epoll(priv->cmn.epoll_fd,
|
||||
priv->sock_main, EPOLLIN);
|
||||
}
|
||||
|
||||
static int j1939_timedate_srv_sock_prepare(struct j1939_timedate_srv_priv *priv)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = libj1939_create_epoll();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
priv->cmn.epoll_fd = ret;
|
||||
|
||||
priv->cmn.epoll_events = calloc(J1939_TIMEDATE_SRV_MAX_EPOLL_EVENTS,
|
||||
sizeof(struct epoll_event));
|
||||
if (!priv->cmn.epoll_events)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->cmn.epoll_events_size = J1939_TIMEDATE_SRV_MAX_EPOLL_EVENTS;
|
||||
|
||||
return j1939_timedate_srv_sock_main_prepare(priv);
|
||||
}
|
||||
|
||||
static void j1939_timedate_srv_print_help(void)
|
||||
{
|
||||
printf("Usage: j1939-timedate-srv [options]\n");
|
||||
printf("Options:\n");
|
||||
printf(" --interface <interface_name> or -i <interface_name>\n");
|
||||
printf(" Specifies the CAN interface to use (mandatory).\n");
|
||||
printf(" --local-address <local_address_hex> or -a <local_address_hex>\n");
|
||||
printf(" Specifies the local address in hexadecimal (mandatory if\n");
|
||||
printf(" local name is not provided).\n");
|
||||
printf(" --local-name <local_name_hex> or -n <local_name_hex>\n");
|
||||
printf(" Specifies the local NAME in hexadecimal (mandatory if\n");
|
||||
printf(" local address is not provided).\n");
|
||||
printf("\n");
|
||||
printf("Note: Local address and local name are mutually exclusive and one\n");
|
||||
printf(" must be provided.\n");
|
||||
printf("\n");
|
||||
printf("Usage Examples:\n");
|
||||
printf(" Using local address:\n");
|
||||
printf(" j1939-timedate-srv -i vcan0 -a 0x90\n");
|
||||
printf("\n");
|
||||
printf(" Using local NAME:\n");
|
||||
printf(" j1939acd -r 64-95 -c /tmp/1122334455667789.jacd 1122334455667789 vcan0 &\n");
|
||||
printf(" j1939-timedate-srv -i vcan0 -n 0x1122334455667789\n");
|
||||
}
|
||||
|
||||
static int j1939_timedate_srv_parse_args(struct j1939_timedate_srv_priv *priv,
|
||||
int argc, char *argv[])
|
||||
{
|
||||
struct sockaddr_can *local = &priv->sockname;
|
||||
bool local_address_set = false;
|
||||
bool local_name_set = false;
|
||||
bool interface_set = false;
|
||||
int long_index = 0;
|
||||
int opt;
|
||||
|
||||
static struct option long_options[] = {
|
||||
{"interface", required_argument, 0, 'i'},
|
||||
{"local-address", required_argument, 0, 'a'},
|
||||
{"local-name", required_argument, 0, 'n'},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
while ((opt = getopt_long(argc, argv, "a:n:i:", long_options, &long_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 'a':
|
||||
local->can_addr.j1939.addr = strtoul(optarg, NULL, 16);
|
||||
local_address_set = true;
|
||||
break;
|
||||
case 'n':
|
||||
local->can_addr.j1939.name = strtoull(optarg, NULL, 16);
|
||||
local_name_set = true;
|
||||
break;
|
||||
case 'i':
|
||||
local->can_ifindex = if_nametoindex(optarg);
|
||||
if (!local->can_ifindex) {
|
||||
pr_err("Interface %s not found. Error: %d (%s)\n",
|
||||
optarg, errno, strerror(errno));
|
||||
return -EINVAL;
|
||||
}
|
||||
interface_set = true;
|
||||
break;
|
||||
default:
|
||||
j1939_timedate_srv_print_help();
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!interface_set) {
|
||||
pr_err("interface not specified");
|
||||
j1939_timedate_srv_print_help();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (local_address_set && local_name_set) {
|
||||
pr_err("local address and local name or remote address and remote name are mutually exclusive");
|
||||
j1939_timedate_srv_print_help();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct j1939_timedate_srv_priv *priv;
|
||||
struct timespec ts;
|
||||
int ret;
|
||||
|
||||
priv = malloc(sizeof(*priv));
|
||||
if (!priv)
|
||||
err(EXIT_FAILURE, "can't allocate priv");
|
||||
|
||||
bzero(priv, sizeof(*priv));
|
||||
|
||||
libj1939_init_sockaddr_can(&priv->sockname, J1939_PGN_REQUEST_PGN);
|
||||
|
||||
ret = j1939_timedate_srv_parse_args(priv, argc, argv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
priv->cmn.next_send_time = ts;
|
||||
|
||||
ret = j1939_timedate_srv_sock_prepare(priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
while (1) {
|
||||
ret = j1939_timedate_srv_process_events_and_tasks(priv);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
close(priv->cmn.epoll_fd);
|
||||
free(priv->cmn.epoll_events);
|
||||
|
||||
close(priv->sock_main);
|
||||
close(priv->sock_nack);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
642
Devices/Libraries/Systems/can-utils/j1939acd.c
Executable file
642
Devices/Libraries/Systems/can-utils/j1939acd.c
Executable file
@@ -0,0 +1,642 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2011 EIA Electronics
|
||||
*
|
||||
* Authors:
|
||||
* Kurt Van Dijck <kurt.van.dijck@eia.be>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the version 2 of the GNU General Public License
|
||||
* as published by the Free Software Foundation
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <err.h>
|
||||
#include <getopt.h>
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/j1939.h>
|
||||
#include <net/if.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "libj1939.h"
|
||||
|
||||
static const char help_msg[] =
|
||||
"j1939acd: An SAE J1939 address claiming daemon" "\n"
|
||||
"Usage: j1939acd [options] NAME [INTF]" "\n"
|
||||
"Options:\n"
|
||||
" -v, --verbose Increase verbosity" "\n"
|
||||
" -r, --range=RANGE Ranges of source addresses" "\n"
|
||||
" e.g. 80,50-100,200-210 (defaults to 0-253)" "\n"
|
||||
" -c, --cache=FILE Cache file to save/restore the source address" "\n"
|
||||
" -a, --address=ADDRESS Start with Source Address ADDRESS" "\n"
|
||||
"\n"
|
||||
"NAME is the 64bit nodename" "\n"
|
||||
"\n"
|
||||
"Examples:" "\n"
|
||||
"j1939acd -r 100,80-120 -c /tmp/1122334455667788.jacd 1122334455667788" "\n"
|
||||
"j1939acd -r 100,80-120 -c /tmp/1122334455667788.jacd 1122334455667788 vcan0" "\n"
|
||||
;
|
||||
|
||||
#ifdef _GNU_SOURCE
|
||||
static struct option long_opts[] = {
|
||||
{ "help", no_argument, NULL, '?', },
|
||||
{ "verbose", no_argument, NULL, 'v', },
|
||||
{ "range", required_argument, NULL, 'r', },
|
||||
{ "cache", required_argument, NULL, 'c', },
|
||||
{ "address", required_argument, NULL, 'a', },
|
||||
{ },
|
||||
};
|
||||
#else
|
||||
#define getopt_long(argc, argv, optstring, longopts, longindex) \
|
||||
getopt((argc), (argv), (optstring))
|
||||
#endif
|
||||
static const char optstring[] = "vr:c:a:?";
|
||||
|
||||
/* byte swap functions */
|
||||
static inline int host_is_little_endian(void)
|
||||
{
|
||||
static const uint16_t endian_test = 1;
|
||||
return *(const uint8_t *)&endian_test;
|
||||
}
|
||||
|
||||
static __attribute__((unused)) void bswap(void *vptr, int size)
|
||||
{
|
||||
uint8_t *p0, *pe;
|
||||
uint8_t tmp;
|
||||
|
||||
p0 = vptr;
|
||||
pe = &p0[size-1];
|
||||
for (; p0 < pe; ++p0, --pe) {
|
||||
tmp = *p0;
|
||||
*p0 = *pe;
|
||||
*pe = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
/* rate-limiting for errors */
|
||||
static inline int must_warn(int ret)
|
||||
{
|
||||
if (ret >= 0)
|
||||
return 0;
|
||||
switch (errno) {
|
||||
case EINTR:
|
||||
case ENOBUFS:
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* global variables */
|
||||
static char default_range[] = "0x80-0xfd";
|
||||
static const char default_intf[] = "can0";
|
||||
|
||||
static struct {
|
||||
int verbose;
|
||||
const char *cachefile;
|
||||
|
||||
const char *intf;
|
||||
char *ranges;
|
||||
uint64_t name;
|
||||
uint8_t current_sa;
|
||||
uint8_t last_sa;
|
||||
volatile sig_atomic_t signal_num;
|
||||
int sig_alrm;
|
||||
int sig_usr1;
|
||||
int state;
|
||||
#define STATE_INITIAL 0
|
||||
#define STATE_REQ_SENT 1
|
||||
#define STATE_REQ_PENDING 2 /* wait 1250 msec for first claim */
|
||||
#define STATE_OPERATIONAL 3
|
||||
} s = {
|
||||
.intf = default_intf,
|
||||
.ranges = default_range,
|
||||
.current_sa = J1939_IDLE_ADDR,
|
||||
.last_sa = J1939_NO_ADDR,
|
||||
};
|
||||
|
||||
struct {
|
||||
uint64_t name;
|
||||
int flags;
|
||||
#define F_USE 0x01
|
||||
#define F_SEEN 0x02
|
||||
} addr[J1939_IDLE_ADDR /* =254 */];
|
||||
|
||||
/* lookup by name */
|
||||
static int lookup_name(uint64_t name)
|
||||
{
|
||||
int j;
|
||||
|
||||
for (j = 0; j < J1939_IDLE_ADDR; ++j) {
|
||||
if (addr[j].name == name)
|
||||
return j;
|
||||
}
|
||||
return J1939_IDLE_ADDR;
|
||||
|
||||
}
|
||||
|
||||
/* parse address range */
|
||||
static int parse_range(char *str)
|
||||
{
|
||||
char *tok, *endp;
|
||||
int a0, ae;
|
||||
int j, cnt;
|
||||
|
||||
cnt = 0;
|
||||
for (tok = strtok(str, ",;"); tok; tok = strtok(NULL, ",;")) {
|
||||
a0 = ae = strtoul(tok, &endp, 0);
|
||||
if (endp <= tok)
|
||||
err(1, "parsing range '%s'", tok);
|
||||
if (*endp == '-') {
|
||||
tok = endp+1;
|
||||
ae = strtoul(tok, &endp, 0);
|
||||
if (endp <= tok)
|
||||
err(1, "parsing addr '%s'", tok);
|
||||
if (ae < a0)
|
||||
ae = a0;
|
||||
}
|
||||
for (j = a0; j <= ae; ++j, ++cnt) {
|
||||
if (j == J1939_IDLE_ADDR)
|
||||
break;
|
||||
addr[j].flags |= F_USE;
|
||||
}
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/* j1939 socket */
|
||||
static const struct j1939_filter filt[] = {
|
||||
{
|
||||
.pgn = J1939_PGN_ADDRESS_CLAIMED,
|
||||
.pgn_mask = J1939_PGN_PDU1_MAX,
|
||||
}, {
|
||||
.pgn = J1939_PGN_REQUEST,
|
||||
.pgn_mask = J1939_PGN_PDU1_MAX,
|
||||
}, {
|
||||
.pgn = J1939_PGN_ADDRESS_COMMANDED,
|
||||
.pgn_mask = J1939_PGN_MAX,
|
||||
},
|
||||
};
|
||||
|
||||
static int open_socket(const char *device, uint64_t name)
|
||||
{
|
||||
int ret, sock;
|
||||
int value;
|
||||
struct sockaddr_can saddr = {
|
||||
.can_family = AF_CAN,
|
||||
.can_addr.j1939 = {
|
||||
.name = name,
|
||||
.addr = J1939_IDLE_ADDR,
|
||||
.pgn = J1939_NO_PGN,
|
||||
},
|
||||
.can_ifindex = if_nametoindex(device),
|
||||
};
|
||||
|
||||
if (s.verbose)
|
||||
fprintf(stderr, "- socket(PF_CAN, SOCK_DGRAM, CAN_J1939);\n");
|
||||
sock = ret = socket(PF_CAN, SOCK_DGRAM, CAN_J1939);
|
||||
if (ret < 0)
|
||||
err(1, "socket(j1939)");
|
||||
|
||||
if (s.verbose)
|
||||
fprintf(stderr, "- setsockopt(, SOL_CAN_J1939, SO_J1939_FILTER, <filter>, %zd);\n", sizeof(filt));
|
||||
ret = setsockopt(sock, SOL_CAN_J1939, SO_J1939_FILTER,
|
||||
&filt, sizeof(filt));
|
||||
if (ret < 0)
|
||||
err(1, "setsockopt filter");
|
||||
|
||||
value = 1;
|
||||
if (s.verbose)
|
||||
fprintf(stderr, "- setsockopt(, SOL_SOCKET, SO_BROADCAST, %d, %zd);\n", value, sizeof(value));
|
||||
ret = setsockopt(sock, SOL_SOCKET, SO_BROADCAST,
|
||||
&value, sizeof(value));
|
||||
if (ret < 0)
|
||||
err(1, "setsockopt set broadcast");
|
||||
|
||||
if (s.verbose)
|
||||
fprintf(stderr, "- bind(, %s, %zi);\n", libj1939_addr2str(&saddr), sizeof(saddr));
|
||||
ret = bind(sock, (void *)&saddr, sizeof(saddr));
|
||||
if (ret < 0)
|
||||
err(1, "bind()");
|
||||
return sock;
|
||||
}
|
||||
|
||||
/* real IO function */
|
||||
static int repeat_address(int sock, uint64_t name)
|
||||
{
|
||||
int ret;
|
||||
uint8_t dat[8];
|
||||
static const struct sockaddr_can saddr = {
|
||||
.can_family = AF_CAN,
|
||||
.can_addr.j1939 = {
|
||||
.pgn = J1939_PGN_ADDRESS_CLAIMED,
|
||||
.addr = J1939_NO_ADDR,
|
||||
},
|
||||
};
|
||||
|
||||
memcpy(dat, &name, 8);
|
||||
if (!host_is_little_endian())
|
||||
bswap(dat, 8);
|
||||
if (s.verbose)
|
||||
fprintf(stderr, "- send(, %" PRId64 ", 8, 0);\n", name);
|
||||
ret = sendto(sock, dat, sizeof(dat), 0, (const struct sockaddr *)&saddr,
|
||||
sizeof(saddr));
|
||||
if (must_warn(ret))
|
||||
fprintf(stderr, "send address claim for 0x%02x\n", s.last_sa);
|
||||
return ret;
|
||||
}
|
||||
static int claim_address(int sock, uint64_t name, int sa)
|
||||
{
|
||||
int ret;
|
||||
struct sockaddr_can saddr = {
|
||||
.can_family = AF_CAN,
|
||||
.can_addr.j1939 = {
|
||||
.name = name,
|
||||
.addr = sa,
|
||||
.pgn = J1939_NO_PGN,
|
||||
},
|
||||
.can_ifindex = if_nametoindex(s.intf),
|
||||
};
|
||||
|
||||
if (s.verbose)
|
||||
fprintf(stderr, "- bind(, %s, %zi);\n", libj1939_addr2str(&saddr), sizeof(saddr));
|
||||
ret = bind(sock, (void *)&saddr, sizeof(saddr));
|
||||
if (ret < 0)
|
||||
err(1, "rebind with sa 0x%02x", sa);
|
||||
s.last_sa = sa;
|
||||
return repeat_address(sock, name);
|
||||
}
|
||||
|
||||
static int request_addresses(int sock)
|
||||
{
|
||||
static const uint8_t dat[3] = { 0, 0xee, 0, };
|
||||
int ret;
|
||||
static const struct sockaddr_can saddr = {
|
||||
.can_family = AF_CAN,
|
||||
.can_addr.j1939.pgn = J1939_PGN_REQUEST,
|
||||
.can_addr.j1939.addr = J1939_NO_ADDR,
|
||||
};
|
||||
|
||||
if (s.verbose)
|
||||
fprintf(stderr, "- sendto(, { 0, 0xee, 0, }, %zi, 0, %s, %zi);\n", sizeof(dat), libj1939_addr2str(&saddr), sizeof(saddr));
|
||||
ret = sendto(sock, dat, sizeof(dat), 0, (void *)&saddr, sizeof(saddr));
|
||||
if (must_warn(ret))
|
||||
fprintf(stdout, "send request for address claims");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* real policy */
|
||||
static int choose_new_sa(uint64_t name, int sa)
|
||||
{
|
||||
int j, cnt;
|
||||
|
||||
/* test current entry */
|
||||
if ((sa < J1939_IDLE_ADDR) && (addr[sa].flags & F_USE)) {
|
||||
j = sa;
|
||||
if (!addr[j].name || (addr[j].name == name) || (addr[j].name > name))
|
||||
return j;
|
||||
}
|
||||
/* take first empty spot */
|
||||
for (j = 0; j < J1939_IDLE_ADDR; ++j) {
|
||||
if (!(addr[j].flags & F_USE))
|
||||
continue;
|
||||
if (!addr[j].name || (addr[j].name == name))
|
||||
return j;
|
||||
}
|
||||
|
||||
/*
|
||||
* no empty spot found
|
||||
* take next (relative to @sa) spot that we can
|
||||
* successfully contest
|
||||
*/
|
||||
j = sa + 1;
|
||||
for (cnt = 0; cnt < J1939_IDLE_ADDR; ++j, ++cnt) {
|
||||
if (j >= J1939_IDLE_ADDR)
|
||||
j = 0;
|
||||
if (!(addr[j].flags & F_USE))
|
||||
continue;
|
||||
if (name < addr[j].name)
|
||||
return j;
|
||||
}
|
||||
return J1939_IDLE_ADDR;
|
||||
}
|
||||
|
||||
/* signa handling */
|
||||
static void sighandler(int sig, siginfo_t *info, void *vp)
|
||||
{
|
||||
switch (sig) {
|
||||
case SIGINT:
|
||||
case SIGTERM:
|
||||
s.signal_num = sig;
|
||||
break;
|
||||
case SIGALRM:
|
||||
s.sig_alrm = 1;
|
||||
break;
|
||||
case SIGUSR1:
|
||||
s.sig_usr1 = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void install_signal(int sig)
|
||||
{
|
||||
int ret;
|
||||
struct sigaction sigact = {
|
||||
.sa_sigaction = sighandler,
|
||||
.sa_flags = SA_SIGINFO,
|
||||
};
|
||||
|
||||
sigfillset(&sigact.sa_mask);
|
||||
ret = sigaction(sig, &sigact, NULL);
|
||||
if (ret < 0)
|
||||
err(1, "sigaction for signal %i", sig);
|
||||
}
|
||||
|
||||
static void schedule_itimer(int msec)
|
||||
{
|
||||
int ret;
|
||||
struct itimerval val = { 0 };
|
||||
|
||||
val.it_value.tv_sec = msec / 1000;
|
||||
val.it_value.tv_usec = (msec % 1000) * 1000;
|
||||
|
||||
s.sig_alrm = 0;
|
||||
do {
|
||||
ret = setitimer(ITIMER_REAL, &val, NULL);
|
||||
} while ((ret < 0) && (errno == EINTR));
|
||||
if (ret < 0)
|
||||
err(1, "setitimer %i msec", msec);
|
||||
}
|
||||
|
||||
/* dump status */
|
||||
static inline int addr_status_mine(int sa)
|
||||
{
|
||||
if (sa == s.current_sa)
|
||||
return '*';
|
||||
if (addr[sa].flags & F_USE)
|
||||
return '+';
|
||||
return '-';
|
||||
}
|
||||
|
||||
static void dump_status(void)
|
||||
{
|
||||
int j;
|
||||
|
||||
for (j = 0; j < J1939_IDLE_ADDR; ++j) {
|
||||
if (!addr[j].flags && !addr[j].name)
|
||||
continue;
|
||||
fprintf(stdout, "%02x: %c", j, addr_status_mine(j));
|
||||
if (addr[j].name)
|
||||
fprintf(stdout, " %016llx", (long long)addr[j].name);
|
||||
else
|
||||
fprintf(stdout, " -");
|
||||
fprintf(stdout, "\n");
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/* cache file */
|
||||
static void save_cache(void)
|
||||
{
|
||||
FILE *fp;
|
||||
time_t t;
|
||||
|
||||
if (!s.cachefile)
|
||||
return;
|
||||
fp = fopen(s.cachefile, "w");
|
||||
if (!fp)
|
||||
err(1, "fopen %s, w", s.cachefile);
|
||||
|
||||
time(&t);
|
||||
fprintf(fp, "# saved on %s\n", ctime(&t));
|
||||
fprintf(fp, "\n");
|
||||
fprintf(fp, "0x%02x\n", s.current_sa);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
static void restore_cache(void)
|
||||
{
|
||||
FILE *fp;
|
||||
int ret;
|
||||
char *endp;
|
||||
char *line = 0;
|
||||
size_t sz = 0;
|
||||
|
||||
if (!s.cachefile)
|
||||
return;
|
||||
fp = fopen(s.cachefile, "r");
|
||||
if (!fp) {
|
||||
if (ENOENT == errno)
|
||||
return;
|
||||
err(1, "fopen %s, r", s.cachefile);
|
||||
}
|
||||
while (!feof(fp)) {
|
||||
ret = getline(&line, &sz, fp);
|
||||
if (ret <= 0)
|
||||
continue;
|
||||
if (line[0] == '#')
|
||||
continue;
|
||||
ret = strtoul(line, &endp, 0);
|
||||
if ((endp > line) && (ret >= 0) && (ret <= J1939_IDLE_ADDR)) {
|
||||
s.current_sa = ret;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
if (line)
|
||||
free(line);
|
||||
}
|
||||
|
||||
/* main */
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int ret, sock, sock_rx, pgn, sa, opt;
|
||||
socklen_t slen;
|
||||
uint8_t dat[9];
|
||||
struct sockaddr_can saddr;
|
||||
uint64_t cmd_name;
|
||||
|
||||
/* argument parsing */
|
||||
while ((opt = getopt_long(argc, argv, optstring, long_opts, NULL)) != -1)
|
||||
switch (opt) {
|
||||
case 'v':
|
||||
++s.verbose;
|
||||
break;
|
||||
case 'c':
|
||||
s.cachefile = optarg;
|
||||
break;
|
||||
case 'r':
|
||||
s.ranges = optarg;
|
||||
break;
|
||||
case 'a':
|
||||
s.current_sa = strtoul(optarg, 0, 0);
|
||||
break;
|
||||
default:
|
||||
fputs(help_msg, stderr);
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (argv[optind])
|
||||
s.name = strtoull(argv[optind++], 0, 16);
|
||||
if (argv[optind])
|
||||
s.intf = argv[optind++];
|
||||
|
||||
/* args done */
|
||||
|
||||
restore_cache();
|
||||
|
||||
ret = parse_range(s.ranges);
|
||||
if (!ret)
|
||||
err(1, "no addresses in range");
|
||||
|
||||
if ((s.current_sa < J1939_IDLE_ADDR) && !(addr[s.current_sa].flags & F_USE)) {
|
||||
if (s.verbose)
|
||||
fprintf(stderr, "- forget saved address 0x%02x\n", s.current_sa);
|
||||
s.current_sa = J1939_IDLE_ADDR;
|
||||
}
|
||||
|
||||
if (s.verbose)
|
||||
fprintf(stderr, "- ready for %s:%016llx\n", s.intf, (long long)s.name);
|
||||
if (!s.intf || !s.name)
|
||||
err(1, "bad arguments");
|
||||
ret = sock = open_socket(s.intf, s.name);
|
||||
sock_rx = open_socket(s.intf, s.name);
|
||||
|
||||
install_signal(SIGTERM);
|
||||
install_signal(SIGINT);
|
||||
install_signal(SIGALRM);
|
||||
install_signal(SIGUSR1);
|
||||
install_signal(SIGUSR2);
|
||||
|
||||
while (!s.signal_num) {
|
||||
if (s.sig_usr1) {
|
||||
s.sig_usr1 = 0;
|
||||
dump_status();
|
||||
}
|
||||
switch (s.state) {
|
||||
case STATE_INITIAL:
|
||||
ret = request_addresses(sock);
|
||||
if (ret < 0)
|
||||
err(1, "could not sent initial request");
|
||||
s.state = STATE_REQ_SENT;
|
||||
break;
|
||||
case STATE_REQ_PENDING:
|
||||
if (!s.sig_alrm)
|
||||
break;
|
||||
s.sig_alrm = 0;
|
||||
/* claim addr */
|
||||
sa = choose_new_sa(s.name, s.current_sa);
|
||||
if (sa == J1939_IDLE_ADDR)
|
||||
err(1, "no free address to use");
|
||||
ret = claim_address(sock, s.name, sa);
|
||||
if (ret < 0)
|
||||
schedule_itimer(50);
|
||||
s.state = STATE_OPERATIONAL;
|
||||
break;
|
||||
case STATE_OPERATIONAL:
|
||||
if (s.sig_alrm) {
|
||||
s.sig_alrm = 0;
|
||||
ret = repeat_address(sock, s.name);
|
||||
if (ret < 0)
|
||||
schedule_itimer(50);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
slen = sizeof(saddr);
|
||||
ret = recvfrom(sock_rx, dat, sizeof(dat), 0, (void *)&saddr, &slen);
|
||||
if (ret < 0) {
|
||||
if (EINTR == errno)
|
||||
continue;
|
||||
err(1, "recvfrom()");
|
||||
}
|
||||
switch (saddr.can_addr.j1939.pgn) {
|
||||
case J1939_PGN_REQUEST:
|
||||
if (ret < 3)
|
||||
break;
|
||||
pgn = dat[0] + (dat[1] << 8) + ((dat[2] & 0x03) << 16);
|
||||
if (pgn != J1939_PGN_ADDRESS_CLAIMED)
|
||||
/* not interested */
|
||||
break;
|
||||
if (s.state == STATE_REQ_SENT) {
|
||||
if (s.verbose)
|
||||
fprintf(stderr, "- request sent, pending for 1250 ms\n");
|
||||
schedule_itimer(1250);
|
||||
s.state = STATE_REQ_PENDING;
|
||||
} else if (s.state == STATE_OPERATIONAL) {
|
||||
ret = claim_address(sock, s.name, s.current_sa);
|
||||
if (ret < 0)
|
||||
schedule_itimer(50);
|
||||
}
|
||||
break;
|
||||
case J1939_PGN_ADDRESS_CLAIMED:
|
||||
if (saddr.can_addr.j1939.addr >= J1939_IDLE_ADDR) {
|
||||
sa = lookup_name(saddr.can_addr.j1939.name);
|
||||
if (sa < J1939_IDLE_ADDR)
|
||||
addr[sa].name = 0;
|
||||
break;
|
||||
}
|
||||
sa = lookup_name(saddr.can_addr.j1939.name);
|
||||
if ((sa != saddr.can_addr.j1939.addr) && (sa < J1939_IDLE_ADDR))
|
||||
/* update cache */
|
||||
addr[sa].name = 0;
|
||||
|
||||
/* shortcut */
|
||||
sa = saddr.can_addr.j1939.addr;
|
||||
addr[sa].name = saddr.can_addr.j1939.name;
|
||||
addr[sa].flags |= F_SEEN;
|
||||
|
||||
if (s.name == saddr.can_addr.j1939.name) {
|
||||
/* ourselves, disable itimer */
|
||||
s.current_sa = sa;
|
||||
if (s.verbose)
|
||||
fprintf(stderr, "- claimed 0x%02x\n", sa);
|
||||
} else if (sa == s.current_sa) {
|
||||
if (s.verbose)
|
||||
fprintf(stderr, "- address collision for 0x%02x\n", sa);
|
||||
if (s.name > saddr.can_addr.j1939.name) {
|
||||
sa = choose_new_sa(s.name, sa);
|
||||
if (sa == J1939_IDLE_ADDR) {
|
||||
fprintf(stdout, "no address left");
|
||||
/* put J1939_IDLE_ADDR in cache file */
|
||||
s.current_sa = sa;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
ret = claim_address(sock, s.name, sa);
|
||||
if (ret < 0)
|
||||
schedule_itimer(50);
|
||||
}
|
||||
break;
|
||||
case J1939_PGN_ADDRESS_COMMANDED:
|
||||
if (!host_is_little_endian())
|
||||
bswap(dat, 8);
|
||||
memcpy(&cmd_name, dat, 8);
|
||||
if (cmd_name == s.name) {
|
||||
ret = claim_address(sock, s.name, dat[8]);
|
||||
if (ret < 0)
|
||||
schedule_itimer(50);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
done:
|
||||
if (s.verbose)
|
||||
fprintf(stderr, "- shutdown\n");
|
||||
claim_address(sock, s.name, J1939_IDLE_ADDR);
|
||||
save_cache();
|
||||
|
||||
if (s.signal_num)
|
||||
return 128 + s.signal_num;
|
||||
|
||||
return 0;
|
||||
}
|
||||
781
Devices/Libraries/Systems/can-utils/j1939cat.c
Executable file
781
Devices/Libraries/Systems/can-utils/j1939cat.c
Executable file
@@ -0,0 +1,781 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2018 Pengutronix, Oleksij Rempel <o.rempel@pengutronix.de>
|
||||
*/
|
||||
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <net/if.h>
|
||||
#include <poll.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <linux/errqueue.h>
|
||||
#include <linux/net_tstamp.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/socket.h>
|
||||
|
||||
#include "libj1939.h"
|
||||
|
||||
#define J1939_MAX_ETP_PACKET_SIZE (7 * 0x00ffffff)
|
||||
#define JCAT_BUF_SIZE (1000 * 1024)
|
||||
|
||||
/*
|
||||
* min()/max()/clamp() macros that also do
|
||||
* strict type-checking.. See the
|
||||
* "unnecessary" pointer comparison.
|
||||
*/
|
||||
#define min(x, y) ({ \
|
||||
typeof(x) _min1 = (x); \
|
||||
typeof(y) _min2 = (y); \
|
||||
(void) (&_min1 == &_min2); \
|
||||
_min1 < _min2 ? _min1 : _min2; })
|
||||
|
||||
|
||||
struct j1939cat_stats {
|
||||
int err;
|
||||
uint32_t tskey;
|
||||
uint32_t send;
|
||||
uint32_t total;
|
||||
uint32_t pgn;
|
||||
uint8_t sa;
|
||||
uint8_t da;
|
||||
uint64_t src_name;
|
||||
uint64_t dst_name;
|
||||
};
|
||||
|
||||
struct j1939cat_priv {
|
||||
int sock;
|
||||
int infile;
|
||||
int outfile;
|
||||
size_t max_transfer;
|
||||
unsigned long repeat;
|
||||
unsigned long round;
|
||||
int todo_prio;
|
||||
|
||||
bool valid_peername;
|
||||
bool todo_recv;
|
||||
bool todo_filesize;
|
||||
bool todo_connect;
|
||||
int todo_broadcast;
|
||||
|
||||
unsigned long polltimeout;
|
||||
|
||||
struct sockaddr_can sockname;
|
||||
struct sockaddr_can peername;
|
||||
|
||||
struct sock_extended_err *serr;
|
||||
struct scm_timestamping *tss;
|
||||
struct j1939cat_stats stats;
|
||||
int32_t last_dpo;
|
||||
};
|
||||
|
||||
static const char help_msg[] =
|
||||
"j1939cat: netcat-like tool for j1939\n"
|
||||
"Usage: j1939cat [options] FROM TO\n"
|
||||
" FROM / TO - or [IFACE][:[SA][,[PGN][,NAME]]]\n"
|
||||
"Options:\n"
|
||||
" -i <infile> (default stdin)\n"
|
||||
" -s <size> Set maximal transfer size. Default: 117440505 byte\n"
|
||||
" -r Receive data\n"
|
||||
" -P <timeout> poll timeout in milliseconds before sending data.\n"
|
||||
" With this option send() will be used with MSG_DONTWAIT flag.\n"
|
||||
" -R <count> Set send repeat count. Default: 1\n"
|
||||
" -B Allow to send and receive broadcast packets.\n"
|
||||
"\n"
|
||||
"Example:\n"
|
||||
"j1939cat -i some_file_to_send can0:0x80 :0x90,0x12300\n"
|
||||
"j1939cat can0:0x90 -r > /tmp/some_file_to_receive\n"
|
||||
"\n"
|
||||
;
|
||||
|
||||
static const char optstring[] = "?hi:vs:rp:P:R:B";
|
||||
|
||||
static ssize_t j1939cat_send_one(struct j1939cat_priv *priv, int out_fd,
|
||||
const void *buf, size_t buf_size)
|
||||
{
|
||||
ssize_t num_sent;
|
||||
int flags = 0;
|
||||
|
||||
if (priv->polltimeout)
|
||||
flags |= MSG_DONTWAIT;
|
||||
|
||||
if (priv->valid_peername && !priv->todo_connect)
|
||||
num_sent = sendto(out_fd, buf, buf_size, flags,
|
||||
(struct sockaddr *)&priv->peername,
|
||||
sizeof(priv->peername));
|
||||
else
|
||||
num_sent = send(out_fd, buf, buf_size, flags);
|
||||
|
||||
if (num_sent == -1) {
|
||||
warn("%s: transfer error: %i", __func__, -errno);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (num_sent == 0) /* Should never happen */ {
|
||||
warn("%s: transferred 0 bytes", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (num_sent > (ssize_t)buf_size) /* Should never happen */ {
|
||||
warn("%s: send more then read", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return num_sent;
|
||||
}
|
||||
|
||||
static void j1939cat_print_timestamp(struct j1939cat_priv *priv, const char *name,
|
||||
struct timespec *cur)
|
||||
{
|
||||
struct j1939cat_stats *stats = &priv->stats;
|
||||
|
||||
if (!(cur->tv_sec | cur->tv_nsec))
|
||||
return;
|
||||
|
||||
fprintf(stderr, " %s: %llu s %llu us (seq=%03u, send=%07u)",
|
||||
name, (unsigned long long)cur->tv_sec, (unsigned long long)cur->tv_nsec / 1000,
|
||||
stats->tskey, stats->send);
|
||||
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
static const char *j1939cat_tstype_to_str(int tstype)
|
||||
{
|
||||
switch (tstype) {
|
||||
case SCM_TSTAMP_SCHED:
|
||||
return "TX ENQ";
|
||||
case SCM_TSTAMP_SND:
|
||||
return "TX SND";
|
||||
case SCM_TSTAMP_ACK:
|
||||
return "TX ACK";
|
||||
default:
|
||||
return " unk";
|
||||
}
|
||||
}
|
||||
|
||||
/* Check the stats of SCM_TIMESTAMPING_OPT_STATS */
|
||||
static void j1939cat_scm_opt_stats(struct j1939cat_priv *priv, void *buf, int len)
|
||||
{
|
||||
struct j1939cat_stats *stats = &priv->stats;
|
||||
int offset = 0;
|
||||
|
||||
while (offset < len) {
|
||||
struct nlattr *nla = (struct nlattr *) ((char *)buf + offset);
|
||||
|
||||
switch (nla->nla_type) {
|
||||
case J1939_NLA_BYTES_ACKED:
|
||||
stats->send = *(uint32_t *)((char *)nla + NLA_HDRLEN);
|
||||
break;
|
||||
case J1939_NLA_TOTAL_SIZE:
|
||||
stats->total = *(uint32_t *)((char *)nla + NLA_HDRLEN);
|
||||
break;
|
||||
case J1939_NLA_PGN:
|
||||
stats->pgn = *(uint32_t *)((char *)nla + NLA_HDRLEN);
|
||||
break;
|
||||
case J1939_NLA_DEST_ADDR:
|
||||
stats->da = *(uint8_t *)((char *)nla + NLA_HDRLEN);
|
||||
break;
|
||||
case J1939_NLA_SRC_ADDR:
|
||||
stats->sa = *(uint8_t *)((char *)nla + NLA_HDRLEN);
|
||||
break;
|
||||
case J1939_NLA_DEST_NAME:
|
||||
stats->dst_name = *(uint64_t *)((char *)nla + NLA_HDRLEN);
|
||||
break;
|
||||
case J1939_NLA_SRC_NAME:
|
||||
stats->src_name = *(uint64_t *)((char *)nla + NLA_HDRLEN);
|
||||
break;
|
||||
default:
|
||||
warnx("not supported J1939_NLA field\n");
|
||||
}
|
||||
|
||||
offset += NLA_ALIGN(nla->nla_len);
|
||||
}
|
||||
}
|
||||
|
||||
static int j1939cat_extract_serr(struct j1939cat_priv *priv)
|
||||
{
|
||||
struct j1939cat_stats *stats = &priv->stats;
|
||||
struct sock_extended_err *serr = priv->serr;
|
||||
struct scm_timestamping *tss = priv->tss;
|
||||
|
||||
switch (serr->ee_origin) {
|
||||
case SO_EE_ORIGIN_TIMESTAMPING:
|
||||
/*
|
||||
* We expect here following patterns:
|
||||
* serr->ee_info == SCM_TSTAMP_ACK
|
||||
* Activated with SOF_TIMESTAMPING_TX_ACK
|
||||
* or
|
||||
* serr->ee_info == SCM_TSTAMP_SCHED
|
||||
* Activated with SOF_TIMESTAMPING_SCHED
|
||||
* and
|
||||
* serr->ee_data == tskey
|
||||
* session message counter which is activate
|
||||
* with SOF_TIMESTAMPING_OPT_ID
|
||||
* the serr->ee_errno should be ENOMSG
|
||||
*/
|
||||
if (serr->ee_errno != ENOMSG)
|
||||
warnx("serr: expected ENOMSG, got: %i",
|
||||
serr->ee_errno);
|
||||
stats->tskey = serr->ee_data;
|
||||
|
||||
j1939cat_print_timestamp(priv, j1939cat_tstype_to_str(serr->ee_info),
|
||||
&tss->ts[0]);
|
||||
|
||||
if (serr->ee_info == SCM_TSTAMP_SCHED)
|
||||
return -EINTR;
|
||||
return 0;
|
||||
case SO_EE_ORIGIN_LOCAL:
|
||||
/*
|
||||
* The serr->ee_origin == SO_EE_ORIGIN_LOCAL is
|
||||
* currently used to notify about locally
|
||||
* detected protocol/stack errors.
|
||||
* Following patterns are expected:
|
||||
* serr->ee_info == J1939_EE_INFO_TX_ABORT
|
||||
* is used to notify about session TX
|
||||
* abort.
|
||||
* serr->ee_data == tskey
|
||||
* session message counter which is activate
|
||||
* with SOF_TIMESTAMPING_OPT_ID
|
||||
* serr->ee_errno == actual error reason
|
||||
* error reason is converted from J1939
|
||||
* abort to linux error name space.
|
||||
*/
|
||||
switch (serr->ee_info) {
|
||||
case J1939_EE_INFO_TX_ABORT:
|
||||
j1939cat_print_timestamp(priv, "TX ABT", &tss->ts[0]);
|
||||
warnx("serr: tx error: %i, %s", serr->ee_errno,
|
||||
strerror(serr->ee_errno));
|
||||
return serr->ee_errno;
|
||||
case J1939_EE_INFO_RX_RTS:
|
||||
stats->tskey = serr->ee_data;
|
||||
j1939cat_print_timestamp(priv, "RX RTS", &tss->ts[0]);
|
||||
fprintf(stderr, " total size: %u, pgn=0x%05x, sa=0x%02x, da=0x%02x src_name=0x%08" PRIx64 ", dst_name=0x%08" PRIx64 ")\n",
|
||||
stats->total, stats->pgn, stats->sa, stats->da,
|
||||
stats->src_name, stats->dst_name);
|
||||
priv->last_dpo = -1;
|
||||
return 0;
|
||||
case J1939_EE_INFO_RX_DPO:
|
||||
stats->tskey = serr->ee_data;
|
||||
j1939cat_print_timestamp(priv, "RX DPO", &tss->ts[0]);
|
||||
if (stats->send <= (uint32_t)priv->last_dpo && priv->last_dpo != -1)
|
||||
warnx("same dpo? current: %i, last: %i",
|
||||
stats->send, priv->last_dpo);
|
||||
priv->last_dpo = stats->send;
|
||||
return 0;
|
||||
case J1939_EE_INFO_RX_ABORT:
|
||||
j1939cat_print_timestamp(priv, "RX ABT", &tss->ts[0]);
|
||||
warnx("serr: rx error: %i, %s", serr->ee_errno,
|
||||
strerror(serr->ee_errno));
|
||||
return serr->ee_errno;
|
||||
default:
|
||||
warnx("serr: unknown ee_info: %i", serr->ee_info);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
warnx("serr: wrong origin: %u", serr->ee_origin);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int j1939cat_parse_cm(struct j1939cat_priv *priv, struct cmsghdr *cm)
|
||||
{
|
||||
const size_t hdr_len = CMSG_ALIGN(sizeof(struct cmsghdr));
|
||||
|
||||
if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SCM_TIMESTAMPING) {
|
||||
priv->tss = (void *)CMSG_DATA(cm);
|
||||
} else if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SCM_TIMESTAMPING_OPT_STATS) {
|
||||
void *jstats = (void *)CMSG_DATA(cm);
|
||||
|
||||
/* Activated with SOF_TIMESTAMPING_OPT_STATS */
|
||||
j1939cat_scm_opt_stats(priv, jstats, cm->cmsg_len - hdr_len);
|
||||
} else if (cm->cmsg_level == SOL_CAN_J1939 &&
|
||||
cm->cmsg_type == SCM_J1939_ERRQUEUE) {
|
||||
priv->serr = (void *)CMSG_DATA(cm);
|
||||
} else
|
||||
warnx("serr: not supported type: %d.%d",
|
||||
cm->cmsg_level, cm->cmsg_type);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int j1939cat_recv_err(struct j1939cat_priv *priv)
|
||||
{
|
||||
char control[200];
|
||||
struct cmsghdr *cm;
|
||||
int ret;
|
||||
struct msghdr msg = {
|
||||
.msg_control = control,
|
||||
.msg_controllen = sizeof(control),
|
||||
};
|
||||
|
||||
ret = recvmsg(priv->sock, &msg, MSG_ERRQUEUE);
|
||||
if (ret == -1)
|
||||
err(EXIT_FAILURE, "recvmsg error notification: %i", errno);
|
||||
if (msg.msg_flags & MSG_CTRUNC)
|
||||
err(EXIT_FAILURE, "recvmsg error notification: truncated");
|
||||
|
||||
priv->serr = NULL;
|
||||
priv->tss = NULL;
|
||||
|
||||
for (cm = CMSG_FIRSTHDR(&msg); cm && cm->cmsg_len;
|
||||
cm = CMSG_NXTHDR(&msg, cm)) {
|
||||
j1939cat_parse_cm(priv, cm);
|
||||
if (priv->serr && priv->tss)
|
||||
return j1939cat_extract_serr(priv);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int j1939cat_send_loop(struct j1939cat_priv *priv, int out_fd, char *buf,
|
||||
size_t buf_size)
|
||||
{
|
||||
struct j1939cat_stats *stats = &priv->stats;
|
||||
ssize_t count;
|
||||
char *tmp_buf = buf;
|
||||
unsigned int events = POLLOUT | POLLERR;
|
||||
bool tx_done = false;
|
||||
|
||||
count = buf_size;
|
||||
|
||||
while (!tx_done) {
|
||||
ssize_t num_sent = 0;
|
||||
|
||||
if (priv->polltimeout) {
|
||||
struct pollfd fds = {
|
||||
.fd = priv->sock,
|
||||
.events = events,
|
||||
};
|
||||
int ret;
|
||||
|
||||
ret = poll(&fds, 1, priv->polltimeout);
|
||||
if (ret == -1) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
else
|
||||
return -errno;
|
||||
}
|
||||
if (!ret)
|
||||
return -ETIME;
|
||||
if (!(fds.revents & events)) {
|
||||
warn("%s: something else is wrong %x %x", __func__, fds.revents, events);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (fds.revents & POLLERR) {
|
||||
ret = j1939cat_recv_err(priv);
|
||||
if (ret == -EINTR)
|
||||
continue;
|
||||
if (ret)
|
||||
return ret;
|
||||
if ((priv->repeat - 1) == stats->tskey)
|
||||
tx_done = true;
|
||||
}
|
||||
|
||||
if (fds.revents & POLLOUT) {
|
||||
num_sent = j1939cat_send_one(priv, out_fd, tmp_buf, count);
|
||||
if (num_sent < 0)
|
||||
return num_sent;
|
||||
}
|
||||
} else {
|
||||
num_sent = j1939cat_send_one(priv, out_fd, tmp_buf, count);
|
||||
if (num_sent < 0)
|
||||
return num_sent;
|
||||
}
|
||||
|
||||
count -= num_sent;
|
||||
tmp_buf += num_sent;
|
||||
if (buf + buf_size < tmp_buf + count) {
|
||||
warn("%s: send buffer is bigger than the read buffer",
|
||||
__func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!count) {
|
||||
if (priv->repeat == priv->round)
|
||||
events = POLLERR;
|
||||
else
|
||||
tx_done = true;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int j1939cat_sendfile(struct j1939cat_priv *priv, int out_fd, int in_fd,
|
||||
off_t *offset, size_t count)
|
||||
{
|
||||
int ret = EXIT_SUCCESS;
|
||||
off_t orig = 0;
|
||||
char *buf;
|
||||
ssize_t num_read;
|
||||
size_t to_read, buf_size;
|
||||
|
||||
buf_size = min(priv->max_transfer, count);
|
||||
buf = malloc(buf_size);
|
||||
if (!buf) {
|
||||
warn("can't allocate buf");
|
||||
ret = EXIT_FAILURE;
|
||||
goto do_nofree;
|
||||
}
|
||||
|
||||
if (offset) {
|
||||
|
||||
/* Save current file offset and set offset to value in '*offset' */
|
||||
|
||||
orig = lseek(in_fd, 0, SEEK_CUR);
|
||||
if (orig == -1) {
|
||||
ret = EXIT_FAILURE;
|
||||
goto do_free;
|
||||
}
|
||||
if (lseek(in_fd, *offset, SEEK_SET) == -1) {
|
||||
ret = EXIT_FAILURE;
|
||||
goto do_free;
|
||||
}
|
||||
}
|
||||
|
||||
while (count > 0) {
|
||||
to_read = min(buf_size, count);
|
||||
|
||||
num_read = read(in_fd, buf, to_read);
|
||||
if (num_read == -1) {
|
||||
ret = EXIT_FAILURE;
|
||||
goto do_free;
|
||||
}
|
||||
if (num_read == 0)
|
||||
break; /* EOF */
|
||||
|
||||
ret = j1939cat_send_loop(priv, out_fd, buf, num_read);
|
||||
if (ret)
|
||||
goto do_free;
|
||||
|
||||
count -= num_read;
|
||||
}
|
||||
|
||||
if (offset) {
|
||||
/* Return updated file offset in '*offset', and reset the file offset
|
||||
to the value it had when we were called. */
|
||||
|
||||
*offset = lseek(in_fd, 0, SEEK_CUR);
|
||||
if (*offset == -1) {
|
||||
ret = EXIT_FAILURE;
|
||||
goto do_free;
|
||||
}
|
||||
|
||||
if (lseek(in_fd, orig, SEEK_SET) == -1) {
|
||||
ret = EXIT_FAILURE;
|
||||
goto do_free;
|
||||
}
|
||||
}
|
||||
|
||||
do_free:
|
||||
free(buf);
|
||||
do_nofree:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static size_t j1939cat_get_file_size(int fd)
|
||||
{
|
||||
off_t offset;
|
||||
|
||||
offset = lseek(fd, 0, SEEK_END);
|
||||
if (offset == -1)
|
||||
err(1, "%s lseek()\n", __func__);
|
||||
|
||||
if (lseek(fd, 0, SEEK_SET) == -1)
|
||||
err(1, "%s lseek() start\n", __func__);
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
static int j1939cat_send(struct j1939cat_priv *priv)
|
||||
{
|
||||
unsigned int size = 0;
|
||||
unsigned int i;
|
||||
int ret = 0;
|
||||
|
||||
if (priv->todo_filesize)
|
||||
size = j1939cat_get_file_size(priv->infile);
|
||||
|
||||
if (!size)
|
||||
return EXIT_FAILURE;
|
||||
|
||||
for (i = 0; i < priv->repeat; i++) {
|
||||
priv->round++;
|
||||
ret = j1939cat_sendfile(priv, priv->sock, priv->infile, NULL, size);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
if (lseek(priv->infile, 0, SEEK_SET) == -1)
|
||||
err(1, "%s lseek() start\n", __func__);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int j1939cat_recv_one(struct j1939cat_priv *priv, uint8_t *buf, size_t buf_size)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = recv(priv->sock, buf, buf_size, 0);
|
||||
if (ret < 0) {
|
||||
warn("recvf()");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
ret = write(priv->outfile, buf, ret);
|
||||
if (ret < 0) {
|
||||
warn("write stdout()");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int j1939cat_recv(struct j1939cat_priv *priv)
|
||||
{
|
||||
unsigned int events = POLLIN | POLLERR;
|
||||
int ret = EXIT_SUCCESS;
|
||||
size_t buf_size;
|
||||
uint8_t *buf;
|
||||
|
||||
buf_size = priv->max_transfer;
|
||||
buf = malloc(buf_size);
|
||||
if (!buf) {
|
||||
warn("can't allocate rx buf");
|
||||
return EXIT_FAILURE;;
|
||||
}
|
||||
|
||||
priv->last_dpo = -1;
|
||||
|
||||
while (priv->todo_recv) {
|
||||
if (priv->polltimeout) {
|
||||
struct pollfd fds = {
|
||||
.fd = priv->sock,
|
||||
.events = events,
|
||||
};
|
||||
int ret;
|
||||
|
||||
ret = poll(&fds, 1, priv->polltimeout);
|
||||
if (ret == -1) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
else
|
||||
return -errno;
|
||||
}
|
||||
if (!ret)
|
||||
continue;
|
||||
if (!(fds.revents & events)) {
|
||||
warn("%s: something else is wrong %x %x", __func__, fds.revents, events);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (fds.revents & POLLERR) {
|
||||
ret = j1939cat_recv_err(priv);
|
||||
if (ret == -EINTR)
|
||||
continue;
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (fds.revents & POLLIN) {
|
||||
ret = j1939cat_recv_one(priv, buf, buf_size);
|
||||
if (ret < 0)
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
ret = j1939cat_recv_one(priv, buf, buf_size);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int j1939cat_sock_prepare(struct j1939cat_priv *priv)
|
||||
{
|
||||
unsigned int sock_opt;
|
||||
int value;
|
||||
int ret;
|
||||
|
||||
/* open socket */
|
||||
priv->sock = socket(PF_CAN, SOCK_DGRAM, CAN_J1939);
|
||||
if (priv->sock < 0) {
|
||||
warn("socket(j1939)");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (priv->todo_prio >= 0) {
|
||||
ret = setsockopt(priv->sock, SOL_CAN_J1939, SO_J1939_SEND_PRIO,
|
||||
&priv->todo_prio, sizeof(priv->todo_prio));
|
||||
if (ret < 0) {
|
||||
warn("set priority %i", priv->todo_prio);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
value = 1;
|
||||
ret = setsockopt(priv->sock, SOL_CAN_J1939, SO_J1939_ERRQUEUE, &value,
|
||||
sizeof(value));
|
||||
if (ret < 0) {
|
||||
warn("set recverr");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
sock_opt = SOF_TIMESTAMPING_SOFTWARE |
|
||||
SOF_TIMESTAMPING_OPT_CMSG |
|
||||
SOF_TIMESTAMPING_TX_ACK |
|
||||
SOF_TIMESTAMPING_TX_SCHED |
|
||||
SOF_TIMESTAMPING_OPT_STATS | SOF_TIMESTAMPING_OPT_TSONLY |
|
||||
SOF_TIMESTAMPING_OPT_ID | SOF_TIMESTAMPING_RX_SOFTWARE;
|
||||
|
||||
if (setsockopt(priv->sock, SOL_SOCKET, SO_TIMESTAMPING,
|
||||
(char *) &sock_opt, sizeof(sock_opt)))
|
||||
err(1, "setsockopt timestamping");
|
||||
|
||||
if (priv->todo_broadcast) {
|
||||
ret = setsockopt(priv->sock, SOL_SOCKET, SO_BROADCAST,
|
||||
&priv->todo_broadcast,
|
||||
sizeof(priv->todo_broadcast));
|
||||
if (ret < 0) {
|
||||
warn("setsockopt: filed to set broadcast");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
ret = bind(priv->sock, (void *)&priv->sockname, sizeof(priv->sockname));
|
||||
if (ret < 0) {
|
||||
warn("bind()");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (!priv->todo_connect)
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
if (!priv->valid_peername) {
|
||||
warn("no peername supplied");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
ret = connect(priv->sock, (void *)&priv->peername,
|
||||
sizeof(priv->peername));
|
||||
if (ret < 0) {
|
||||
warn("connect()");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int j1939cat_parse_args(struct j1939cat_priv *priv, int argc, char *argv[])
|
||||
{
|
||||
int opt;
|
||||
|
||||
/* argument parsing */
|
||||
while ((opt = getopt(argc, argv, optstring)) != -1)
|
||||
switch (opt) {
|
||||
case 'i':
|
||||
priv->infile = open(optarg, O_RDONLY);
|
||||
if (priv->infile == -1)
|
||||
err(EXIT_FAILURE, "can't open input file");
|
||||
priv->todo_filesize = 1;
|
||||
break;
|
||||
case 's':
|
||||
priv->max_transfer = strtoul(optarg, NULL, 0);
|
||||
if (priv->max_transfer > J1939_MAX_ETP_PACKET_SIZE)
|
||||
err(EXIT_FAILURE,
|
||||
"used value (%zu) is bigger then allowed maximal size: %u.\n",
|
||||
priv->max_transfer,
|
||||
J1939_MAX_ETP_PACKET_SIZE);
|
||||
break;
|
||||
case 'r':
|
||||
priv->todo_recv = 1;
|
||||
break;
|
||||
case 'p':
|
||||
priv->todo_prio = strtoul(optarg, NULL, 0);
|
||||
break;
|
||||
case 'P':
|
||||
priv->polltimeout = strtoul(optarg, NULL, 0);
|
||||
break;
|
||||
case 'c':
|
||||
priv->todo_connect = 1;
|
||||
break;
|
||||
case 'R':
|
||||
priv->repeat = strtoul(optarg, NULL, 0);
|
||||
if (priv->repeat < 1)
|
||||
err(EXIT_FAILURE,
|
||||
"send/repeat count can't be less then 1\n");
|
||||
break;
|
||||
case 'B':
|
||||
priv->todo_broadcast = 1;
|
||||
break;
|
||||
case 'h': /*fallthrough*/
|
||||
default:
|
||||
fputs(help_msg, stderr);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (argv[optind]) {
|
||||
if (strcmp("-", argv[optind]) != 0)
|
||||
libj1939_parse_canaddr(argv[optind], &priv->sockname);
|
||||
optind++;
|
||||
}
|
||||
|
||||
if (argv[optind]) {
|
||||
if (strcmp("-", argv[optind]) != 0) {
|
||||
libj1939_parse_canaddr(argv[optind], &priv->peername);
|
||||
priv->valid_peername = 1;
|
||||
}
|
||||
optind++;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct j1939cat_priv *priv;
|
||||
int ret = 0;
|
||||
|
||||
priv = calloc(1, sizeof(*priv));
|
||||
if (!priv)
|
||||
err(EXIT_FAILURE, "can't allocate priv");
|
||||
|
||||
priv->todo_prio = -1;
|
||||
priv->infile = STDIN_FILENO;
|
||||
priv->outfile = STDOUT_FILENO;
|
||||
priv->max_transfer = J1939_MAX_ETP_PACKET_SIZE;
|
||||
priv->polltimeout = 100000;
|
||||
priv->repeat = 1;
|
||||
|
||||
libj1939_init_sockaddr_can(&priv->sockname, J1939_NO_PGN);
|
||||
libj1939_init_sockaddr_can(&priv->peername, J1939_NO_PGN);
|
||||
|
||||
ret = j1939cat_parse_args(priv, argc, argv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = j1939cat_sock_prepare(priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (priv->todo_recv)
|
||||
ret = j1939cat_recv(priv);
|
||||
else
|
||||
ret = j1939cat_send(priv);
|
||||
|
||||
close(priv->infile);
|
||||
close(priv->outfile);
|
||||
close(priv->sock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
295
Devices/Libraries/Systems/can-utils/j1939spy.c
Executable file
295
Devices/Libraries/Systems/can-utils/j1939spy.c
Executable file
@@ -0,0 +1,295 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2011 EIA Electronics
|
||||
*
|
||||
* Authors:
|
||||
* Kurt Van Dijck <kurt.van.dijck@eia.be>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the version 2 of the GNU General Public License
|
||||
* as published by the Free Software Foundation
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <err.h>
|
||||
#include <getopt.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "libj1939.h"
|
||||
|
||||
/*
|
||||
* getopt
|
||||
*/
|
||||
static const char help_msg[] =
|
||||
"j1939spy: An SAE J1939 spy utility" "\n"
|
||||
"Usage: j1939spy [OPTION...] [[IFACE:][NAME|SA][,PGN]]" "\n"
|
||||
"Options:\n"
|
||||
" -P, --promisc Run in promiscuous mode" "\n"
|
||||
" (= receive traffic not for this ECU)" "\n"
|
||||
" -b, --block=SIZE Use a receive buffer of SIZE (default 1024)" "\n"
|
||||
" -t, --time[=a|d|z|A] Show time: (a)bsolute, (d)elta, (z)ero, (A)bsolute w date" "\n"
|
||||
;
|
||||
|
||||
#ifdef _GNU_SOURCE
|
||||
static struct option long_opts[] = {
|
||||
{ "help", no_argument, NULL, '?', },
|
||||
{ "promisc", no_argument, NULL, 'P', },
|
||||
{ "block", required_argument, NULL, 'b', },
|
||||
{ "time", optional_argument, NULL, 't', },
|
||||
{ },
|
||||
};
|
||||
#else
|
||||
#define getopt_long(argc, argv, optstring, longopts, longindex) \
|
||||
getopt((argc), (argv), (optstring))
|
||||
#endif
|
||||
static const char optstring[] = "vPb:t::?";
|
||||
|
||||
/*
|
||||
* static variables
|
||||
*/
|
||||
static struct {
|
||||
struct sockaddr_can addr;
|
||||
int promisc;
|
||||
int time;
|
||||
int pkt_len;
|
||||
} s = {
|
||||
.pkt_len = 1024,
|
||||
.addr.can_addr.j1939 = {
|
||||
.name = J1939_NO_NAME,
|
||||
.addr = J1939_NO_ADDR,
|
||||
.pgn = J1939_NO_PGN,
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
* useful buffers
|
||||
*/
|
||||
static const int ival_1 = 1;
|
||||
|
||||
static char ctrlmsg[
|
||||
CMSG_SPACE(sizeof(struct timeval))
|
||||
+ CMSG_SPACE(sizeof(uint8_t)) /* dest addr */
|
||||
+ CMSG_SPACE(sizeof(uint64_t)) /* dest name */
|
||||
+ CMSG_SPACE(sizeof(uint8_t)) /* priority */
|
||||
];
|
||||
static struct iovec iov;
|
||||
static struct msghdr msg;
|
||||
static struct cmsghdr *cmsg;
|
||||
static uint8_t *buf;
|
||||
|
||||
/*
|
||||
* program
|
||||
*/
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int ret, sock, opt;
|
||||
unsigned int j, len;
|
||||
struct timeval tref, tdut, ttmp;
|
||||
struct sockaddr_can src;
|
||||
struct j1939_filter filt;
|
||||
int filter = 0;
|
||||
uint8_t priority, dst_addr;
|
||||
uint64_t dst_name;
|
||||
uint64_t recvflags;
|
||||
|
||||
/* argument parsing */
|
||||
while ((opt = getopt_long(argc, argv, optstring, long_opts, NULL)) != -1)
|
||||
switch (opt) {
|
||||
case 'b':
|
||||
s.pkt_len = strtoul(optarg, 0, 0);
|
||||
break;
|
||||
case 'P':
|
||||
++s.promisc;
|
||||
break;
|
||||
case 't':
|
||||
if (optarg) {
|
||||
if (!strchr("adzA", optarg[0]))
|
||||
err(1, "unknown time option '%c'",
|
||||
optarg[0]);
|
||||
s.time = optarg[0];
|
||||
} else {
|
||||
s.time = 'z';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
fputs(help_msg, stderr);
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (argv[optind]) {
|
||||
optarg = argv[optind];
|
||||
ret = libj1939_str2addr(optarg, 0, &s.addr);
|
||||
if (ret < 0) {
|
||||
err(0, "bad URI %s", optarg);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
buf = malloc(s.pkt_len);
|
||||
if (!buf)
|
||||
err(1, "malloc %u", s.pkt_len);
|
||||
|
||||
/* setup socket */
|
||||
sock = socket(PF_CAN, SOCK_DGRAM, CAN_J1939);
|
||||
if (sock < 0)
|
||||
err(1, "socket(can, dgram, j1939)");
|
||||
|
||||
memset(&filt, 0, sizeof(filt));
|
||||
if (s.addr.can_addr.j1939.name) {
|
||||
filt.name = s.addr.can_addr.j1939.name;
|
||||
filt.name_mask = ~0ULL;
|
||||
++filter;
|
||||
}
|
||||
if (s.addr.can_addr.j1939.addr < 0xff) {
|
||||
filt.addr = s.addr.can_addr.j1939.addr;
|
||||
filt.addr_mask = ~0;
|
||||
++filter;
|
||||
}
|
||||
if (s.addr.can_addr.j1939.pgn <= J1939_PGN_MAX) {
|
||||
filt.pgn = s.addr.can_addr.j1939.pgn;
|
||||
filt.pgn_mask = ~0;
|
||||
++filter;
|
||||
}
|
||||
if (filter) {
|
||||
ret = setsockopt(sock, SOL_CAN_J1939, SO_J1939_FILTER, &filt, sizeof(filt));
|
||||
if (ret < 0)
|
||||
err(1, "setsockopt filter");
|
||||
}
|
||||
|
||||
if (s.promisc) {
|
||||
ret = setsockopt(sock, SOL_CAN_J1939, SO_J1939_PROMISC, &ival_1, sizeof(ival_1));
|
||||
if (ret < 0)
|
||||
err(1, "setsockopt promisc");
|
||||
}
|
||||
|
||||
if (s.time) {
|
||||
ret = setsockopt(sock, SOL_SOCKET, SO_TIMESTAMP, &ival_1, sizeof(ival_1));
|
||||
if (ret < 0)
|
||||
err(1, "setsockopt timestamp");
|
||||
}
|
||||
ret = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &s.pkt_len, sizeof(s.pkt_len));
|
||||
if (ret < 0)
|
||||
err(1, "setsockopt rcvbuf %u", s.pkt_len);
|
||||
|
||||
/* bind(): to default, only ifindex is used. */
|
||||
memset(&src, 0, sizeof(src));
|
||||
src.can_ifindex = s.addr.can_ifindex;
|
||||
src.can_family = AF_CAN;
|
||||
src.can_addr.j1939.name = J1939_NO_NAME;
|
||||
src.can_addr.j1939.addr = J1939_NO_ADDR;
|
||||
src.can_addr.j1939.pgn = J1939_NO_PGN;
|
||||
ret = bind(sock, (void *)&src, sizeof(src));
|
||||
if (ret < 0)
|
||||
err(1, "bind(%s)", argv[1]);
|
||||
|
||||
/* these settings are static and can be held out of the hot path */
|
||||
iov.iov_base = &buf[0];
|
||||
msg.msg_name = &src;
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_control = &ctrlmsg;
|
||||
|
||||
memset(&tref, 0, sizeof(tref));
|
||||
while (1) {
|
||||
/* these settings may be modified by recvmsg() */
|
||||
iov.iov_len = s.pkt_len;
|
||||
msg.msg_namelen = sizeof(src);
|
||||
msg.msg_controllen = sizeof(ctrlmsg);
|
||||
msg.msg_flags = 0;
|
||||
|
||||
ret = recvmsg(sock, &msg, 0);
|
||||
//ret = recvfrom(buf, s.pkt_len, 0, (void *)&addr, &len);
|
||||
if (ret < 0) {
|
||||
switch (errno) {
|
||||
case ENETDOWN:
|
||||
err(0, "ifindex %i", s.addr.can_ifindex);
|
||||
continue;
|
||||
case EINTR:
|
||||
continue;
|
||||
default:
|
||||
err(1, "recvmsg(ifindex %i)", s.addr.can_ifindex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
len = ret;
|
||||
recvflags = 0;
|
||||
dst_addr = 0;
|
||||
priority = 0;
|
||||
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
|
||||
switch (cmsg->cmsg_level) {
|
||||
case SOL_SOCKET:
|
||||
if (cmsg->cmsg_type == SCM_TIMESTAMP) {
|
||||
memcpy(&tdut, CMSG_DATA(cmsg), sizeof(tdut));
|
||||
recvflags |= 1ULL << cmsg->cmsg_type;
|
||||
}
|
||||
break;
|
||||
case SOL_CAN_J1939:
|
||||
recvflags |= 1ULL << cmsg->cmsg_type;
|
||||
if (cmsg->cmsg_type == SCM_J1939_DEST_ADDR)
|
||||
dst_addr = *CMSG_DATA(cmsg);
|
||||
else if (cmsg->cmsg_type == SCM_J1939_DEST_NAME)
|
||||
memcpy(&dst_name, CMSG_DATA(cmsg), cmsg->cmsg_len - CMSG_LEN(0));
|
||||
else if (cmsg->cmsg_type == SCM_J1939_PRIO)
|
||||
priority = *CMSG_DATA(cmsg);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
if (recvflags & (1ULL << SCM_TIMESTAMP)) {
|
||||
if ('z' == s.time) {
|
||||
if (!tref.tv_sec)
|
||||
tref = tdut;
|
||||
timersub(&tdut, &tref, &ttmp);
|
||||
tdut = ttmp;
|
||||
goto abs_time;
|
||||
} else if ('d' == s.time) {
|
||||
timersub(&tdut, &tref, &ttmp);
|
||||
tref = tdut;
|
||||
tdut = ttmp;
|
||||
goto abs_time;
|
||||
} else if ('a' == s.time) {
|
||||
abs_time:
|
||||
printf("(%llu.%04llu)", (unsigned long long)tdut.tv_sec, (unsigned long long)tdut.tv_usec / 100);
|
||||
} else if ('A' == s.time) {
|
||||
struct tm tm;
|
||||
tm = *localtime(&tdut.tv_sec);
|
||||
printf("(%04u%02u%02uT%02u%02u%02u.%04llu)",
|
||||
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
||||
tm.tm_hour, tm.tm_min, tm.tm_sec,
|
||||
(unsigned long long)tdut.tv_usec/100);
|
||||
}
|
||||
}
|
||||
printf(" %s ", libj1939_addr2str(&src));
|
||||
if (recvflags & (1ULL << SCM_J1939_DEST_NAME))
|
||||
printf("%016llx ", (unsigned long long)dst_name);
|
||||
else if (recvflags & (1ULL << SCM_J1939_DEST_ADDR))
|
||||
printf("%02x ", dst_addr);
|
||||
else
|
||||
printf("- ");
|
||||
printf("!%u ", priority);
|
||||
|
||||
printf("[%i%s]", len, (msg.msg_flags & MSG_TRUNC) ? "..." : "");
|
||||
for (j = 0; j < len;) {
|
||||
unsigned int end = j + 4;
|
||||
if (end > len)
|
||||
end = len;
|
||||
printf(" ");
|
||||
for (; j < end; ++j)
|
||||
printf("%02x", buf[j]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
219
Devices/Libraries/Systems/can-utils/j1939sr.c
Executable file
219
Devices/Libraries/Systems/can-utils/j1939sr.c
Executable file
@@ -0,0 +1,219 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2011 EIA Electronics
|
||||
*
|
||||
* Authors:
|
||||
* Kurt Van Dijck <kurt.van.dijck@eia.be>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the version 2 of the GNU General Public License
|
||||
* as published by the Free Software Foundation
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <err.h>
|
||||
#include <getopt.h>
|
||||
#include <poll.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "libj1939.h"
|
||||
|
||||
/*
|
||||
* getopt
|
||||
*/
|
||||
static const char help_msg[] =
|
||||
"j1939sr: An SAE J1939 send/recv utility" "\n"
|
||||
"Usage: j1939sr [OPTION...] SOURCE [DEST]" "\n"
|
||||
"Options:\n"
|
||||
" -v, --verbose Increase verbosity" "\n"
|
||||
" -p, --priority=VAL J1939 priority (0..7, default 6)" "\n"
|
||||
" -S, --serialize Strictly serialize outgoing packets" "\n"
|
||||
" -s, --size Packet size, default autodetected" "\n"
|
||||
"\n"
|
||||
" SOURCE [IFACE:][NAME|SA][,PGN]" "\n"
|
||||
" DEST [NAME|SA]" "\n"
|
||||
;
|
||||
|
||||
#ifdef _GNU_SOURCE
|
||||
static struct option long_opts[] = {
|
||||
{ "help", no_argument, NULL, '?', },
|
||||
{ "verbose", no_argument, NULL, 'v', },
|
||||
|
||||
{ "priority", required_argument, NULL, 'p', },
|
||||
{ "size", required_argument, NULL, 's', },
|
||||
{ "serialize", no_argument, NULL, 'S', },
|
||||
{ },
|
||||
};
|
||||
#else
|
||||
#define getopt_long(argc, argv, optstring, longopts, longindex) \
|
||||
getopt((argc), (argv), (optstring))
|
||||
#endif
|
||||
static const char optstring[] = "vp:s:S?";
|
||||
|
||||
/*
|
||||
* static variables: configurations
|
||||
*/
|
||||
static struct {
|
||||
int verbose;
|
||||
int sendflags; /* flags for sendto() */
|
||||
int pkt_len;
|
||||
int priority;
|
||||
int defined;
|
||||
#define DEF_SRC 1
|
||||
#define DEF_DST 2
|
||||
#define DEF_PRIO 4
|
||||
struct sockaddr_can src, dst;
|
||||
} s = {
|
||||
.priority = 6,
|
||||
.src.can_addr.j1939 = {
|
||||
.name = J1939_NO_NAME,
|
||||
.addr = J1939_NO_ADDR,
|
||||
.pgn = J1939_NO_PGN,
|
||||
},
|
||||
.dst.can_addr.j1939 = {
|
||||
.name = J1939_NO_NAME,
|
||||
.addr = J1939_NO_ADDR,
|
||||
.pgn = J1939_NO_PGN,
|
||||
},
|
||||
};
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
|
||||
int ret, sock, opt;
|
||||
unsigned int len;
|
||||
struct pollfd pfd[2];
|
||||
uint8_t *buf;
|
||||
|
||||
/* argument parsing */
|
||||
while ((opt = getopt_long(argc, argv, optstring, long_opts, NULL)) != -1)
|
||||
switch (opt) {
|
||||
case 'v':
|
||||
++s.verbose;
|
||||
break;
|
||||
case 's':
|
||||
s.pkt_len = strtoul(optarg, 0, 0);
|
||||
if (!s.pkt_len)
|
||||
err(1, "packet size of %s", optarg);
|
||||
break;
|
||||
case 'p':
|
||||
s.priority = strtoul(optarg, 0, 0);
|
||||
s.defined |= DEF_PRIO;
|
||||
break;
|
||||
case 'S':
|
||||
s.sendflags |= MSG_SYN;
|
||||
break;
|
||||
default:
|
||||
fputs(help_msg, stderr);
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
|
||||
if (argv[optind]) {
|
||||
optarg = argv[optind++];
|
||||
ret = libj1939_str2addr(optarg, 0, &s.src);
|
||||
if (ret < 0)
|
||||
err(1, "bad address spec [%s]", optarg);
|
||||
s.defined |= DEF_SRC;
|
||||
}
|
||||
if (argv[optind]) {
|
||||
optarg = argv[optind++];
|
||||
ret = libj1939_str2addr(optarg, 0, &s.dst);
|
||||
if (ret < 0)
|
||||
err(1, "bad address spec [%s]", optarg);
|
||||
s.defined |= DEF_DST;
|
||||
}
|
||||
|
||||
if (!s.pkt_len) {
|
||||
struct stat st;
|
||||
|
||||
if (fstat(STDIN_FILENO, &st) < 0)
|
||||
err(1, "stat stdin, could not determine buffer size");
|
||||
s.pkt_len = st.st_size ?: 1024;
|
||||
}
|
||||
|
||||
/* prepare */
|
||||
buf = malloc(s.pkt_len);
|
||||
if (!buf)
|
||||
err(1, "malloc %u", s.pkt_len);
|
||||
|
||||
sock = socket(PF_CAN, SOCK_DGRAM, CAN_J1939);
|
||||
if (sock < 0)
|
||||
err(1, "socket(can, dgram, j1939)");
|
||||
|
||||
if (s.defined & DEF_PRIO) {
|
||||
ret = setsockopt(sock, SOL_CAN_J1939, SO_J1939_SEND_PRIO, &s.priority, sizeof(s.priority));
|
||||
if (ret < 0)
|
||||
err(1, "setsockopt priority");
|
||||
}
|
||||
if (s.defined & DEF_SRC) {
|
||||
s.src.can_family = AF_CAN;
|
||||
ret = bind(sock, (void *)&s.src, sizeof(s.src));
|
||||
if (ret < 0)
|
||||
err(1, "bind(%s), %i", libj1939_addr2str(&s.src), -errno);
|
||||
}
|
||||
|
||||
if (s.defined & DEF_DST) {
|
||||
s.dst.can_family = AF_CAN;
|
||||
ret = connect(sock, (void *)&s.dst, sizeof(s.dst));
|
||||
if (ret < 0)
|
||||
err(1, "connect(%s), %i", libj1939_addr2str(&s.dst), -errno);
|
||||
}
|
||||
|
||||
pfd[0].fd = STDIN_FILENO;
|
||||
pfd[0].events = POLLIN;
|
||||
pfd[1].fd = sock;
|
||||
pfd[1].events = POLLIN;
|
||||
|
||||
/* run */
|
||||
while (1) {
|
||||
ret = poll(pfd, sizeof(pfd)/sizeof(pfd[0]), -1);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
err(1, "poll()");
|
||||
}
|
||||
if (pfd[0].revents) {
|
||||
ret = read(pfd[0].fd, buf, s.pkt_len);
|
||||
if (ret < 0)
|
||||
err(1, "read(stdin)");
|
||||
if (!ret)
|
||||
break;
|
||||
len = ret;
|
||||
do {
|
||||
ret = send(pfd[1].fd, buf, len, s.sendflags);
|
||||
if (ret < 0 && errno != ENOBUFS)
|
||||
err(1, "write(%s)", libj1939_addr2str(&s.src));
|
||||
} while (ret < 0);
|
||||
}
|
||||
if (pfd[1].revents) {
|
||||
ret = read(pfd[1].fd, buf, s.pkt_len);
|
||||
if (ret < 0) {
|
||||
ret = errno;
|
||||
err(0, "read(%s)", libj1939_addr2str(&s.dst));
|
||||
switch (ret) {
|
||||
case EHOSTDOWN:
|
||||
break;
|
||||
default:
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
if (write(STDOUT_FILENO, buf, ret) < 0)
|
||||
err(1, "write(stdout)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
896
Devices/Libraries/Systems/can-utils/lib.c
Executable file
896
Devices/Libraries/Systems/can-utils/lib.c
Executable file
@@ -0,0 +1,896 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
|
||||
/*
|
||||
* lib.c - library for command line tools
|
||||
*
|
||||
* Copyright (c) 2002-2007 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* Send feedback to <linux-can@vger.kernel.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/error.h>
|
||||
#include <sys/socket.h> /* for sa_family_t */
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#define CANID_DELIM '#'
|
||||
#define CC_DLC_DELIM '_'
|
||||
#define XL_HDR_DELIM ':'
|
||||
#define DATA_SEPERATOR '.'
|
||||
|
||||
const char hex_asc_upper[] = "0123456789ABCDEF";
|
||||
|
||||
#define hex_asc_upper_lo(x) hex_asc_upper[((x)&0x0F)]
|
||||
#define hex_asc_upper_hi(x) hex_asc_upper[((x)&0xF0) >> 4]
|
||||
|
||||
static inline void put_hex_byte(char *buf, __u8 byte)
|
||||
{
|
||||
buf[0] = hex_asc_upper_hi(byte);
|
||||
buf[1] = hex_asc_upper_lo(byte);
|
||||
}
|
||||
|
||||
static inline void _put_id(char *buf, int end_offset, canid_t id)
|
||||
{
|
||||
/* build 3 (SFF) or 8 (EFF) digit CAN identifier */
|
||||
while (end_offset >= 0) {
|
||||
buf[end_offset--] = hex_asc_upper_lo(id);
|
||||
id >>= 4;
|
||||
}
|
||||
}
|
||||
|
||||
#define put_sff_id(buf, id) _put_id(buf, 2, id)
|
||||
#define put_eff_id(buf, id) _put_id(buf, 7, id)
|
||||
|
||||
/* CAN DLC to real data length conversion helpers */
|
||||
|
||||
static const unsigned char dlc2len[] = {0, 1, 2, 3, 4, 5, 6, 7,
|
||||
8, 12, 16, 20, 24, 32, 48, 64};
|
||||
|
||||
/* get data length from raw data length code (DLC) */
|
||||
unsigned char can_fd_dlc2len(unsigned char dlc)
|
||||
{
|
||||
return dlc2len[dlc & 0x0F];
|
||||
}
|
||||
|
||||
static const unsigned char len2dlc[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, /* 0 - 8 */
|
||||
9, 9, 9, 9, /* 9 - 12 */
|
||||
10, 10, 10, 10, /* 13 - 16 */
|
||||
11, 11, 11, 11, /* 17 - 20 */
|
||||
12, 12, 12, 12, /* 21 - 24 */
|
||||
13, 13, 13, 13, 13, 13, 13, 13, /* 25 - 32 */
|
||||
14, 14, 14, 14, 14, 14, 14, 14, /* 33 - 40 */
|
||||
14, 14, 14, 14, 14, 14, 14, 14, /* 41 - 48 */
|
||||
15, 15, 15, 15, 15, 15, 15, 15, /* 49 - 56 */
|
||||
15, 15, 15, 15, 15, 15, 15, 15}; /* 57 - 64 */
|
||||
|
||||
/* map the sanitized data length to an appropriate data length code */
|
||||
unsigned char can_fd_len2dlc(unsigned char len)
|
||||
{
|
||||
if (len > 64)
|
||||
return 0xF;
|
||||
|
||||
return len2dlc[len];
|
||||
}
|
||||
|
||||
unsigned char asc2nibble(char c)
|
||||
{
|
||||
if ((c >= '0') && (c <= '9'))
|
||||
return c - '0';
|
||||
|
||||
if ((c >= 'A') && (c <= 'F'))
|
||||
return c - 'A' + 10;
|
||||
|
||||
if ((c >= 'a') && (c <= 'f'))
|
||||
return c - 'a' + 10;
|
||||
|
||||
return 16; /* error */
|
||||
}
|
||||
|
||||
int hexstring2data(char *arg, unsigned char *data, int maxdlen)
|
||||
{
|
||||
int len = strlen(arg);
|
||||
int i;
|
||||
unsigned char tmp;
|
||||
|
||||
if (!len || len % 2 || len > maxdlen * 2)
|
||||
return 1;
|
||||
|
||||
memset(data, 0, maxdlen);
|
||||
|
||||
for (i = 0; i < len / 2; i++) {
|
||||
tmp = asc2nibble(*(arg + (2 * i)));
|
||||
if (tmp > 0x0F)
|
||||
return 1;
|
||||
|
||||
data[i] = (tmp << 4);
|
||||
|
||||
tmp = asc2nibble(*(arg + (2 * i) + 1));
|
||||
if (tmp > 0x0F)
|
||||
return 1;
|
||||
|
||||
data[i] |= tmp;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int parse_canframe(char *cs, cu_t *cu)
|
||||
{
|
||||
/* documentation see lib.h */
|
||||
|
||||
int i, idx, dlen, len;
|
||||
int maxdlen = CAN_MAX_DLEN;
|
||||
int mtu = CAN_MTU;
|
||||
__u8 *data = cu->fd.data; /* fill CAN CC/FD data by default */
|
||||
canid_t tmp;
|
||||
|
||||
len = strlen(cs);
|
||||
//printf("'%s' len %d\n", cs, len);
|
||||
|
||||
memset(cu, 0, sizeof(*cu)); /* init CAN CC/FD/XL frame, e.g. LEN = 0 */
|
||||
|
||||
if (len < 4)
|
||||
return 0;
|
||||
|
||||
if (cs[3] == CANID_DELIM) { /* 3 digits SFF */
|
||||
|
||||
idx = 4;
|
||||
for (i = 0; i < 3; i++) {
|
||||
if ((tmp = asc2nibble(cs[i])) > 0x0F)
|
||||
return 0;
|
||||
cu->cc.can_id |= tmp << (2 - i) * 4;
|
||||
}
|
||||
|
||||
} else if (cs[5] == CANID_DELIM) { /* 5 digits CAN XL VCID/PRIO*/
|
||||
|
||||
idx = 6;
|
||||
for (i = 0; i < 5; i++) {
|
||||
if ((tmp = asc2nibble(cs[i])) > 0x0F)
|
||||
return 0;
|
||||
cu->xl.prio |= tmp << (4 - i) * 4;
|
||||
}
|
||||
|
||||
/* the VCID starts at bit position 16 */
|
||||
tmp = (cu->xl.prio << 4) & CANXL_VCID_MASK;
|
||||
cu->xl.prio &= CANXL_PRIO_MASK;
|
||||
cu->xl.prio |= tmp;
|
||||
|
||||
} else if (cs[8] == CANID_DELIM) { /* 8 digits EFF */
|
||||
|
||||
idx = 9;
|
||||
for (i = 0; i < 8; i++) {
|
||||
if ((tmp = asc2nibble(cs[i])) > 0x0F)
|
||||
return 0;
|
||||
cu->cc.can_id |= tmp << (7 - i) * 4;
|
||||
}
|
||||
if (!(cu->cc.can_id & CAN_ERR_FLAG)) /* 8 digits but no errorframe? */
|
||||
cu->cc.can_id |= CAN_EFF_FLAG; /* then it is an extended frame */
|
||||
|
||||
} else
|
||||
return 0;
|
||||
|
||||
if ((cs[idx] == 'R') || (cs[idx] == 'r')) { /* RTR frame */
|
||||
cu->cc.can_id |= CAN_RTR_FLAG;
|
||||
|
||||
/* check for optional DLC value for CAN 2.0B frames */
|
||||
if (cs[++idx] && (tmp = asc2nibble(cs[idx++])) <= CAN_MAX_DLEN) {
|
||||
cu->cc.len = tmp;
|
||||
|
||||
/* check for optional raw DLC value for CAN 2.0B frames */
|
||||
if ((tmp == CAN_MAX_DLEN) && (cs[idx++] == CC_DLC_DELIM)) {
|
||||
tmp = asc2nibble(cs[idx]);
|
||||
if ((tmp > CAN_MAX_DLEN) && (tmp <= CAN_MAX_RAW_DLC))
|
||||
cu->cc.len8_dlc = tmp;
|
||||
}
|
||||
}
|
||||
return mtu;
|
||||
}
|
||||
|
||||
if (cs[idx] == CANID_DELIM) { /* CAN FD frame escape char '##' */
|
||||
maxdlen = CANFD_MAX_DLEN;
|
||||
mtu = CANFD_MTU;
|
||||
|
||||
/* CAN FD frame <canid>##<flags><data>* */
|
||||
if ((tmp = asc2nibble(cs[idx + 1])) > 0x0F)
|
||||
return 0;
|
||||
|
||||
cu->fd.flags = tmp;
|
||||
cu->fd.flags |= CANFD_FDF; /* dual-use */
|
||||
idx += 2;
|
||||
|
||||
} else if (cs[idx + 14] == CANID_DELIM) { /* CAN XL frame '#80:00:11223344#' */
|
||||
maxdlen = CANXL_MAX_DLEN;
|
||||
mtu = CANXL_MTU;
|
||||
data = cu->xl.data; /* fill CAN XL data */
|
||||
|
||||
if ((cs[idx + 2] != XL_HDR_DELIM) || (cs[idx + 5] != XL_HDR_DELIM))
|
||||
return 0;
|
||||
|
||||
if ((tmp = asc2nibble(cs[idx++])) > 0x0F)
|
||||
return 0;
|
||||
cu->xl.flags = tmp << 4;
|
||||
if ((tmp = asc2nibble(cs[idx++])) > 0x0F)
|
||||
return 0;
|
||||
cu->xl.flags |= tmp;
|
||||
|
||||
/* force CAN XL flag if it was missing in the ASCII string */
|
||||
cu->xl.flags |= CANXL_XLF;
|
||||
|
||||
idx++; /* skip XL_HDR_DELIM */
|
||||
|
||||
if ((tmp = asc2nibble(cs[idx++])) > 0x0F)
|
||||
return 0;
|
||||
cu->xl.sdt = tmp << 4;
|
||||
if ((tmp = asc2nibble(cs[idx++])) > 0x0F)
|
||||
return 0;
|
||||
cu->xl.sdt |= tmp;
|
||||
|
||||
idx++; /* skip XL_HDR_DELIM */
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
if ((tmp = asc2nibble(cs[idx++])) > 0x0F)
|
||||
return 0;
|
||||
cu->xl.af |= tmp << (7 - i) * 4;
|
||||
}
|
||||
|
||||
idx++; /* skip CANID_DELIM */
|
||||
}
|
||||
|
||||
for (i = 0, dlen = 0; i < maxdlen; i++) {
|
||||
if (cs[idx] == DATA_SEPERATOR) /* skip (optional) separator */
|
||||
idx++;
|
||||
|
||||
if (idx >= len) /* end of string => end of data */
|
||||
break;
|
||||
|
||||
if ((tmp = asc2nibble(cs[idx++])) > 0x0F)
|
||||
return 0;
|
||||
data[i] = tmp << 4;
|
||||
if ((tmp = asc2nibble(cs[idx++])) > 0x0F)
|
||||
return 0;
|
||||
data[i] |= tmp;
|
||||
dlen++;
|
||||
}
|
||||
|
||||
if (mtu == CANXL_MTU)
|
||||
cu->xl.len = dlen;
|
||||
else
|
||||
cu->fd.len = dlen;
|
||||
|
||||
/* check for extra DLC when having a Classic CAN with 8 bytes payload */
|
||||
if ((maxdlen == CAN_MAX_DLEN) && (dlen == CAN_MAX_DLEN) && (cs[idx++] == CC_DLC_DELIM)) {
|
||||
unsigned char dlc = asc2nibble(cs[idx]);
|
||||
|
||||
if ((dlc > CAN_MAX_DLEN) && (dlc <= CAN_MAX_RAW_DLC))
|
||||
cu->cc.len8_dlc = dlc;
|
||||
}
|
||||
|
||||
return mtu;
|
||||
}
|
||||
|
||||
int snprintf_canframe(char *buf, size_t size, cu_t *cu, int sep)
|
||||
{
|
||||
/* documentation see lib.h */
|
||||
|
||||
unsigned char is_canfd = cu->fd.flags;
|
||||
int i, offset;
|
||||
int len;
|
||||
|
||||
/* ensure space for string termination */
|
||||
if (size < 1)
|
||||
return size;
|
||||
|
||||
/* handle CAN XL frames */
|
||||
if (cu->xl.flags & CANXL_XLF) {
|
||||
len = cu->xl.len;
|
||||
|
||||
/* check if the CAN frame fits into the provided buffer */
|
||||
if (sizeof("00123#11:22:12345678#") + 2 * len + (sep ? len : 0) > size - 1) {
|
||||
/* mark buffer overflow in output */
|
||||
memset(buf, '-', size - 1);
|
||||
buf[size - 1] = 0;
|
||||
return size;
|
||||
}
|
||||
|
||||
/* print prio and CAN XL header content */
|
||||
offset = sprintf(buf, "%02X%03X#%02X:%02X:%08X#",
|
||||
(canid_t)(cu->xl.prio & CANXL_VCID_MASK) >> CANXL_VCID_OFFSET,
|
||||
(canid_t)(cu->xl.prio & CANXL_PRIO_MASK),
|
||||
cu->xl.flags, cu->xl.sdt, cu->xl.af);
|
||||
|
||||
/* data */
|
||||
for (i = 0; i < len; i++) {
|
||||
put_hex_byte(buf + offset, cu->xl.data[i]);
|
||||
offset += 2;
|
||||
if (sep && (i + 1 < len))
|
||||
buf[offset++] = '.';
|
||||
}
|
||||
|
||||
buf[offset] = 0;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
/* handle CAN CC/FD frames - ensure max length values */
|
||||
if (is_canfd)
|
||||
len = (cu->fd.len > CANFD_MAX_DLEN) ? CANFD_MAX_DLEN : cu->fd.len;
|
||||
else
|
||||
len = (cu->fd.len > CAN_MAX_DLEN) ? CAN_MAX_DLEN : cu->fd.len;
|
||||
|
||||
/* check if the CAN frame fits into the provided buffer */
|
||||
if (sizeof("12345678#_F") + 2 * len + (sep ? len : 0) + \
|
||||
(cu->fd.can_id & CAN_RTR_FLAG ? 2 : 0) > size - 1) {
|
||||
/* mark buffer overflow in output */
|
||||
memset(buf, '-', size - 1);
|
||||
buf[size - 1] = 0;
|
||||
return size;
|
||||
}
|
||||
|
||||
if (cu->fd.can_id & CAN_ERR_FLAG) {
|
||||
put_eff_id(buf, cu->fd.can_id & (CAN_ERR_MASK | CAN_ERR_FLAG));
|
||||
buf[8] = '#';
|
||||
offset = 9;
|
||||
} else if (cu->fd.can_id & CAN_EFF_FLAG) {
|
||||
put_eff_id(buf, cu->fd.can_id & CAN_EFF_MASK);
|
||||
buf[8] = '#';
|
||||
offset = 9;
|
||||
} else {
|
||||
put_sff_id(buf, cu->fd.can_id & CAN_SFF_MASK);
|
||||
buf[3] = '#';
|
||||
offset = 4;
|
||||
}
|
||||
|
||||
/* CAN CC frames may have RTR enabled. There are no ERR frames with RTR */
|
||||
if (!is_canfd && cu->fd.can_id & CAN_RTR_FLAG) {
|
||||
buf[offset++] = 'R';
|
||||
/* print a given CAN 2.0B DLC if it's not zero */
|
||||
if (len && len <= CAN_MAX_DLEN) {
|
||||
buf[offset++] = hex_asc_upper_lo(cu->fd.len);
|
||||
|
||||
/* check for optional raw DLC value for CAN 2.0B frames */
|
||||
if (len == CAN_MAX_DLEN) {
|
||||
if ((cu->cc.len8_dlc > CAN_MAX_DLEN) && (cu->cc.len8_dlc <= CAN_MAX_RAW_DLC)) {
|
||||
buf[offset++] = CC_DLC_DELIM;
|
||||
buf[offset++] = hex_asc_upper_lo(cu->cc.len8_dlc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf[offset] = 0;
|
||||
return offset;
|
||||
}
|
||||
|
||||
/* any CAN FD flags */
|
||||
if (is_canfd) {
|
||||
/* add CAN FD specific escape char and flags */
|
||||
buf[offset++] = '#';
|
||||
buf[offset++] = hex_asc_upper_lo(cu->fd.flags);
|
||||
if (sep && len)
|
||||
buf[offset++] = '.';
|
||||
}
|
||||
|
||||
/* data */
|
||||
for (i = 0; i < len; i++) {
|
||||
put_hex_byte(buf + offset, cu->fd.data[i]);
|
||||
offset += 2;
|
||||
if (sep && (i + 1 < len))
|
||||
buf[offset++] = '.';
|
||||
}
|
||||
|
||||
/* check for extra DLC when having a Classic CAN with 8 bytes payload */
|
||||
if (!is_canfd && (len == CAN_MAX_DLEN)) {
|
||||
unsigned char dlc = cu->cc.len8_dlc;
|
||||
|
||||
if ((dlc > CAN_MAX_DLEN) && (dlc <= CAN_MAX_RAW_DLC)) {
|
||||
buf[offset++] = CC_DLC_DELIM;
|
||||
buf[offset++] = hex_asc_upper_lo(dlc);
|
||||
}
|
||||
}
|
||||
|
||||
buf[offset] = 0;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
int snprintf_long_canframe(char *buf, size_t size, cu_t *cu, int view)
|
||||
{
|
||||
/* documentation see lib.h */
|
||||
|
||||
unsigned char is_canfd = cu->fd.flags;
|
||||
int i, j, dlen, offset;
|
||||
size_t maxsize;
|
||||
int len;
|
||||
|
||||
/* ensure space for string termination */
|
||||
if (size < 1)
|
||||
return size;
|
||||
|
||||
/* handle CAN XL frames */
|
||||
if (cu->xl.flags & CANXL_XLF) {
|
||||
len = cu->xl.len;
|
||||
|
||||
/* crop to CANFD_MAX_DLEN */
|
||||
if (len > CANFD_MAX_DLEN)
|
||||
dlen = CANFD_MAX_DLEN;
|
||||
else
|
||||
dlen = len;
|
||||
|
||||
/* check if the CAN frame fits into the provided buffer */
|
||||
if (sizeof(".....123 [2048] (00|11:22:12345678) ...") + 3 * dlen > size - 1) {
|
||||
/* mark buffer overflow in output */
|
||||
memset(buf, '-', size - 1);
|
||||
buf[size - 1] = 0;
|
||||
return size;
|
||||
}
|
||||
|
||||
if (view & CANLIB_VIEW_INDENT_SFF) {
|
||||
memset(buf, ' ', 5);
|
||||
put_sff_id(buf + 5, cu->xl.prio & CANXL_PRIO_MASK);
|
||||
offset = 8;
|
||||
} else {
|
||||
put_sff_id(buf, cu->xl.prio & CANXL_PRIO_MASK);
|
||||
offset = 3;
|
||||
}
|
||||
|
||||
/* print prio and CAN XL header content */
|
||||
offset += sprintf(buf + offset, " [%04d] (%02X|%02X:%02X:%08X) ",
|
||||
len,
|
||||
(canid_t)(cu->xl.prio & CANXL_VCID_MASK) >> CANXL_VCID_OFFSET,
|
||||
cu->xl.flags, cu->xl.sdt, cu->xl.af);
|
||||
|
||||
for (i = 0; i < dlen; i++) {
|
||||
put_hex_byte(buf + offset, cu->xl.data[i]);
|
||||
offset += 2;
|
||||
if (i + 1 < dlen)
|
||||
buf[offset++] = ' ';
|
||||
}
|
||||
|
||||
/* indicate cropped output */
|
||||
if (cu->xl.len > dlen)
|
||||
offset += sprintf(buf + offset, " ...");
|
||||
|
||||
buf[offset] = 0;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
/* ensure max length values */
|
||||
if (is_canfd)
|
||||
len = (cu->fd.len > CANFD_MAX_DLEN) ? CANFD_MAX_DLEN : cu->fd.len;
|
||||
else
|
||||
len = (cu->fd.len > CAN_MAX_DLEN) ? CAN_MAX_DLEN : cu->fd.len;
|
||||
|
||||
/* check if the CAN frame fits into the provided buffer */
|
||||
maxsize = sizeof("12345678 [12] ");
|
||||
if (view & CANLIB_VIEW_BINARY)
|
||||
dlen = 9; /* _10101010 */
|
||||
else
|
||||
dlen = 3; /* _AA */
|
||||
|
||||
if (cu->fd.can_id & CAN_RTR_FLAG) {
|
||||
maxsize += sizeof(" remote request");
|
||||
} else {
|
||||
maxsize += len * dlen;
|
||||
|
||||
if (len <= CAN_MAX_DLEN) {
|
||||
if (cu->fd.can_id & CAN_ERR_FLAG) {
|
||||
maxsize += sizeof(" ERRORFRAME");
|
||||
maxsize += (8 - len) * dlen;
|
||||
} else if (view & CANLIB_VIEW_ASCII) {
|
||||
maxsize += sizeof(" 'a.b.CDEF'");
|
||||
maxsize += (8 - len) * dlen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (maxsize > size - 1) {
|
||||
/* mark buffer overflow in output */
|
||||
memset(buf, '-', size - 1);
|
||||
buf[size - 1] = 0;
|
||||
return size;
|
||||
}
|
||||
|
||||
/* initialize space for CAN-ID and length information */
|
||||
memset(buf, ' ', 15);
|
||||
|
||||
if (cu->cc.can_id & CAN_ERR_FLAG) {
|
||||
put_eff_id(buf, cu->cc.can_id & (CAN_ERR_MASK | CAN_ERR_FLAG));
|
||||
offset = 10;
|
||||
} else if (cu->fd.can_id & CAN_EFF_FLAG) {
|
||||
put_eff_id(buf, cu->fd.can_id & CAN_EFF_MASK);
|
||||
offset = 10;
|
||||
} else {
|
||||
if (view & CANLIB_VIEW_INDENT_SFF) {
|
||||
put_sff_id(buf + 5, cu->fd.can_id & CAN_SFF_MASK);
|
||||
offset = 10;
|
||||
} else {
|
||||
put_sff_id(buf, cu->fd.can_id & CAN_SFF_MASK);
|
||||
offset = 5;
|
||||
}
|
||||
}
|
||||
|
||||
/* The len value is sanitized (see above) */
|
||||
if (!is_canfd) {
|
||||
if (view & CANLIB_VIEW_LEN8_DLC) {
|
||||
unsigned char dlc = cu->cc.len8_dlc;
|
||||
|
||||
/* fall back to len if we don't have a valid DLC > 8 */
|
||||
if (!((len == CAN_MAX_DLEN) && (dlc > CAN_MAX_DLEN) &&
|
||||
(dlc <= CAN_MAX_RAW_DLC)))
|
||||
dlc = len;
|
||||
|
||||
buf[offset + 1] = '{';
|
||||
buf[offset + 2] = hex_asc_upper[dlc];
|
||||
buf[offset + 3] = '}';
|
||||
} else {
|
||||
buf[offset + 1] = '[';
|
||||
buf[offset + 2] = len + '0';
|
||||
buf[offset + 3] = ']';
|
||||
}
|
||||
|
||||
/* standard CAN frames may have RTR enabled */
|
||||
if (cu->fd.can_id & CAN_RTR_FLAG) {
|
||||
offset += sprintf(buf + offset + 5, " remote request");
|
||||
return offset + 5;
|
||||
}
|
||||
} else {
|
||||
buf[offset] = '[';
|
||||
buf[offset + 1] = (len / 10) + '0';
|
||||
buf[offset + 2] = (len % 10) + '0';
|
||||
buf[offset + 3] = ']';
|
||||
}
|
||||
offset += 5;
|
||||
|
||||
if (view & CANLIB_VIEW_BINARY) {
|
||||
/* _10101010 - dlen = 9, see above */
|
||||
if (view & CANLIB_VIEW_SWAP) {
|
||||
for (i = len - 1; i >= 0; i--) {
|
||||
buf[offset++] = (i == len - 1) ? ' ' : SWAP_DELIMITER;
|
||||
for (j = 7; j >= 0; j--)
|
||||
buf[offset++] = (1 << j & cu->fd.data[i]) ? '1' : '0';
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < len; i++) {
|
||||
buf[offset++] = ' ';
|
||||
for (j = 7; j >= 0; j--)
|
||||
buf[offset++] = (1 << j & cu->fd.data[i]) ? '1' : '0';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* _AA - dlen = 3, see above */
|
||||
if (view & CANLIB_VIEW_SWAP) {
|
||||
for (i = len - 1; i >= 0; i--) {
|
||||
if (i == len - 1)
|
||||
buf[offset++] = ' ';
|
||||
else
|
||||
buf[offset++] = SWAP_DELIMITER;
|
||||
|
||||
put_hex_byte(buf + offset, cu->fd.data[i]);
|
||||
offset += 2;
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < len; i++) {
|
||||
buf[offset++] = ' ';
|
||||
put_hex_byte(buf + offset, cu->fd.data[i]);
|
||||
offset += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf[offset] = 0; /* terminate string */
|
||||
|
||||
/*
|
||||
* The ASCII & ERRORFRAME output is put at a fixed len behind the data.
|
||||
* For now we support ASCII output only for payload length up to 8 bytes.
|
||||
* Does it make sense to write 64 ASCII byte behind 64 ASCII HEX data on the console?
|
||||
*/
|
||||
if (len > CAN_MAX_DLEN)
|
||||
return offset;
|
||||
|
||||
if (cu->fd.can_id & CAN_ERR_FLAG)
|
||||
offset += sprintf(buf + offset, "%*s", dlen * (8 - len) + 13, "ERRORFRAME");
|
||||
else if (view & CANLIB_VIEW_ASCII) {
|
||||
j = dlen * (8 - len) + 4;
|
||||
if (view & CANLIB_VIEW_SWAP) {
|
||||
sprintf(buf + offset, "%*s", j, "`");
|
||||
offset += j;
|
||||
for (i = len - 1; i >= 0; i--)
|
||||
if ((cu->fd.data[i] > 0x1F) && (cu->fd.data[i] < 0x7F))
|
||||
buf[offset++] = cu->fd.data[i];
|
||||
else
|
||||
buf[offset++] = '.';
|
||||
|
||||
offset += sprintf(buf + offset, "`");
|
||||
} else {
|
||||
sprintf(buf + offset, "%*s", j, "'");
|
||||
offset += j;
|
||||
for (i = 0; i < len; i++)
|
||||
if ((cu->fd.data[i] > 0x1F) && (cu->fd.data[i] < 0x7F))
|
||||
buf[offset++] = cu->fd.data[i];
|
||||
else
|
||||
buf[offset++] = '.';
|
||||
|
||||
offset += sprintf(buf + offset, "'");
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
static const char *error_classes[] = {
|
||||
"tx-timeout",
|
||||
"lost-arbitration",
|
||||
"controller-problem",
|
||||
"protocol-violation",
|
||||
"transceiver-status",
|
||||
"no-acknowledgement-on-tx",
|
||||
"bus-off",
|
||||
"bus-error",
|
||||
"restarted-after-bus-off",
|
||||
"error-counter-tx-rx",
|
||||
};
|
||||
|
||||
static const char *controller_problems[] = {
|
||||
"rx-overflow",
|
||||
"tx-overflow",
|
||||
"rx-error-warning",
|
||||
"tx-error-warning",
|
||||
"rx-error-passive",
|
||||
"tx-error-passive",
|
||||
"back-to-error-active",
|
||||
};
|
||||
|
||||
static const char *protocol_violation_types[] = {
|
||||
"single-bit-error",
|
||||
"frame-format-error",
|
||||
"bit-stuffing-error",
|
||||
"tx-dominant-bit-error",
|
||||
"tx-recessive-bit-error",
|
||||
"bus-overload",
|
||||
"active-error",
|
||||
"error-on-tx",
|
||||
};
|
||||
|
||||
static const char *protocol_violation_locations[] = {
|
||||
"unspecified",
|
||||
"unspecified",
|
||||
"id.28-to-id.21",
|
||||
"start-of-frame",
|
||||
"bit-srtr",
|
||||
"bit-ide",
|
||||
"id.20-to-id.18",
|
||||
"id.17-to-id.13",
|
||||
"crc-sequence",
|
||||
"reserved-bit-0",
|
||||
"data-field",
|
||||
"data-length-code",
|
||||
"bit-rtr",
|
||||
"reserved-bit-1",
|
||||
"id.4-to-id.0",
|
||||
"id.12-to-id.5",
|
||||
"unspecified",
|
||||
"active-error-flag",
|
||||
"intermission",
|
||||
"tolerate-dominant-bits",
|
||||
"unspecified",
|
||||
"unspecified",
|
||||
"passive-error-flag",
|
||||
"error-delimiter",
|
||||
"crc-delimiter",
|
||||
"acknowledge-slot",
|
||||
"end-of-frame",
|
||||
"acknowledge-delimiter",
|
||||
"overload-flag",
|
||||
"unspecified",
|
||||
"unspecified",
|
||||
"unspecified",
|
||||
};
|
||||
|
||||
#ifndef ARRAY_SIZE
|
||||
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
|
||||
#endif
|
||||
|
||||
static int snprintf_error_data(char *buf, size_t len, uint8_t err,
|
||||
const char **arr, int arr_len)
|
||||
{
|
||||
int i, n = 0, count = 0;
|
||||
|
||||
if (!err || len <= 0)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < arr_len; i++) {
|
||||
if (err & (1 << i)) {
|
||||
int tmp_n = 0;
|
||||
if (count) {
|
||||
/* Fix for potential buffer overflow https://lgtm.com/rules/1505913226124/ */
|
||||
tmp_n = snprintf(buf + n, len - n, ",");
|
||||
if (tmp_n < 0 || (size_t)tmp_n >= len - n) {
|
||||
return n;
|
||||
}
|
||||
n += tmp_n;
|
||||
}
|
||||
tmp_n = snprintf(buf + n, len - n, "%s", arr[i]);
|
||||
if (tmp_n < 0 || (size_t)tmp_n >= len - n) {
|
||||
return n;
|
||||
}
|
||||
n += tmp_n;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static int snprintf_error_lostarb(char *buf, size_t len, const struct canfd_frame *cf)
|
||||
{
|
||||
if (len <= 0)
|
||||
return 0;
|
||||
return snprintf(buf, len, "{at bit %d}", cf->data[0]);
|
||||
}
|
||||
|
||||
static int snprintf_error_ctrl(char *buf, size_t len, const struct canfd_frame *cf)
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
if (len <= 0)
|
||||
return 0;
|
||||
|
||||
n += snprintf(buf + n, len - n, "{");
|
||||
n += snprintf_error_data(buf + n, len - n, cf->data[1],
|
||||
controller_problems,
|
||||
ARRAY_SIZE(controller_problems));
|
||||
n += snprintf(buf + n, len - n, "}");
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static int snprintf_error_prot(char *buf, size_t len, const struct canfd_frame *cf)
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
if (len <= 0)
|
||||
return 0;
|
||||
|
||||
n += snprintf(buf + n, len - n, "{{");
|
||||
n += snprintf_error_data(buf + n, len - n, cf->data[2],
|
||||
protocol_violation_types,
|
||||
ARRAY_SIZE(protocol_violation_types));
|
||||
n += snprintf(buf + n, len - n, "}{");
|
||||
if (cf->data[3] > 0 &&
|
||||
cf->data[3] < ARRAY_SIZE(protocol_violation_locations))
|
||||
n += snprintf(buf + n, len - n, "%s",
|
||||
protocol_violation_locations[cf->data[3]]);
|
||||
n += snprintf(buf + n, len - n, "}}");
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static int snprintf_error_cnt(char *buf, size_t len, const struct canfd_frame *cf)
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
if (len <= 0)
|
||||
return 0;
|
||||
|
||||
n += snprintf(buf + n, len - n, "{{%d}{%d}}",
|
||||
cf->data[6], cf->data[7]);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
int snprintf_can_error_frame(char *buf, size_t len, const struct canfd_frame *cf,
|
||||
const char* sep)
|
||||
{
|
||||
canid_t class, mask;
|
||||
int i, n = 0, classes = 0;
|
||||
char *defsep = ",";
|
||||
|
||||
if (!(cf->can_id & CAN_ERR_FLAG))
|
||||
return 0;
|
||||
|
||||
class = cf->can_id & CAN_EFF_MASK;
|
||||
if (class > (1 << ARRAY_SIZE(error_classes))) {
|
||||
fprintf(stderr, "Error class %#x is invalid\n", class);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!sep)
|
||||
sep = defsep;
|
||||
|
||||
for (i = 0; i < (int)ARRAY_SIZE(error_classes); i++) {
|
||||
mask = 1 << i;
|
||||
if (class & mask) {
|
||||
int tmp_n = 0;
|
||||
if (classes) {
|
||||
/* Fix for potential buffer overflow https://lgtm.com/rules/1505913226124/ */
|
||||
tmp_n = snprintf(buf + n, len - n, "%s", sep);
|
||||
if (tmp_n < 0 || (size_t)tmp_n >= len - n) {
|
||||
buf[0] = 0; /* empty terminated string */
|
||||
return 0;
|
||||
}
|
||||
n += tmp_n;
|
||||
}
|
||||
tmp_n = snprintf(buf + n, len - n, "%s", error_classes[i]);
|
||||
if (tmp_n < 0 || (size_t)tmp_n >= len - n) {
|
||||
buf[0] = 0; /* empty terminated string */
|
||||
return 0;
|
||||
}
|
||||
n += tmp_n;
|
||||
if (mask == CAN_ERR_LOSTARB)
|
||||
n += snprintf_error_lostarb(buf + n, len - n,
|
||||
cf);
|
||||
if (mask == CAN_ERR_CRTL)
|
||||
n += snprintf_error_ctrl(buf + n, len - n, cf);
|
||||
if (mask == CAN_ERR_PROT)
|
||||
n += snprintf_error_prot(buf + n, len - n, cf);
|
||||
if (mask == CAN_ERR_CNT)
|
||||
n += snprintf_error_cnt(buf + n, len - n, cf);
|
||||
classes++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(cf->can_id & CAN_ERR_CNT) && (cf->data[6] || cf->data[7])) {
|
||||
n += snprintf(buf + n, len - n, "%serror-counter-tx-rx", sep);
|
||||
n += snprintf_error_cnt(buf + n, len - n, cf);
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
int64_t timespec_diff_ms(struct timespec *ts1,
|
||||
struct timespec *ts2)
|
||||
{
|
||||
int64_t diff = (ts1->tv_sec - ts2->tv_sec) * 1000;
|
||||
|
||||
diff += (ts1->tv_nsec - ts2->tv_nsec) / 1000000;
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
void timespec_add_ms(struct timespec *ts, uint64_t milliseconds)
|
||||
{
|
||||
uint64_t total_ns = ts->tv_nsec + (milliseconds * 1000000);
|
||||
|
||||
ts->tv_sec += total_ns / 1000000000;
|
||||
ts->tv_nsec = total_ns % 1000000000;
|
||||
}
|
||||
261
Devices/Libraries/Systems/can-utils/lib.h
Executable file
261
Devices/Libraries/Systems/can-utils/lib.h
Executable file
@@ -0,0 +1,261 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
|
||||
/*
|
||||
* lib.h - library include for command line tools
|
||||
*
|
||||
* Copyright (c) 2002-2007 Volkswagen Group Electronic Research
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of Volkswagen nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* Alternatively, provided that this notice is retained in full, this
|
||||
* software may be distributed under the terms of the GNU General
|
||||
* Public License ("GPL") version 2, in which case the provisions of the
|
||||
* GPL apply INSTEAD OF those given above.
|
||||
*
|
||||
* The provided data structures and external interfaces from this code
|
||||
* are not restricted to be used by modules with a GPL compatible license.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* Send feedback to <linux-can@vger.kernel.org>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CAN_UTILS_LIB_H
|
||||
#define CAN_UTILS_LIB_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef DEBUG
|
||||
#define pr_debug(fmt, args...) printf(fmt, ##args)
|
||||
#else
|
||||
__attribute__((format (printf, 1, 2)))
|
||||
static inline int pr_debug(const char* fmt, ...) {return 0;}
|
||||
#endif
|
||||
|
||||
/* CAN CC/FD/XL frame union */
|
||||
typedef union {
|
||||
struct can_frame cc;
|
||||
struct canfd_frame fd;
|
||||
struct canxl_frame xl;
|
||||
} cu_t;
|
||||
|
||||
/*
|
||||
* The buffer size for ASCII CAN frame string representations
|
||||
* covers also the 'long' CAN frame output from sprint_long_canframe()
|
||||
* including (swapped) binary represetations, timestamps, netdevice names,
|
||||
* lengths and error message details as the CAN XL data is cropped to 64
|
||||
* byte (the 'long' CAN frame output is only for display on terminals).
|
||||
*/
|
||||
#define AFRSZ 6300 /* 3*2048 (data) + 22 (timestamp) + 18 (netdev) + ID/HDR */
|
||||
|
||||
/* CAN DLC to real data length conversion helpers especially for CAN FD */
|
||||
|
||||
/* get data length from raw data length code (DLC) */
|
||||
unsigned char can_fd_dlc2len(unsigned char dlc);
|
||||
|
||||
/* map the sanitized data length to an appropriate data length code */
|
||||
unsigned char can_fd_len2dlc(unsigned char len);
|
||||
|
||||
unsigned char asc2nibble(char c);
|
||||
/*
|
||||
* Returns the decimal value of a given ASCII hex character.
|
||||
*
|
||||
* While 0..9, a..f, A..F are valid ASCII hex characters.
|
||||
* On invalid characters the value 16 is returned for error handling.
|
||||
*/
|
||||
|
||||
int hexstring2data(char *arg, unsigned char *data, int maxdlen);
|
||||
/*
|
||||
* Converts a given ASCII hex string to a (binary) byte string.
|
||||
*
|
||||
* A valid ASCII hex string consists of an even number of up to 16 chars.
|
||||
* Leading zeros '00' in the ASCII hex string are interpreted.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* "1234" => data[0] = 0x12, data[1] = 0x34
|
||||
* "001234" => data[0] = 0x00, data[1] = 0x12, data[2] = 0x34
|
||||
*
|
||||
* Return values:
|
||||
* 0 = success
|
||||
* 1 = error (in length or the given characters are no ASCII hex characters)
|
||||
*
|
||||
* Remark: The not written data[] elements are initialized with zero.
|
||||
*
|
||||
*/
|
||||
|
||||
int parse_canframe(char *cs, cu_t *cu);
|
||||
/*
|
||||
* Transfers a valid ASCII string describing a CAN frame into the CAN union
|
||||
* containing CAN CC/FD/XL structs.
|
||||
*
|
||||
* CAN CC frames (aka Classical CAN, CAN 2.0B)
|
||||
* - string layout <can_id>#{R{len}|data}{_len8_dlc}
|
||||
* - {data} has 0 to 8 hex-values that can (optionally) be separated by '.'
|
||||
* - {len} can take values from 0 to 8 and can be omitted if zero
|
||||
* - {_len8_dlc} can take hex values from '_9' to '_F' when len is CAN_MAX_DLEN
|
||||
* - return value on successful parsing: CAN_MTU
|
||||
*
|
||||
* CAN FD frames
|
||||
* - string layout <can_id>##<flags>{data}
|
||||
* - <flags> a single ASCII Hex value (0 .. F) which defines canfd_frame.flags
|
||||
* - {data} has 0 to 64 hex-values that can (optionally) be separated by '.'
|
||||
* - return value on successful parsing: CANFD_MTU
|
||||
*
|
||||
* CAN XL frames
|
||||
* - string layout <vcid><prio>#<flags>:<sdt>:<af>#{data}
|
||||
* - <vcid> a two ASCII Hex value (00 .. FF) which defines the VCID
|
||||
* - <prio> a three ASCII Hex value (000 .. 7FF) which defines the 11 bit PRIO
|
||||
* - <flags> a two ASCII Hex value (00 .. FF) which defines canxl_frame.flags
|
||||
* - <sdt> a two ASCII Hex value (00 .. FF) which defines canxl_frame.sdt
|
||||
* - <af> a 8 digit ASCII Hex value which defines the 32 bit canxl_frame.af
|
||||
* - {data} has 1 to 2048 hex-values that can (optionally) be separated by '.'
|
||||
* - return value on successful parsing: CANXL_MTU
|
||||
*
|
||||
* Return value on detected problems: 0
|
||||
*
|
||||
* <can_id> can have 3 (standard frame format) or 8 (extended frame format)
|
||||
* hexadecimal chars
|
||||
*
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* 123# -> standard CAN-Id = 0x123, len = 0
|
||||
* 12345678# -> extended CAN-Id = 0x12345678, len = 0
|
||||
* 123#R -> standard CAN-Id = 0x123, len = 0, RTR-frame
|
||||
* 123#R0 -> standard CAN-Id = 0x123, len = 0, RTR-frame
|
||||
* 123#R7 -> standard CAN-Id = 0x123, len = 7, RTR-frame
|
||||
* 123#R8_9 -> standard CAN-Id = 0x123, len = 8, dlc = 9, RTR-frame
|
||||
* 7A1#r -> standard CAN-Id = 0x7A1, len = 0, RTR-frame
|
||||
*
|
||||
* 123#00 -> standard CAN-Id = 0x123, len = 1, data[0] = 0x00
|
||||
* 123#1122334455667788 -> standard CAN-Id = 0x123, len = 8
|
||||
* 123#1122334455667788_E -> standard CAN-Id = 0x123, len = 8, dlc = 14
|
||||
* 123#11.22.33.44.55.66.77.88 -> standard CAN-Id = 0x123, len = 8
|
||||
* 123#11.2233.44556677.88 -> standard CAN-Id = 0x123, len = 8
|
||||
* 32345678#112233 -> error frame with CAN_ERR_FLAG (0x2000000) set
|
||||
*
|
||||
* 123##0112233 -> CAN FD frame standard CAN-Id = 0x123, flags = 0, len = 3
|
||||
* 123##1112233 -> CAN FD frame, flags = CANFD_BRS, len = 3
|
||||
* 123##2112233 -> CAN FD frame, flags = CANFD_ESI, len = 3
|
||||
* 123##3 -> CAN FD frame, flags = (CANFD_ESI | CANFD_BRS), len = 0
|
||||
* ^^
|
||||
* CAN FD extension to handle the canfd_frame.flags content
|
||||
*
|
||||
* 45123#81:00:12345678#11223344.556677 -> CAN XL frame with len = 7,
|
||||
* VCID = 0x45, PRIO = 0x123, flags = 0x81, sdt = 0x00, af = 0x12345678
|
||||
*
|
||||
* Simple facts on this compact ASCII CAN frame representation:
|
||||
*
|
||||
* - 3 digits: standard frame format
|
||||
* - 8 digits: extendend frame format OR error frame
|
||||
* - 8 digits with CAN_ERR_FLAG (0x2000000) set: error frame
|
||||
* - an error frame is never a RTR frame
|
||||
* - CAN FD frames do not have a RTR bit
|
||||
*/
|
||||
|
||||
int snprintf_canframe(char *buf, size_t size, cu_t *cu, int sep);
|
||||
/*
|
||||
* Creates a CAN frame hexadecimal output in compact format.
|
||||
* The CAN data[] is separated by '.' when sep != 0.
|
||||
*
|
||||
* A CAN XL frame is detected when CANXL_XLF is set in the struct
|
||||
* cu.canxl_frame.flags. Otherwise the type of the CAN frame (CAN CC/FD)
|
||||
* is specified by the dual-use struct cu.canfd_frame.flags element:
|
||||
* w/o CAN FD flags (== 0) -> CAN CC frame (aka Classical CAN, CAN2.0B)
|
||||
* with CAN FD flags (!= 0) -> CAN FD frame (with CANFD_[FDF/BRS/ESI])
|
||||
*
|
||||
* 12345678#112233 -> extended CAN-Id = 0x12345678, len = 3, data, sep = 0
|
||||
* 123#1122334455667788_E -> standard CAN-Id = 0x123, len = 8, dlc = 14, data, sep = 0
|
||||
* 12345678#R -> extended CAN-Id = 0x12345678, RTR, len = 0
|
||||
* 12345678#R5 -> extended CAN-Id = 0x12345678, RTR, len = 5
|
||||
* 123#11.22.33.44.55.66.77.88 -> standard CAN-Id = 0x123, dlc = 8, sep = 1
|
||||
* 32345678#112233 -> error frame with CAN_ERR_FLAG (0x2000000) set
|
||||
* 123##0112233 -> CAN FD frame standard CAN-Id = 0x123, flags = 0, len = 3
|
||||
* 123##2112233 -> CAN FD frame, flags = CANFD_ESI, len = 3
|
||||
* 45123#81:00:12345678#11223344.556677 -> CAN XL frame with len = 7,
|
||||
* VCID = 0x45, PRIO = 0x123, flags = 0x81, sdt = 0x00, af = 0x12345678
|
||||
*
|
||||
*/
|
||||
|
||||
#define CANLIB_VIEW_ASCII 0x1
|
||||
#define CANLIB_VIEW_BINARY 0x2
|
||||
#define CANLIB_VIEW_SWAP 0x4
|
||||
#define CANLIB_VIEW_ERROR 0x8
|
||||
#define CANLIB_VIEW_INDENT_SFF 0x10
|
||||
#define CANLIB_VIEW_LEN8_DLC 0x20
|
||||
|
||||
#define SWAP_DELIMITER '`'
|
||||
|
||||
int snprintf_long_canframe(char *buf, size_t size, cu_t *cu, int view);
|
||||
/*
|
||||
* Creates a CAN frame hexadecimal output in user readable format.
|
||||
*
|
||||
* A CAN XL frame is detected when CANXL_XLF is set in the struct
|
||||
* cu.canxl_frame.flags. Otherwise the type of the CAN frame (CAN CC/FD)
|
||||
* is specified by the dual-use struct cu.canfd_frame.flags element:
|
||||
* w/o CAN FD flags (== 0) -> CAN CC frame (aka Classical CAN, CAN2.0B)
|
||||
* with CAN FD flags (!= 0) -> CAN FD frame (with CANFD_[FDF/BRS/ESI])
|
||||
*
|
||||
* 12345678 [3] 11 22 33 -> extended CAN-Id = 0x12345678, len = 3, data
|
||||
* 12345678 [0] remote request -> extended CAN-Id = 0x12345678, RTR
|
||||
* 14B0DC51 [8] 4A 94 E8 2A EC 58 55 62 'J..*.XUb' -> (with ASCII output)
|
||||
* 321 {B} 11 22 33 44 55 66 77 88 -> Classical CAN with raw '{DLC}' value B
|
||||
* 20001111 [7] C6 23 7B 32 69 98 3C ERRORFRAME -> (CAN_ERR_FLAG set)
|
||||
* 12345678 [03] 11 22 33 -> CAN FD with extended CAN-Id = 0x12345678, len = 3
|
||||
* 123 [0003] (45|81:00:12345678) 11 22 33 -> CAN XL frame with VCID 0x45
|
||||
*
|
||||
* 123 [3] 11 22 33 -> CANLIB_VIEW_INDENT_SFF == 0
|
||||
* 123 [3] 11 22 33 -> CANLIB_VIEW_INDENT_SFF == set
|
||||
*
|
||||
* There are no binary or ASCII view modes for CAN XL and the number of displayed
|
||||
* data bytes is limited to 64 to fit terminal output use-cases.
|
||||
*/
|
||||
|
||||
int snprintf_can_error_frame(char *buf, size_t len, const struct canfd_frame *cf,
|
||||
const char *sep);
|
||||
/*
|
||||
* Creates a CAN error frame output in user readable format.
|
||||
*/
|
||||
|
||||
/**
|
||||
* timespec_diff_ms - calculate timespec difference in milliseconds
|
||||
* @ts1: first timespec
|
||||
* @ts2: second timespec
|
||||
*
|
||||
* Return negative difference if in the past.
|
||||
*/
|
||||
int64_t timespec_diff_ms(struct timespec *ts1, struct timespec *ts2);
|
||||
|
||||
/**
|
||||
* timespec_add_ms - add milliseconds to timespec
|
||||
* @ts: timespec
|
||||
* @milliseconds: milliseconds to add
|
||||
*/
|
||||
void timespec_add_ms(struct timespec *ts, uint64_t milliseconds);
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user