xref: /aosp_15_r20/hardware/interfaces/automotive/can/aidl/default/CanController.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 "CanController.h"
18 
19 #include "CanBusNative.h"
20 #include "CanBusSlcan.h"
21 #include "CanBusVirtual.h"
22 
23 #include <android-base/format.h>
24 #include <android-base/logging.h>
25 
26 #include <filesystem>
27 #include <fstream>
28 #include <regex>
29 
30 namespace aidl::android::hardware::automotive::can {
31 
32 namespace fs = ::std::filesystem;
33 
34 namespace fsErrors {
35 static const std::error_code ok;
36 static const std::error_code eperm(EPERM, std::generic_category());
37 static const std::error_code enoent(ENOENT, std::generic_category());
38 static const std::error_code eacces(EACCES, std::generic_category());
39 }  // namespace fsErrors
40 
41 /* In the /sys/devices tree, there are files called "serial", which contain the serial numbers
42  * for various devices. The exact location inside of this directory is dependent upon the
43  * hardware we are running on, so we have to start from /sys/devices and work our way down. */
44 static const fs::path kDevPath("/sys/devices/");
45 static const std::regex kTtyRe("^tty[A-Z]+[0-9]+$");
46 static constexpr auto kOpts = ~(fs::directory_options::follow_directory_symlink |
47                                 fs::directory_options::skip_permission_denied);
48 
49 constexpr auto ok = &ndk::ScopedAStatus::ok;
50 
51 /**
52  * A helper object to associate the interface name and type of a USB to CAN adapter.
53  */
54 struct UsbCanIface {
55     InterfaceType iftype;
56     std::string ifaceName;
57 };
58 
isValidName(const std::string & name)59 static bool isValidName(const std::string& name) {
60     static const std::regex nameRE("^[a-zA-Z0-9_]{1,32}$");
61     return std::regex_match(name, nameRE);
62 }
63 
64 /**
65  * Given a path, get the last element from it.
66  *
67  * \param itrPath - the path we want the last element of
68  * \return - the last element in the path (in string form).
69  */
getLeaf(const fs::path & itrPath)70 static std::string getLeaf(const fs::path& itrPath) {
71     /* end() returns an iterator one past the leaf of the path, so we've overshot
72     decrement (--) to go back one to the leaf
73     dereference and now we have our leaf. */
74     return *(--(itrPath.end()));
75 }
76 
resultToStatus(Result res,const std::string & msg="")77 static ndk::ScopedAStatus resultToStatus(Result res, const std::string& msg = "") {
78     if (msg.empty()) {
79         return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(static_cast<int>(res)));
80     }
81     return ndk::ScopedAStatus(
82             AStatus_fromServiceSpecificErrorWithMessage(static_cast<int>(res), msg.c_str()));
83 }
84 
85 /**
86  * Given a UsbCanIface object, get the ifaceName given the serialPath.
87  *
88  * \param serialPath - Absolute path to a "serial" file for a given device in /sys.
89  * \return A populated UsbCanIface. On failure, nullopt is returned.
90  */
getIfaceName(const fs::path & serialPath)91 static std::optional<UsbCanIface> getIfaceName(const fs::path& serialPath) {
92     std::error_code fsStatus;
93     // Since the path is to a file called "serial", we need to search its parent directory.
94     fs::recursive_directory_iterator fsItr(serialPath.parent_path(), kOpts, fsStatus);
95     if (fsStatus != fsErrors::ok) {
96         LOG(ERROR) << "Failed to open " << serialPath.parent_path();
97         return std::nullopt;
98     }
99 
100     for (; fsStatus == fsErrors::ok && fsItr != fs::recursive_directory_iterator();
101          fsItr.increment(fsStatus)) {
102         /* We want either a directory called "net" or a directory that looks like tty<something>, so
103          * skip files. */
104         bool isDir = fsItr->is_directory(fsStatus);
105         if (fsStatus != fsErrors::ok || !isDir) continue;
106 
107         std::string currentDir = getLeaf(fsItr->path());
108         if (currentDir == "net") {
109             /* This device is a SocketCAN device. The iface name is the only directory under
110              * net/. Multiple directories under net/ is an error.*/
111             fs::directory_iterator netItr(fsItr->path(), kOpts, fsStatus);
112             if (fsStatus != fsErrors::ok) {
113                 LOG(ERROR) << "Failed to open " << fsItr->path() << " to get net name!";
114                 return std::nullopt;
115             }
116 
117             // The leaf of our path should be the interface name.
118             std::string netName = getLeaf(netItr->path());
119 
120             // Check if there is more than one item in net/
121             netItr.increment(fsStatus);
122             if (fsStatus != fsErrors::ok) {
123                 // It's possible we have a valid net name, but this is most likely an error.
124                 LOG(ERROR) << "Failed to verify " << fsItr->path() << " has valid net name!";
125                 return std::nullopt;
126             }
127             if (netItr != fs::directory_iterator()) {
128                 // There should never be more than one name under net/
129                 LOG(ERROR) << "Found more than one net name in " << fsItr->path() << "!";
130                 return std::nullopt;
131             }
132             return {{InterfaceType::NATIVE, netName}};
133         } else if (std::regex_match(currentDir, kTtyRe)) {
134             // This device is a USB serial device, and currentDir is the tty name.
135             return {{InterfaceType::SLCAN, "/dev/" + currentDir}};
136         }
137     }
138 
139     // check if the loop above exited due to a c++fs error.
140     if (fsStatus != fsErrors::ok) {
141         LOG(ERROR) << "Failed search filesystem: " << fsStatus;
142     }
143     return std::nullopt;
144 }
145 
146 /**
147  * A helper function to read the serial number from a "serial" file in /sys/devices/
148  *
149  * \param serialnoPath - path to the file to read.
150  * \return the serial number, or nullopt on failure.
151  */
readSerialNo(const std::string & serialnoPath)152 static std::optional<std::string> readSerialNo(const std::string& serialnoPath) {
153     std::ifstream serialnoStream(serialnoPath);
154     std::string serialno;
155     if (!serialnoStream.good()) {
156         LOG(ERROR) << "Failed to read serial number from " << serialnoPath;
157         return std::nullopt;
158     }
159     std::getline(serialnoStream, serialno);
160     return serialno;
161 }
162 
163 /**
164  * Searches for USB devices found in /sys/devices/, and attempts to find a device matching the
165  * provided list of serial numbers.
166  *
167  * \param configSerialnos - a list of serial number (suffixes) from the HAL config.
168  * \param iftype - the type of the interface to be located.
169  * \return a matching USB device. On failure, std::nullopt is returned.
170  */
findUsbDevice(const std::vector<std::string> & configSerialnos)171 static std::optional<UsbCanIface> findUsbDevice(const std::vector<std::string>& configSerialnos) {
172     std::error_code fsStatus;
173     fs::recursive_directory_iterator fsItr(kDevPath, kOpts, fsStatus);
174     if (fsStatus != fsErrors::ok) {
175         LOG(ERROR) << "Failed to open " << kDevPath;
176         return std::nullopt;
177     }
178 
179     for (; fsStatus == fsErrors::ok && fsItr != fs::recursive_directory_iterator();
180          fsItr.increment(fsStatus)) {
181         // We want to find a file called "serial", which is in a directory somewhere. Skip files.
182         bool isDir = fsItr->is_directory(fsStatus);
183         if (fsStatus != fsErrors::ok) {
184             LOG(ERROR) << "Failed check if " << fsStatus;
185             return std::nullopt;
186         }
187         if (!isDir) continue;
188 
189         auto serialnoPath = fsItr->path() / "serial";
190         bool isReg = fs::is_regular_file(serialnoPath, fsStatus);
191 
192         /* Make sure we have permissions to this directory, ignore enoent, since the file
193          * "serial" may not exist, which is ok. */
194         if (fsStatus == fsErrors::eperm || fsStatus == fsErrors::eacces) {
195             /* This means we  don't have access to this directory. If we recurse into it, this
196              * will cause the iterator to loose its state and we'll crash. */
197             fsItr.disable_recursion_pending();
198             continue;
199         }
200         if (fsStatus == fsErrors::enoent) continue;
201         if (fsStatus != fsErrors::ok) {
202             LOG(WARNING) << "An unexpected error occurred while checking for serialno: "
203                          << fsStatus;
204             continue;
205         }
206         if (!isReg) continue;
207 
208         // we found a serial number
209         auto serialno = readSerialNo(serialnoPath);
210         if (!serialno.has_value()) continue;
211 
212         // see if the serial number exists in the config
213         for (auto&& cfgSn : configSerialnos) {
214             if (serialno->ends_with(std::string(cfgSn))) {
215                 auto ifaceInfo = getIfaceName(serialnoPath);
216                 if (!ifaceInfo.has_value()) break;
217                 return ifaceInfo;
218             }
219         }
220     }
221     if (fsStatus != fsErrors::ok) {
222         LOG(ERROR) << "Error searching filesystem: " << fsStatus;
223         return std::nullopt;
224     }
225     return std::nullopt;
226 }
227 
getSupportedInterfaceTypes(std::vector<InterfaceType> * supportedTypes)228 ndk::ScopedAStatus CanController::getSupportedInterfaceTypes(
229         std::vector<InterfaceType>* supportedTypes) {
230     *supportedTypes = {InterfaceType::VIRTUAL, InterfaceType::NATIVE, InterfaceType::SLCAN};
231     return ok();
232 }
233 
getInterfaceName(const std::string & busName,std::string * ifaceName)234 ndk::ScopedAStatus CanController::getInterfaceName(const std::string& busName,
235                                                    std::string* ifaceName) {
236     *ifaceName = {};
237     if (mBusesByName.find(busName) == mBusesByName.end()) {
238         return resultToStatus(Result::BAD_BUS_NAME, fmt::format("{} doesn't exist", busName));
239     }
240     *ifaceName = std::string(mBusesByName[busName]->getIfaceName());
241     return ok();
242 }
243 
upBus(const BusConfig & config,std::string * ifaceName)244 ndk::ScopedAStatus CanController::upBus(const BusConfig& config, std::string* ifaceName) {
245     if (!isValidName(config.name)) {
246         LOG(ERROR) << "Bus name " << config.name << " is invalid";
247         return resultToStatus(Result::BAD_BUS_NAME,
248                               fmt::format("{} is not a valid bus name", config.name));
249     } else if (mBusesByName.find(config.name) != mBusesByName.end()) {
250         LOG(ERROR) << "A bus named " << config.name << " already exists!";
251         return resultToStatus(Result::INVALID_STATE,
252                               fmt::format("A bus named {} already exists", config.name));
253     }
254 
255     if (config.interfaceId.getTag() == BusConfig::InterfaceId::Tag::virtualif) {
256         auto& virtualif = config.interfaceId.get<BusConfig::InterfaceId::Tag::virtualif>();
257         mBusesByName[config.name] = std::make_unique<CanBusVirtual>(virtualif.ifname);
258     }
259 
260     else if (config.interfaceId.getTag() == BusConfig::InterfaceId::Tag::nativeif) {
261         auto& nativeif = config.interfaceId.get<BusConfig::InterfaceId::Tag::nativeif>();
262         std::string ifaceName;
263         if (nativeif.interfaceId.getTag() == NativeInterface::InterfaceId::Tag::serialno) {
264             // Configure by serial number.
265             auto selectedDevice = findUsbDevice(
266                     nativeif.interfaceId.get<NativeInterface::InterfaceId::Tag::serialno>());
267             // verify the returned device is the correct one
268             if (!selectedDevice.has_value() || selectedDevice->iftype != InterfaceType::NATIVE) {
269                 return resultToStatus(
270                         Result::BAD_INTERFACE_ID,
271                         "Couldn't find a native socketcan device with the given serial number(s)");
272             }
273             ifaceName = selectedDevice->ifaceName;
274         } else {
275             // configure by iface name.
276             ifaceName = nativeif.interfaceId.get<NativeInterface::InterfaceId::Tag::ifname>();
277         }
278         mBusesByName[config.name] = std::make_unique<CanBusNative>(ifaceName, config.bitrate);
279     }
280 
281     else if (config.interfaceId.getTag() == BusConfig::InterfaceId::Tag::slcan) {
282         auto& slcanif = config.interfaceId.get<BusConfig::InterfaceId::Tag::slcan>();
283         std::string ttyName;
284         if (slcanif.interfaceId.getTag() == SlcanInterface::InterfaceId::Tag::serialno) {
285             // Configure by serial number.
286             auto selectedDevice = findUsbDevice(
287                     slcanif.interfaceId.get<SlcanInterface::InterfaceId::Tag::serialno>());
288             if (!selectedDevice.has_value() || selectedDevice->iftype != InterfaceType::SLCAN) {
289                 return resultToStatus(
290                         Result::BAD_INTERFACE_ID,
291                         "Couldn't find a slcan device with the given serial number(s)");
292             }
293             ttyName = selectedDevice->ifaceName;
294         } else {
295             // Configure by tty name.
296             ttyName = slcanif.interfaceId.get<SlcanInterface::InterfaceId::Tag::ttyname>();
297         }
298         mBusesByName[config.name] = std::make_unique<CanBusSlcan>(ttyName, config.bitrate);
299     }
300 
301     else if (config.interfaceId.getTag() == BusConfig::InterfaceId::Tag::indexed) {
302         return resultToStatus(Result::NOT_SUPPORTED,
303                               "Indexed devices are not supported in this implementation");
304     } else {
305         // this shouldn't happen.
306         return resultToStatus(Result::UNKNOWN_ERROR, "Unknown interface id type");
307     }
308 
309     Result result = mBusesByName[config.name]->up();
310     if (result != Result::OK) {
311         // the bus failed to come up, don't leave a broken entry in the map.
312         mBusesByName.erase(config.name);
313         return resultToStatus(result, fmt::format("CanBus::up failed for {}", config.name));
314     }
315 
316     *ifaceName = mBusesByName[config.name]->getIfaceName();
317     return ok();
318 }
319 
downBus(const std::string & busName)320 ndk::ScopedAStatus CanController::downBus(const std::string& busName) {
321     if (mBusesByName.find(busName) == mBusesByName.end()) {
322         return resultToStatus(
323                 Result::UNKNOWN_ERROR,
324                 fmt::format("Couldn't bring down {}, because it doesn't exist", busName));
325     }
326     Result result = mBusesByName[busName]->down();
327     if (result != Result::OK) {
328         return resultToStatus(result, fmt::format("Couldn't bring down {}!", busName));
329     }
330     mBusesByName.erase(busName);
331     return ok();
332 }
333 }  // namespace aidl::android::hardware::automotive::can
334