xref: /aosp_15_r20/hardware/interfaces/automotive/can/aidl/default/CanBusSlcan.cpp (revision 4d7e907c777eeecc4c5bd7cf640a754fac206ff7)
1 /*
2  * Copyright 2022, The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "CanBusSlcan.h"
18 
19 #include <android-base/file.h>
20 #include <android-base/logging.h>
21 #include <libnetdevice/libnetdevice.h>
22 
23 #include <linux/serial.h>
24 #include <linux/tty.h>
25 #include <net/if.h>
26 #include <termios.h>
27 
28 #include <map>
29 
30 namespace aidl::android::hardware::automotive::can {
31 
32 using namespace std::string_view_literals;
33 using namespace ::android::base;
34 
35 namespace slcanprotocol {
36 static constexpr std::string_view kOpenCommand = "O\r"sv;
37 static constexpr std::string_view kCloseCommand = "C\r"sv;
38 static constexpr int kSlcanDiscipline = N_SLCAN;
39 static constexpr int kDefaultDiscipline = N_TTY;
40 
41 static const std::map<uint32_t, std::string_view> kBitrateCommands = {
42         {10000, "C\rS0\r"sv},  {20000, "C\rS1\r"sv},  {50000, "C\rS2\r"sv},
43         {100000, "C\rS3\r"sv}, {125000, "C\rS4\r"sv}, {250000, "C\rS5\r"sv},
44         {500000, "C\rS6\r"sv}, {800000, "C\rS7\r"sv}, {1000000, "C\rS8\r"sv}};
45 }  // namespace slcanprotocol
46 
47 /**
48  * Serial Line CAN constructor
49  * \param string uartName - name of slcan device (e.x. /dev/ttyUSB0)
50  * \param uint32_t bitrate - speed of the CAN bus (125k = MSCAN, 500k = HSCAN)
51  */
CanBusSlcan(const std::string & uartName,uint32_t bitrate)52 CanBusSlcan::CanBusSlcan(const std::string& uartName, uint32_t bitrate)
53     : CanBus(), mTtyPath(uartName), kBitrate(bitrate) {}
54 
55 /** helper function to update CanBusSlcan object's iface name */
updateIfaceName(unique_fd & uartFd)56 Result CanBusSlcan::updateIfaceName(unique_fd& uartFd) {
57     struct ifreq ifrequest = {};
58     /*
59      * Fetching the iface name with an ioctl won't interfere with an open socketCAN iface attached
60      * to this tty. This is important in the event we are trying to register a SLCAN based iface
61      * that has already been configured and brought up.
62      */
63     if (ioctl(uartFd.get(), SIOCGIFNAME, ifrequest.ifr_name) < 0) {
64         PLOG(ERROR) << "Failed to get the name of the created device";
65         return Result::UNKNOWN_ERROR;
66     }
67 
68     // Update the CanBus object with name that was assigned to it
69     mIfname = ifrequest.ifr_name;
70     return Result::OK;
71 }
72 
preUp()73 Result CanBusSlcan::preUp() {
74     // verify valid bitrate and translate to serial command format
75     std::optional<std::string_view> canBitrateCommand = std::nullopt;
76     if (kBitrate != 0) {
77         const auto lookupIt = slcanprotocol::kBitrateCommands.find(kBitrate);
78         if (lookupIt == slcanprotocol::kBitrateCommands.end()) {
79             return Result::BAD_BITRATE;
80         }
81         canBitrateCommand = lookupIt->second;
82     }
83 
84     /* Attempt to open the uart in r/w without blocking or becoming the
85      * controlling terminal */
86     mFd = unique_fd(open(mTtyPath.c_str(), O_RDWR | O_NONBLOCK | O_NOCTTY | O_CLOEXEC));
87     if (!mFd.ok()) {
88         PLOG(ERROR) << "SLCAN Failed to open " << mTtyPath;
89         return Result::BAD_INTERFACE_ID;
90     }
91 
92     // If the device is already up, update the iface name in our CanBusSlcan object
93     if (kBitrate == 0) {
94         return updateIfaceName(mFd);
95     }
96 
97     // blank terminal settings and pull them from the device
98     struct termios terminalSettings = {};
99     if (tcgetattr(mFd.get(), &terminalSettings) < 0) {
100         PLOG(ERROR) << "Failed to read attrs of" << mTtyPath;
101         return Result::UNKNOWN_ERROR;
102     }
103 
104     // change settings to raw mode
105     cfmakeraw(&terminalSettings);
106 
107     // disable software flow control
108     terminalSettings.c_iflag &= ~IXOFF;
109     // enable hardware flow control
110     terminalSettings.c_cflag |= CRTSCTS;
111 
112     struct serial_struct serialSettings;
113     // get serial settings
114     if (ioctl(mFd.get(), TIOCGSERIAL, &serialSettings) < 0) {
115         PLOG(ERROR) << "Failed to read serial settings from " << mTtyPath;
116         return Result::UNKNOWN_ERROR;
117     }
118     // set low latency mode
119     serialSettings.flags |= ASYNC_LOW_LATENCY;
120     // apply serial settings
121     if (ioctl(mFd.get(), TIOCSSERIAL, &serialSettings) < 0) {
122         PLOG(ERROR) << "Failed to set low latency mode on " << mTtyPath;
123         return Result::UNKNOWN_ERROR;
124     }
125 
126     /* TCSADRAIN applies settings after we finish writing the rest of our
127      * changes (as opposed to TCSANOW, which changes immediately) */
128     if (tcsetattr(mFd.get(), TCSADRAIN, &terminalSettings) < 0) {
129         PLOG(ERROR) << "Failed to apply terminal settings to " << mTtyPath;
130         return Result::UNKNOWN_ERROR;
131     }
132 
133     // apply speed setting for CAN
134     if (!WriteStringToFd(*canBitrateCommand, mFd)) {
135         PLOG(ERROR) << "Failed to apply CAN bitrate";
136         return Result::UNKNOWN_ERROR;
137     }
138 
139     // TODO(b/144775286): set open flag & support listen only
140     if (!WriteStringToFd(slcanprotocol::kOpenCommand, mFd)) {
141         PLOG(ERROR) << "Failed to set open flag";
142         return Result::UNKNOWN_ERROR;
143     }
144 
145     // set line discipline to slcan
146     if (ioctl(mFd.get(), TIOCSETD, &slcanprotocol::kSlcanDiscipline) < 0) {
147         PLOG(ERROR) << "Failed to set line discipline to slcan";
148         return Result::UNKNOWN_ERROR;
149     }
150 
151     // Update the CanBus object with name that was assigned to it
152     return updateIfaceName(mFd);
153 }
154 
postDown()155 bool CanBusSlcan::postDown() {
156     // reset the line discipline to TTY mode
157     if (ioctl(mFd.get(), TIOCSETD, &slcanprotocol::kDefaultDiscipline) < 0) {
158         LOG(ERROR) << "Failed to reset line discipline!";
159         return false;
160     }
161 
162     // issue the close command
163     if (!WriteStringToFd(slcanprotocol::kCloseCommand, mFd)) {
164         LOG(ERROR) << "Failed to close tty!";
165         return false;
166     }
167 
168     // close our unique_fd
169     mFd.reset();
170 
171     return true;
172 }
173 
174 }  // namespace aidl::android::hardware::automotive::can
175