blob: bc27a9845338b8b6efabe6a02ec31a73709f592d [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2019 Google LLC
*
* Depthcharge driver for using the Android Open Accessory (AOA) protocol to
* implement a connected Android device as a storage backend for recovery.
*/
#include <assert.h>
#include <libpayload.h>
#include <usb/usb.h>
#include <usb/usbmsc.h>
#include <vb2_api.h>
#include "boot/commandline.h"
#include "drivers/bus/usb/usb.h"
#include "vboot/util/commonparams.h"
#include "vboot/util/init_funcs.h"
#define USB_VID_GOOGLE 0x18d1
#define USB_PID_AOA 0x2d00
#define USB_PID_AOA_ADB 0x2d01
#define AOA_GET_PROTOCOL 51
#define AOA_SET_STRING 52
#define AOA_START 53
#define AOA_STRING_MANUFACTURER 0
#define AOA_STRING_MODEL 1
#define AOA_STRING_DESCRIPTION 2
#define AOA_STRING_VERSION 3
#define AOA_STRING_URI 4
#define AOA_STRING_SERIAL 5
const char manufacturer[] = "Google";
const char model[] = "Chrome OS Recovery";
const char description[] = "Chrome OS device in Recovery Mode";
const char version[] = "1.0";
const char uri[] = "https://google.com/chromeos/recovery_android";
static int aoa_recovery_connect(GenericUsbDevice *dev)
{
printf("USB device %d connected in AOA mode!\n", dev->dev->address);
usb_msc_force_init(dev->dev,
USB_MSC_QUIRK_NO_LUNS | USB_MSC_QUIRK_NO_RESET);
// ******** DANGER ******** Ugly hack ahead! ******** DANGER ********
// We want to keep this device registered, so we *should* return 1. But
// the problem is that we're reusing the libpayload MSC driver in the
// context of depthcharge's driver framework. Libpayload's usbdev_t has
// 'data', 'init' and 'destroy' pointers for use by the individual USB
// driver... however, for payload-specific drivers, the depthcharge
// framework takes control of these pointers, installs it's own custom
// GenericUsbDevice type in usbdev->data and then expects its custom
// USB drivers to work with that type. Libpayload's MSC stack can't
// work with GenericUsbDevice, it expects to have full control over the
// usbdev_t itself.
//
// The solution here is to return 0 so depthcharge "thinks" we did not
// succeed and avoids touching the usbdev_t. Libpayload decides whether
// a driver was found by looking if usbdev->data is set, which the MSC
// driver did, so it will be happy. The MSC driver installed its own
// teardown code in usbdev->destroy, so teardown will still work fine
// even though it bypasses the depthcharge framework. An ugly but
// harmless consequence is that depthcharge will still run through all
// other driver probes in the list (but they should all fail).
return 0;
}
static int aoa_get_protocol(GenericUsbDevice *dev)
{
uint16_t protocol;
dev_req_t get_protocol = {
.req_recp = dev_recp,
.req_type = vendor_type,
.data_dir = device_to_host,
.bRequest = AOA_GET_PROTOCOL,
.wValue = 0,
.wIndex = 0,
.wLength = sizeof(protocol),
};
int ret = dev->dev->controller->control(dev->dev, IN,
sizeof(get_protocol), &get_protocol,
sizeof(protocol), (void *)&protocol);
if (ret < 0)
return ret;
else
return protocol;
}
static int aoa_set_string(GenericUsbDevice *dev, uint16_t type, const char *str)
{
size_t len = strlen(str) + 1;
assert(len < 1 << (sizeof(uint16_t) * 8));
dev_req_t set_string = {
.req_recp = dev_recp,
.req_type = vendor_type,
.data_dir = host_to_device,
.bRequest = AOA_SET_STRING,
.wValue = 0,
.wIndex = type,
.wLength = len,
};
return dev->dev->controller->control(dev->dev, OUT, sizeof(set_string),
&set_string, len, (void *)str);
}
static int aoa_start(GenericUsbDevice *dev)
{
dev_req_t aoa_start = {
.req_recp = dev_recp,
.req_type = vendor_type,
.data_dir = host_to_device,
.bRequest = AOA_START,
.wValue = 0,
.wIndex = 0,
.wLength = 0,
};
return dev->dev->controller->control(dev->dev, OUT, sizeof(aoa_start),
&aoa_start, 0, NULL);
}
static int aoa_try_enter(GenericUsbDevice *dev)
{
char hwid[VB2_GBB_HWID_MAX_SIZE];
uint32_t hwid_size = sizeof(hwid);
// Ideally we'd just check the return code, but libpayload's USB HC
// drivers don't share common return codes... so if we want to check for
// timeout we'll have to do it manually.
uint64_t time = timer_us(0);
if (aoa_get_protocol(dev) <= 0) {
device_descriptor_t *dd = dev->dev->descriptor;
if (timer_us(time) >= USB_MAX_PROCESSING_TIME_US)
printf("ERROR: AOA probe timed out on USB #%d (%04x:%04x).\n",
dev->dev->address, dd->idVendor, dd->idProduct);
return -1; // Doesn't support AOA (probably no Android device)
}
if (vb2api_gbb_read_hwid(vboot_get_context(), hwid, &hwid_size))
hwid[0] = '\0';
printf("USB device %d supports Android Open Accessory! "
"Trying to connect to Chrome OS Recovery app...\n",
dev->dev->address);
if (aoa_set_string(dev, AOA_STRING_MANUFACTURER, manufacturer) < 0 ||
aoa_set_string(dev, AOA_STRING_MODEL, model) < 0 ||
aoa_set_string(dev, AOA_STRING_DESCRIPTION, description) < 0 ||
aoa_set_string(dev, AOA_STRING_VERSION, version) < 0 ||
aoa_set_string(dev, AOA_STRING_URI, uri) < 0 ||
aoa_set_string(dev, AOA_STRING_SERIAL, hwid) < 0) {
printf("Error sending AOA identification strings\n");
return -1;
}
if (aoa_start(dev)) {
printf("Error switching device into AOA mode\n");
return -1;
}
return 0;
}
static int aoa_recovery_probe(GenericUsbDevice *dev)
{
device_descriptor_t *dd = dev->dev->descriptor;
if (dd->idVendor == USB_VID_GOOGLE &&
(dd->idProduct == USB_PID_AOA || dd->idProduct == USB_PID_AOA_ADB))
return aoa_recovery_connect(dev);
if (aoa_try_enter(dev) == 0) {
// Note: This will break if you connect two phones at the same
// time but only the first serves a valid image. It's very hard
// to avoid, so... uhh... just don't do that?
char param[32];
snprintf(param, sizeof(param), "cros_aoa=%.4x:%.4x",
dd->idVendor, dd->idProduct);
commandline_append(param);
// If the AOA device disconnects and reconnects again after this
// we'll have more than one instance of the param. The kernel
// should only react to the last one, so this should be fine
// unless we blow the commandline buffer size, which should be
// too unlikely in practice to really become a problem.
}
// If we succeeded entering, device will drop off the bus and
// reenumerate, so still returning 0 here.
return 0;
}
static GenericUsbDriver aoa_recovery_driver = {
.probe = &aoa_recovery_probe,
};
static int aoa_recovery_driver_register(VbootInitFunc *unused)
{
list_insert_after(&aoa_recovery_driver.list_node, &generic_usb_drivers);
return 0;
}
/*
* This is intentionally registered in a VBOOT_INIT_FUNC(), not a normal
* INIT_FUNC(). This means that it will not be available during the first
* usb_poll() that's triggered by input_init() (called by common_params_init()).
* We only want this driver to run on devices that get plugged in after we're up
* and running in recovery mode, not on anything that was already plugged in
* during boot, to reduce the chance of sending AOA probe commands to devices
* that don't expect it and cause unintended behavior.
*/
VBOOT_INIT_FUNC(aoa_recovery_driver_register);