307 lines
7.9 KiB
C
307 lines
7.9 KiB
C
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
/*
|
|
* fbusb.c - helper functions for fastboot usb protocol
|
|
*
|
|
* Copyright (C) 2025 j4nn at xdaforums
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/types.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <libusb-1.0/libusb.h>
|
|
|
|
#include "fbusb.h"
|
|
#include "logging.h"
|
|
|
|
static int verbosity = LOG_ERR;
|
|
|
|
static const char *fastboot_results[] = {
|
|
[FASTBOOT_OKAY] = "OKAY",
|
|
[FASTBOOT_FAIL] = "FAIL",
|
|
[FASTBOOT_DATA] = "DATA",
|
|
[FASTBOOT_INFO] = "INFO",
|
|
[FASTBOOT_UNKNOWN] = "UNKN",
|
|
};
|
|
|
|
|
|
struct fbusb {
|
|
libusb_device_handle *h;
|
|
int iface;
|
|
int epi;
|
|
int epo;
|
|
int maxsize;
|
|
int timeout;
|
|
};
|
|
|
|
|
|
int fastboot_parse_result(const char *status)
|
|
{
|
|
int i;
|
|
for (i = 0; i < FASTBOOT_UNKNOWN; i++)
|
|
if (strncmp(status, fastboot_results[i], 4) == 0)
|
|
return i;
|
|
return i;
|
|
}
|
|
|
|
struct fbusb *fbusb_init(int vid, int pid, int iface, int epi, int epo)
|
|
{
|
|
int res;
|
|
struct fbusb *dev;
|
|
libusb_device_handle *h;
|
|
|
|
res = libusb_init(NULL);
|
|
if (res < 0) {
|
|
PERR("libusb_init failed: %s\n", libusb_strerror(res));
|
|
return NULL;
|
|
}
|
|
|
|
h = libusb_open_device_with_vid_pid(NULL, vid, pid);
|
|
if (h == NULL) {
|
|
PERR("libusb_open_device_with_vid_pid (%04x:%04x) failed\n",
|
|
vid, pid);
|
|
goto err_exit1;
|
|
}
|
|
|
|
if (libusb_kernel_driver_active(h, 0) == 1) {
|
|
if (libusb_detach_kernel_driver(h, 0) != 0) {
|
|
PERR("libusb_detach_kernel_driver failed\n");
|
|
goto err_exit2;
|
|
}
|
|
}
|
|
|
|
res = libusb_claim_interface(h, iface);
|
|
if (res < 0) {
|
|
PERR("libusb_claim_interface failed: %s\n", libusb_strerror(res));
|
|
goto err_exit2;
|
|
}
|
|
|
|
dev = calloc(1, sizeof(struct fbusb));
|
|
if (dev != NULL) {
|
|
dev->h = h;
|
|
dev->iface = iface;
|
|
dev->epi = epi;
|
|
dev->epo = epo;
|
|
dev->maxsize = 16 * 1024 * 1024;
|
|
dev->timeout = 5000;
|
|
return dev;
|
|
}
|
|
|
|
libusb_release_interface(h, iface);
|
|
err_exit2:
|
|
libusb_close(h);
|
|
err_exit1:
|
|
libusb_exit(NULL);
|
|
return NULL;
|
|
}
|
|
|
|
void fbusb_exit(struct fbusb *dev)
|
|
{
|
|
libusb_release_interface(dev->h, dev->iface);
|
|
libusb_close(dev->h);
|
|
libusb_exit(NULL);
|
|
free(dev);
|
|
}
|
|
|
|
void fbusb_set_maxsize(struct fbusb *dev, int maxsize)
|
|
{
|
|
dev->maxsize = maxsize;
|
|
}
|
|
|
|
void fbusb_set_timeout(struct fbusb *dev, int timeout_ms)
|
|
{
|
|
dev->timeout = timeout_ms;
|
|
}
|
|
|
|
void fbusb_inc_verbosity(struct fbusb *dev)
|
|
{
|
|
if (verbosity < LOG_DBG)
|
|
verbosity++;
|
|
}
|
|
|
|
void fbusb_dec_verbosity(struct fbusb *dev)
|
|
{
|
|
if (verbosity > 0)
|
|
verbosity--;
|
|
}
|
|
|
|
static void fbusb_log(struct fbusb *dev, int ep, void *buff, int len, int done,
|
|
int res)
|
|
{
|
|
int i;
|
|
const char *dir;
|
|
const char *status;
|
|
const unsigned char *ptr = buff;
|
|
|
|
dir = (ep & 0x80) ? "<-" : "->";
|
|
|
|
switch (res) {
|
|
case LIBUSB_SUCCESS: status = "OK"; break;
|
|
case LIBUSB_ERROR_IO: status = "IO"; break;
|
|
case LIBUSB_ERROR_INVALID_PARAM: status = "IP"; break;
|
|
case LIBUSB_ERROR_ACCESS: status = "AC"; break;
|
|
case LIBUSB_ERROR_NO_DEVICE: status = "ND"; break;
|
|
case LIBUSB_ERROR_NOT_FOUND: status = "NF"; break;
|
|
case LIBUSB_ERROR_BUSY: status = "BS"; break;
|
|
case LIBUSB_ERROR_TIMEOUT: status = "TO"; break;
|
|
case LIBUSB_ERROR_OVERFLOW: status = "OF"; break;
|
|
case LIBUSB_ERROR_PIPE: status = "PI"; break;
|
|
case LIBUSB_ERROR_INTERRUPTED: status = "IN"; break;
|
|
case LIBUSB_ERROR_NO_MEM: status = "NM"; break;
|
|
case LIBUSB_ERROR_NOT_SUPPORTED: status = "NS"; break;
|
|
case LIBUSB_ERROR_OTHER: status = "OT"; break;
|
|
default: status = "??"; break;
|
|
}
|
|
|
|
PNFO_CONT(" {%08x%s%08x:%s}", len, dir, done, status);
|
|
|
|
if (verbosity >= LOG_DBG) {
|
|
for (i = 0; i < 16; i++)
|
|
if (i < done)
|
|
PDBG_CONT(" %02x", ptr[i]);
|
|
else
|
|
PDBG_CONT(" ");
|
|
}
|
|
|
|
PNFO_CONT(" \"");
|
|
for (i = 0; i < 64 && i < done; i++)
|
|
PNFO_CONT("%c", isprint(ptr[i]) ? ptr[i] : '.');
|
|
PNFO_CONT("\"\n");
|
|
}
|
|
|
|
static int fbusb_transfer(struct fbusb *dev, void *buff, int size, int ep)
|
|
{
|
|
int res, idx;
|
|
int len, done;
|
|
int transferred = 0;
|
|
|
|
for (idx = 0; idx < size; idx += done) {
|
|
len = size - idx;
|
|
if (dev->maxsize > 0) {
|
|
if (len > dev->maxsize)
|
|
len = dev->maxsize;
|
|
}
|
|
|
|
done = 0;
|
|
res = libusb_bulk_transfer(dev->h, ep, buff + idx, len, &done,
|
|
dev->timeout);
|
|
transferred += done;
|
|
fbusb_log(dev, ep, buff + idx, len, done, res);
|
|
|
|
if (res != 0 && transferred == 0) {
|
|
PERR("libusb_bulk_transfer failed: %s ep=0x%02x "
|
|
"len=0x%04x size=0x%04x\n",
|
|
libusb_strerror(res), ep, len, size);
|
|
return -1;
|
|
}
|
|
if (done < len)
|
|
break;
|
|
}
|
|
|
|
return transferred;
|
|
}
|
|
|
|
int fbusb_send(struct fbusb *dev, void *buff, int size)
|
|
{
|
|
return fbusb_transfer(dev, buff, size, dev->epo);
|
|
}
|
|
|
|
int fbusb_recv(struct fbusb *dev, void *buff, int size)
|
|
{
|
|
return fbusb_transfer(dev, buff, size, dev->epi);
|
|
}
|
|
|
|
int fbusb_bufcmd_resp(struct fbusb *dev, void *rsp, int *rspsz)
|
|
{
|
|
int res;
|
|
int received;
|
|
char *s = rsp;
|
|
if (rspsz == NULL || *rspsz < 4)
|
|
return -1;
|
|
memset(rsp, 0, *rspsz);
|
|
received = fbusb_recv(dev, rsp, *rspsz);
|
|
if (received >= 4) {
|
|
res = fastboot_parse_result(rsp);
|
|
if (res < FASTBOOT_UNKNOWN) {
|
|
received -= 4;
|
|
memmove(rsp, rsp + 4, received);
|
|
memset(s + received, 0, 4);
|
|
*rspsz = received;
|
|
return res;
|
|
}
|
|
PDBG("fbusb_bufcmd_resp recv unknown fastboot response: "
|
|
"'%c%c%c%c' (rspsz=0x%04x received=0x%04x)\n",
|
|
s[0], s[1], s[2], s[3], *rspsz, received);
|
|
*rspsz = received;
|
|
return FASTBOOT_UNKNOWN;
|
|
}
|
|
if (received < 0) {
|
|
PERR("fbusb_bufcmd_resp recv failed (rspsz=0x%04x)\n", *rspsz);
|
|
*rspsz = 0;
|
|
return received;
|
|
}
|
|
PERR("fbusb_bufcmd_resp recv invalid fastboot response: "
|
|
"received=0x%04x (rspsz=0x%04x)\n", received, *rspsz);
|
|
*rspsz = received;
|
|
return FASTBOOT_UNKNOWN;
|
|
}
|
|
|
|
int fbusb_bufcmd(struct fbusb *dev, void *req, int reqsz, void *rsp, int *rspsz)
|
|
{
|
|
int res = fbusb_send(dev, req, reqsz);
|
|
if (res != reqsz) {
|
|
*rspsz = 0;
|
|
if (res > 0) {
|
|
PERR("fbusb_bufcmd send incomplete: "
|
|
"reqsz=0x%02x res=0x%04x\n", reqsz, res);
|
|
return -1;
|
|
}
|
|
PERR("fbusb_bufcmd send failed: reqsz=0x%02x res=0x%04x\n", reqsz, res);
|
|
return -1;
|
|
}
|
|
return fbusb_bufcmd_resp(dev, rsp, rspsz);
|
|
}
|
|
|
|
int fbusb_bufrcv(struct fbusb *dev, void *rcv, int rcvsz, void *rsp, int *rspsz)
|
|
{
|
|
int res = fbusb_recv(dev, rcv, rcvsz);
|
|
if (res != rcvsz) {
|
|
*rspsz = 0;
|
|
if (res > 0) {
|
|
PDBG("fbusb_bufrcv recv incomplete: rcvsz=0x%02x res=0x%04x\n",
|
|
rcvsz, res);
|
|
return -1;
|
|
}
|
|
PERR("fbusb_bufrcv recv failed: rcvsz=0x%02x res=0x%04x\n", rcvsz, res);
|
|
return -1;
|
|
}
|
|
return fbusb_bufcmd_resp(dev, rsp, rspsz);
|
|
}
|
|
|
|
int fbusb_strcmd(struct fbusb *dev, const char *req, char *rsp, int rspmaxsize)
|
|
{
|
|
int res;
|
|
int rspsz = rspmaxsize - 1;
|
|
if (rspsz < 4)
|
|
return -1;
|
|
res = fbusb_bufcmd(dev, (void *)req, strlen(req), rsp, &rspsz);
|
|
if (res >= 0)
|
|
if (rspsz >= 0 && rspsz < rspmaxsize)
|
|
rsp[rspsz] = '\0';
|
|
return res;
|
|
}
|
|
|
|
int fbusb_strcmd_resp(struct fbusb *dev, char *rsp, int rspmaxsize)
|
|
{
|
|
int res;
|
|
int rspsz = rspmaxsize - 1;
|
|
if (rspsz < 4)
|
|
return -1;
|
|
res = fbusb_bufcmd_resp(dev, rsp, &rspsz);
|
|
if (res >= 0)
|
|
if (rspsz >= 0 && rspsz < rspmaxsize)
|
|
rsp[rspsz] = '\0';
|
|
return res;
|
|
}
|
|
|