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