git commit -m "first commit for v2"

This commit is contained in:
2025-12-29 16:21:22 +07:00
commit aa3d832d5c
1807 changed files with 307078 additions and 0 deletions

View 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

View 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

View 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

View 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

View 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

View 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

View 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"
)

View 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.

View 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.

View 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

View 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 $@

View 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)

View 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;
}

View 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;
}

View File

@@ -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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

File diff suppressed because it is too large Load Diff

View 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 */

View 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 dont 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!

View 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

View 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

View 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

View 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;
}

View 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,
&timestamping_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,
&timestamp_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;
}

View 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;
}

View 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 */
}

View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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;
}

View 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;
}

View 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;
}

View 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);
}

View 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 */
}

View 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

View 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)

View 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)

View File

@@ -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)

View 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)

View 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()

View 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)

View 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;
}

View 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 */

View 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 */

View 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 */

View 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 */

View 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 */

View 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_ */

View 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 */

View 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 */

View 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

View 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 */

View 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 */

View 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 */

View 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 */

View 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, &current_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;
}

View 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 */

View 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;
}

View 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;
}

View 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;
}

File diff suppressed because it is too large Load Diff

View 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, &current_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, &current_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, &current_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, &current_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, &current_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, &current_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, &current_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, &current_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;
}
}
}

View 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;
}
}

View 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_ */

View 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 */

View 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;
}

View 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 */

View 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 */

View 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 */

View 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 */

View 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

View 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

View 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;
}

View 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 */

View 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;
}

View 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;
}

View 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;
}

View 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 2n: 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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_ */

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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