summaryrefslogtreecommitdiff
path: root/automotive/can/1.0/default/CanBusSlcan.cpp
blob: f08566ca5fc0985d58235cd35a1f9496abb488b3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "CanBusSlcan.h"

#include <android-base/logging.h>
#include <libnetdevice/can.h>
#include <libnetdevice/libnetdevice.h>

#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <termios.h>

namespace android::hardware::automotive::can::V1_0::implementation {

namespace slcanprotocol {
static const std::string kOpenCommand = "O\r";
static const std::string kCloseCommand = "C\r";
static constexpr int kSlcanDiscipline = N_SLCAN;
static constexpr int kDefaultDiscipline = N_TTY;

static const std::map<uint32_t, std::string> kBitrateCommands = {
        {10000, "C\rS0\r"},  {20000, "C\rS1\r"},  {50000, "C\rS2\r"},
        {100000, "C\rS3\r"}, {125000, "C\rS4\r"}, {250000, "C\rS5\r"},
        {500000, "C\rS6\r"}, {800000, "C\rS7\r"}, {1000000, "C\rS8\r"}};
}  // namespace slcanprotocol

/**
 * Serial Line CAN constructor
 * \param string uartName - name of slcan device (e.x. /dev/ttyUSB0)
 * \param uint32_t bitrate - speed of the CAN bus (125k = MSCAN, 500k = HSCAN)
 */
CanBusSlcan::CanBusSlcan(const std::string& uartName, uint32_t bitrate)
    : CanBus(), mUartName(uartName), kBitrate(bitrate) {}

/** helper function to update CanBusSlcan object's iface name */
ICanController::Result CanBusSlcan::updateIfaceName(base::unique_fd& uartFd) {
    struct ifreq ifrequest = {};
    /*
     * Fetching the iface name with an ioctl won't interfere with an open socketCAN iface attached
     * to this tty. This is important in the event we are trying to register a SLCAN based iface
     * that has already been configured and brought up.
     */
    if (ioctl(uartFd.get(), SIOCGIFNAME, ifrequest.ifr_name) < 0) {
        PLOG(ERROR) << "Failed to get the name of the created device";
        return ICanController::Result::UNKNOWN_ERROR;
    }

    // Update the CanBus object with name that was assigned to it
    mIfname = ifrequest.ifr_name;
    return ICanController::Result::OK;
}

ICanController::Result CanBusSlcan::preUp() {
    // verify valid bitrate and translate to serial command format
    std::optional<std::string> canBitrateCommand = std::nullopt;
    if (kBitrate != 0) {
        const auto lookupIt = slcanprotocol::kBitrateCommands.find(kBitrate);
        if (lookupIt == slcanprotocol::kBitrateCommands.end()) {
            return ICanController::Result::BAD_BITRATE;
        }
        canBitrateCommand = lookupIt->second;
    }

    /* Attempt to open the uart in r/w without blocking or becoming the
     * controlling terminal */
    mFd = base::unique_fd(open(mUartName.c_str(), O_RDWR | O_NONBLOCK | O_NOCTTY));
    if (!mFd.ok()) {
        PLOG(ERROR) << "SLCAN Failed to open " << mUartName;
        return ICanController::Result::BAD_INTERFACE_ID;
    }

    // If the device is already up, update the iface name in our CanBusSlcan object
    if (kBitrate == 0) {
        return updateIfaceName(mFd);
    }

    // blank terminal settings and pull them from the device
    struct termios terminalSettings = {};
    if (tcgetattr(mFd.get(), &terminalSettings) < 0) {
        PLOG(ERROR) << "Failed to read attrs of" << mUartName;
        return ICanController::Result::UNKNOWN_ERROR;
    }

    // change settings to raw mode
    cfmakeraw(&terminalSettings);

    // disable software flow control
    terminalSettings.c_iflag &= ~IXOFF;
    // enable hardware flow control
    terminalSettings.c_cflag |= CRTSCTS;

    struct serial_struct serialSettings;
    // get serial settings
    if (ioctl(mFd.get(), TIOCGSERIAL, &serialSettings) < 0) {
        PLOG(ERROR) << "Failed to read serial settings from " << mUartName;
        return ICanController::Result::UNKNOWN_ERROR;
    }
    // set low latency mode
    serialSettings.flags |= ASYNC_LOW_LATENCY;
    // apply serial settings
    if (ioctl(mFd.get(), TIOCSSERIAL, &serialSettings) < 0) {
        PLOG(ERROR) << "Failed to set low latency mode on " << mUartName;
        return ICanController::Result::UNKNOWN_ERROR;
    }

    /* TCSADRAIN applies settings after we finish writing the rest of our
     * changes (as opposed to TCSANOW, which changes immediately) */
    if (tcsetattr(mFd.get(), TCSADRAIN, &terminalSettings) < 0) {
        PLOG(ERROR) << "Failed to apply terminal settings to " << mUartName;
        return ICanController::Result::UNKNOWN_ERROR;
    }

    // apply speed setting for CAN
    if (write(mFd.get(), canBitrateCommand->c_str(), canBitrateCommand->length()) <= 0) {
        PLOG(ERROR) << "Failed to apply CAN bitrate";
        return ICanController::Result::UNKNOWN_ERROR;
    }

    // TODO(b/144775286): set open flag & support listen only
    if (write(mFd.get(), slcanprotocol::kOpenCommand.c_str(),
              slcanprotocol::kOpenCommand.length()) <= 0) {
        PLOG(ERROR) << "Failed to set open flag";
        return ICanController::Result::UNKNOWN_ERROR;
    }

    // set line discipline to slcan
    if (ioctl(mFd.get(), TIOCSETD, &slcanprotocol::kSlcanDiscipline) < 0) {
        PLOG(ERROR) << "Failed to set line discipline to slcan";
        return ICanController::Result::UNKNOWN_ERROR;
    }

    // Update the CanBus object with name that was assigned to it
    return updateIfaceName(mFd);
}

bool CanBusSlcan::postDown() {
    // reset the line discipline to TTY mode
    if (ioctl(mFd.get(), TIOCSETD, &slcanprotocol::kDefaultDiscipline) < 0) {
        LOG(ERROR) << "Failed to reset line discipline!";
        return false;
    }

    // issue the close command
    if (write(mFd.get(), slcanprotocol::kCloseCommand.c_str(),
              slcanprotocol::kCloseCommand.length()) <= 0) {
        LOG(ERROR) << "Failed to close tty!";
        return false;
    }

    // close our unique_fd
    mFd.reset();

    return true;
}

}  // namespace android::hardware::automotive::can::V1_0::implementation