* Copyright (C) 2024 Huawei Device Co., Ltd.
* 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 "host_usb.h"
#include <thread>
#include "usb_util.h"
namespace Hdc {
constexpr uint16_t DEVICE_CHECK_INTERVAL = 3000;
constexpr uint16_t BUF_SIZE_MEDIUM = 512;
constexpr uint16_t BUF_SIZE_TINY = 64;
constexpr uint8_t GLOBAL_TIMEOUT = 30;
constexpr uint16_t TIME_BASE = 1000;
constexpr uint16_t MAX_SIZE_IOBUF = 61440;
uint8_t *g_bufPtr = nullptr;
const std::string StringFormat(const char * const formater, va_list &vaArgs)
{
std::vector<char> args(MAX_SIZE_IOBUF);
const int retSize = vsnprintf_s(args.data(), MAX_SIZE_IOBUF, MAX_SIZE_IOBUF - 1, formater, vaArgs);
if (retSize < 0) {
return std::string("");
} else {
return std::string(args.data(), retSize);
}
}
const std::string StringFormat(const char * const formater, ...)
{
va_list vaArgs;
va_start(vaArgs, formater);
std::string ret = StringFormat(formater, vaArgs);
va_end(vaArgs);
return ret;
}
HostUsb::HostUsb()
{
if (libusb_init((libusb_context **)&ctxUSB) != 0) {
ctxUSB = nullptr;
}
running = false;
}
HostUsb::~HostUsb()
{
if (running) {
Stop();
}
}
void HostUsb::Stop()
{
if (!ctxUSB) {
return;
}
timer->Stop();
libusb_exit((libusb_context *)ctxUSB);
running = false;
if (g_bufPtr != nullptr) {
delete[] g_bufPtr;
g_bufPtr = nullptr;
}
}
void HostUsb::UsbWorkThread(void *arg)
{
HostUsb *thisClass = (HostUsb *)arg;
constexpr uint8_t usbHandleTimeout = 30;
while (thisClass->running) {
struct timeval zerotime;
zerotime.tv_sec = usbHandleTimeout;
zerotime.tv_usec = 0;
libusb_handle_events_timeout(thisClass->ctxUSB, &zerotime);
}
}
void HostUsb::WatchUsbNodeChange(void *arg)
{
HostUsb *thisClass = (HostUsb *)arg;
libusb_device **devs = nullptr;
libusb_device *dev = nullptr;
ssize_t cnt = libusb_get_device_list(thisClass->ctxUSB, &devs);
if (cnt < 0) {
return;
}
int i = 0;
while ((dev = devs[i++]) != nullptr) {
std::string szTmpKey = StringFormat("%d-%d", libusb_get_bus_number(dev), libusb_get_device_address(dev));
UsbCheckStatus statusCheck = thisClass->mapIgnoreDevice[szTmpKey];
if (statusCheck == HOST_USB_IGNORE || statusCheck == HOST_USB_REGISTER) {
continue;
}
std::string sn = szTmpKey;
if (thisClass->HasValidDevice(dev) && !thisClass->DetectMyNeed(dev, sn)) {
thisClass->ReviewUsbNodeLater(szTmpKey);
}
}
libusb_free_device_list(devs, 1);
}
void HostUsb::ReviewUsbNodeLater(string &nodeKey)
{
mapIgnoreDevice[nodeKey] = HOST_USB_IGNORE;
RemoveIgnoreDevice(nodeKey);
}
bool HostUsb::HasValidDevice(libusb_device *device)
{
struct libusb_config_descriptor *descConfig = nullptr;
int ret = libusb_get_active_config_descriptor(device, &descConfig);
if (ret != 0) {
return false;
}
bool hasValid = false;
for (unsigned int j = 0; j < descConfig->bNumInterfaces; ++j) {
const struct libusb_interface *interface = &descConfig->interface[j];
if (interface->num_altsetting < 1) {
continue;
}
const struct libusb_interface_descriptor *ifDescriptor = &interface->altsetting[0];
if (!IsDebuggableDev(ifDescriptor)) {
continue;
}
hasValid = true;
break;
}
return hasValid;
}
bool HostUsb::IsDebuggableDev(const struct libusb_interface_descriptor *ifDescriptor)
{
constexpr uint8_t harmonyEpNum = 2;
constexpr uint8_t harmonyClass = 0xff;
constexpr uint8_t harmonySubClass = 0x50;
constexpr uint8_t harmonyProtocol = 0x01;
if (ifDescriptor->bInterfaceClass != harmonyClass || ifDescriptor->bInterfaceSubClass != harmonySubClass ||
ifDescriptor->bInterfaceProtocol != harmonyProtocol) {
return false;
}
if (ifDescriptor->bNumEndpoints != harmonyEpNum) {
return false;
}
return true;
}
bool HostUsb::DetectMyNeed(libusb_device *device, string &sn)
{
HUSB hUSB = new(std::nothrow) HdcUSB();
if (hUSB == nullptr) {
return false;
}
hUSB->device = device;
int childRet = OpenDeviceMyNeed(hUSB);
if (childRet < 0) {
delete hUSB;
return false;
}
UpdateUSBDaemonInfo(hUSB, STATUS_READY);
mapIgnoreDevice[sn] = HOST_USB_REGISTER;
mapUsbDevice[hUSB->serialNumber] = hUSB;
return true;
}
void HostUsb::UpdateUSBDaemonInfo(HUSB hUSB, uint8_t connStatus)
{
HdcDaemonInformation di;
di.connectKey = hUSB->serialNumber;
di.connType = CONN_USB;
di.connStatus = connStatus;
di.usbMountPoint = "";
di.usbMountPoint = StringFormat("%d-%d", hUSB->busId, hUSB->devId);
HDaemonInfo pDi = nullptr;
HDaemonInfo hdiNew = &di;
AdminDaemonMap(OP_QUERY, hUSB->serialNumber, pDi);
if (!pDi) {
AdminDaemonMap(OP_ADD, hUSB->serialNumber, hdiNew);
} else {
AdminDaemonMap(OP_UPDATE, hUSB->serialNumber, hdiNew);
if (connStatus == STATUS_OFFLINE) {
RemoveIgnoreDevice(di.usbMountPoint);
}
}
}
int HostUsb::OpenDeviceMyNeed(HUSB hUSB)
{
libusb_device *device = hUSB->device;
int ret = -1;
int openRet = libusb_open(device, &hUSB->devHandle);
if (openRet != LIBUSB_SUCCESS) {
return -1;
}
while (running) {
libusb_device_handle *handle = hUSB->devHandle;
struct libusb_device_descriptor desc;
if (CheckDescriptor(hUSB, desc)) {
break;
}
#ifdef HOST_MAC
if (CheckActiveConfig(device, hUSB, desc)) {
#else
if (CheckActiveConfig(device, hUSB)) {
#endif
break;
}
ret = libusb_claim_interface(handle, hUSB->interfaceNumber);
break;
}
if (ret) {
libusb_close(hUSB->devHandle);
hUSB->devHandle = nullptr;
}
return ret;
}
int HostUsb::CheckDescriptor(HUSB hUSB, libusb_device_descriptor& desc)
{
char serialNum[BUF_SIZE_MEDIUM] = "";
int childRet = 0;
uint8_t curBus = libusb_get_bus_number(hUSB->device);
uint8_t curDev = libusb_get_device_address(hUSB->device);
hUSB->busId = curBus;
hUSB->devId = curDev;
if (libusb_get_device_descriptor(hUSB->device, &desc)) {
return -1;
}
childRet = libusb_get_string_descriptor_ascii(hUSB->devHandle, desc.iSerialNumber, (uint8_t *)serialNum,
sizeof(serialNum));
if (childRet < 0) {
return -1;
} else {
hUSB->serialNumber = serialNum;
}
return 0;
}
#ifdef HOST_MAC
int HostUsb::CheckActiveConfig(libusb_device *device, HUSB hUSB, libusb_device_descriptor& desc)
#else
int HostUsb::CheckActiveConfig(libusb_device *device, HUSB hUSB)
#endif
{
struct libusb_config_descriptor *descConfig = nullptr;
int ret = libusb_get_active_config_descriptor(device, &descConfig);
if (ret != 0) {
#ifdef HOST_MAC
if ((desc.bDeviceClass == 0xFF)
&& (desc.bDeviceSubClass == 0xFF)
&& (desc.bDeviceProtocol == 0xFF)) {
ret = libusb_set_configuration(hUSB->devHandle, 1);
if (ret != 0) {
return -1;
}
}
ret = libusb_get_active_config_descriptor(device, &descConfig);
if (ret != 0) {
#endif
return -1;
}
#ifdef HOST_MAC
}
#endif
ret = -1;
CheckUsbEndpoint(ret, hUSB, descConfig);
libusb_free_config_descriptor(descConfig);
return ret;
}
void HostUsb::CheckUsbEndpoint(int& ret, HUSB hUSB, libusb_config_descriptor *descConfig)
{
unsigned int j = 0;
for (j = 0; j < descConfig->bNumInterfaces; ++j) {
const struct libusb_interface *interface = &descConfig->interface[j];
if (interface->num_altsetting < 1) {
continue;
}
const struct libusb_interface_descriptor *ifDescriptor = &interface->altsetting[0];
if (!IsDebuggableDev(ifDescriptor)) {
continue;
}
hUSB->interfaceNumber = ifDescriptor->bInterfaceNumber;
unsigned int k = 0;
for (k = 0; k < ifDescriptor->bNumEndpoints; ++k) {
const struct libusb_endpoint_descriptor *ep_desc = &ifDescriptor->endpoint[k];
if ((ep_desc->bmAttributes & 0x03) != LIBUSB_TRANSFER_TYPE_BULK) {
continue;
}
if (ep_desc->bEndpointAddress & LIBUSB_ENDPOINT_IN) {
hUSB->hostBulkIn.endpoint = ep_desc->bEndpointAddress;
hUSB->hostBulkIn.bulkInOut = true;
} else {
hUSB->hostBulkOut.endpoint = ep_desc->bEndpointAddress;
hUSB->wMaxPacketSizeSend = ep_desc->wMaxPacketSize;
hUSB->hostBulkOut.bulkInOut = false;
}
}
if (hUSB->hostBulkIn.endpoint == 0 || hUSB->hostBulkOut.endpoint == 0) {
break;
}
ret = 0;
}
}
bool HostUsb::FindDeviceByID(HUSB hUSB, const char *usbMountPoint, libusb_context *ctxUSB)
{
libusb_device **listDevices = nullptr;
bool ret = false;
char tmpStr[BUF_SIZE_TINY] = "";
int busNum = 0;
int devNum = 0;
int curBus = 0;
int curDev = 0;
int deviceNum = libusb_get_device_list(ctxUSB, &listDevices);
if (deviceNum <= 0) {
libusb_free_device_list(listDevices, 1);
return false;
}
if (strchr(usbMountPoint, '-') && EOK == strcpy_s(tmpStr, sizeof(tmpStr), usbMountPoint)) {
*strchr(tmpStr, '-') = '\0';
busNum = atoi(tmpStr);
devNum = atoi(tmpStr + strlen(tmpStr) + 1);
} else {
return false;
}
int i = 0;
for (i = 0; i < deviceNum; ++i) {
struct libusb_device_descriptor desc;
if (LIBUSB_SUCCESS != libusb_get_device_descriptor(listDevices[i], &desc)) {
continue;
}
curBus = libusb_get_bus_number(listDevices[i]);
curDev = libusb_get_device_address(listDevices[i]);
if ((curBus == busNum && curDev == devNum)) {
hUSB->device = listDevices[i];
int childRet = OpenDeviceMyNeed(hUSB);
if (!childRet) {
ret = true;
} else {
string key = string(usbMountPoint);
RemoveIgnoreDevice(key);
}
break;
}
}
libusb_free_device_list(listDevices, 1);
return ret;
}
void HostUsb::CancelUsbIo(HUSB hUSB)
{
std::unique_lock<std::mutex> lock(hUSB->lockDeviceHandle);
if (!hUSB->hostBulkIn.isShutdown) {
if (!hUSB->hostBulkIn.isComplete) {
libusb_cancel_transfer(hUSB->hostBulkIn.transfer);
hUSB->hostBulkIn.cv.notify_one();
} else {
hUSB->hostBulkIn.isShutdown = true;
}
}
if (!hUSB->hostBulkOut.isShutdown) {
if (!hUSB->hostBulkOut.isComplete) {
libusb_cancel_transfer(hUSB->hostBulkOut.transfer);
hUSB->hostBulkOut.cv.notify_one();
} else {
hUSB->hostBulkOut.isShutdown = true;
}
}
}
void HostUsb::RemoveIgnoreDevice(string &mountInfo)
{
if (mapIgnoreDevice.count(mountInfo)) {
mapIgnoreDevice.erase(mountInfo);
}
}
void LIBUSB_CALL HostUsb::USBBulkCallback(struct libusb_transfer *transfer)
{
auto *ep = reinterpret_cast<HostUSBEndpoint *>(transfer->user_data);
std::unique_lock<std::mutex> lock(ep->mutexIo);
bool retrySumit = false;
int childRet = 0;
do {
if (transfer->status != LIBUSB_TRANSFER_COMPLETED) {
break;
}
if (!ep->bulkInOut && transfer->actual_length != transfer->length) {
transfer->length -= transfer->actual_length;
transfer->buffer += transfer->actual_length;
retrySumit = true;
break;
}
} while (false);
while (retrySumit) {
childRet = libusb_submit_transfer(transfer);
if (childRet != 0) {
transfer->status = LIBUSB_TRANSFER_ERROR;
break;
}
return;
}
ep->isComplete = true;
ep->cv.notify_one();
}
PersistBuffer HostUsb::ReadUsbIO(HUSB hUSB, int exceptedSize)
{
int timeout = 0;
int childRet = 0;
int ret = 0;
HostUSBEndpoint* ep = &hUSB->hostBulkIn;
if (g_bufPtr == nullptr) {
g_bufPtr = new uint8_t[MAX_SIZE_IOBUF];
}
hUSB->lockDeviceHandle.lock();
ep->isComplete = false;
do {
std::unique_lock<std::mutex> lock(ep->mutexIo);
libusb_fill_bulk_transfer(ep->transfer, hUSB->devHandle, ep->endpoint, g_bufPtr, exceptedSize,
USBBulkCallback, ep, timeout);
childRet = libusb_submit_transfer(ep->transfer);
hUSB->lockDeviceHandle.unlock();
if (childRet < 0) {
break;
}
ep->cv.wait(lock, [ep]() { return ep->isComplete; });
if (ep->transfer->status != 0) {
break;
}
ret = ep->transfer->actual_length;
} while (false);
return PersistBuffer{reinterpret_cast<char *>(g_bufPtr), static_cast<uint64_t>(ret)};
}
HUSB HostUsb::GetUsbDevice(std::string connectKey)
{
return mapUsbDevice[connectKey];
}
int HostUsb::WriteUsbIO(HUSB hUSB, SerializedBuffer buf)
{
int childRet = 0;
int ret = -14000;
int timeout = GLOBAL_TIMEOUT * TIME_BASE;
HostUSBEndpoint *ep = &hUSB->hostBulkOut;
hUSB->lockDeviceHandle.lock();
ep->isComplete = false;
uint8_t* ptr = reinterpret_cast<uint8_t *>(buf.ptr);
size_t size = static_cast<size_t>(buf.size);
do {
std::unique_lock<std::mutex> lock(ep->mutexIo);
libusb_fill_bulk_transfer(ep->transfer, hUSB->devHandle, ep->endpoint, ptr, size, USBBulkCallback, ep,
timeout);
childRet = libusb_submit_transfer(ep->transfer);
hUSB->lockDeviceHandle.unlock();
if (childRet < 0) {
break;
}
ep->cv.wait(lock, [ep]() { return ep->isComplete; });
if (ep->transfer->status != 0) {
break;
}
ret = ep->transfer->actual_length;
} while (false);
return ret;
}
int HostUsb::Initial()
{
if (!ctxUSB) {
return -1;
}
running = true;
auto WatchUsbNodeChangeFunc = [this]() { WatchUsbNodeChange(this); };
timer = std::make_unique<CTimer>(WatchUsbNodeChangeFunc);
timer->Start(DEVICE_CHECK_INTERVAL);
std::thread([this]() {
UsbWorkThread(this);
}).detach();
return 0;
}
static void BuildDaemonVisableLine(HDaemonInfo hdi, bool fullDisplay, string &out)
{
if (fullDisplay) {
string sConn;
string sStatus;
switch (hdi->connType) {
case CONN_TCP:
sConn = "TCP";
break;
case CONN_USB:
sConn = "USB";
break;
#ifdef HDC_SUPPORT_UART
case CONN_SERIAL:
sConn = "UART";
break;
#endif
case CONN_BT:
sConn = "BT";
break;
default:
sConn = "UNKNOW";
break;
}
switch (hdi->connStatus) {
case STATUS_READY:
sStatus = "Ready";
break;
case STATUS_CONNECTED:
sStatus = "Connected";
break;
case STATUS_OFFLINE:
sStatus = "Offline";
break;
default:
sStatus = "UNKNOW";
break;
}
out = StringFormat("%s\t\t%s\t%s\t%s\n", hdi->connectKey.c_str(), sConn.c_str(), sStatus.c_str(),
hdi->devName.c_str());
} else {
if (hdi->connStatus == STATUS_CONNECTED) {
out = StringFormat("%s\n", hdi->connectKey.c_str());
}
}
}
string HostUsb::GetDaemonMapList(uint8_t opType)
{
string ret;
bool fullDisplay = false;
if (opType == OP_GET_STRLIST_FULL) {
fullDisplay = true;
}
lockMapDaemon.lock();
map<string, HDaemonInfo>::iterator iter;
string echoLine;
for (iter = mapDaemon.begin(); iter != mapDaemon.end(); ++iter) {
HDaemonInfo di = iter->second;
if (!di) {
continue;
}
echoLine = "";
if (opType == OP_GET_READY_STRLIST) {
if (di->connStatus == STATUS_READY) {
echoLine = StringFormat("%s ", di->connectKey.c_str());
ret += echoLine;
}
continue;
}
BuildDaemonVisableLine(di, fullDisplay, echoLine);
ret += echoLine;
}
lockMapDaemon.unlock();
return ret;
}
string HostUsb::AdminDaemonMap(uint8_t opType, const string &connectKey, HDaemonInfo &hDaemonInfoInOut)
{
string sRet;
switch (opType) {
case OP_ADD: {
HDaemonInfo pdiNew = new(std::nothrow) HdcDaemonInformation();
if (pdiNew == nullptr) {
break;
}
*pdiNew = *hDaemonInfoInOut;
lockMapDaemon.lock();
if (!mapDaemon[hDaemonInfoInOut->connectKey]) {
mapDaemon[hDaemonInfoInOut->connectKey] = pdiNew;
}
lockMapDaemon.unlock();
break;
}
case OP_GET_READY_STRLIST:
sRet = GetDaemonMapList(opType);
break;
case OP_GET_STRLIST:
case OP_GET_STRLIST_FULL: {
sRet = GetDaemonMapList(opType);
break;
}
case OP_QUERY: {
lockMapDaemon.lock();
if (mapDaemon.count(connectKey)) {
hDaemonInfoInOut = mapDaemon[connectKey];
}
lockMapDaemon.unlock();
break;
}
case OP_REMOVE: {
lockMapDaemon.lock();
if (mapDaemon.count(connectKey)) {
mapDaemon.erase(connectKey);
}
lockMapDaemon.unlock();
break;
}
case OP_GET_ANY: {
lockMapDaemon.lock();
map<string, HDaemonInfo>::iterator iter;
for (iter = mapDaemon.begin(); iter != mapDaemon.end(); ++iter) {
HDaemonInfo di = iter->second;
if (di->connStatus == STATUS_READY || di->connStatus == STATUS_CONNECTED) {
hDaemonInfoInOut = di;
break;
}
}
lockMapDaemon.unlock();
break;
}
case OP_WAIT_FOR_ANY: {
lockMapDaemon.lock();
map<string, HDaemonInfo>::iterator iter;
for (iter = mapDaemon.begin(); iter != mapDaemon.end(); ++iter) {
HDaemonInfo di = iter->second;
if (di->connStatus == STATUS_CONNECTED) {
hDaemonInfoInOut = di;
break;
}
}
lockMapDaemon.unlock();
break;
}
case OP_UPDATE: {
lockMapDaemon.lock();
HDaemonInfo hdi = mapDaemon[hDaemonInfoInOut->connectKey];
if (hdi) {
*mapDaemon[hDaemonInfoInOut->connectKey] = *hDaemonInfoInOut;
}
lockMapDaemon.unlock();
break;
}
default:
break;
}
return sRet;
}
}