Commit ac0b5b8a authored by reillyg@chromium.org's avatar reillyg@chromium.org

[usb_gadget p13] Replace LUFA with UsbTestGadget in HidConnection tests.

The culmination of all this USB gadget nonsense... the HidConnection
tests can now use the USB gadget framework I have built to validate
device I/O works. This replaces the code based on a specially programmed
Arduino running the LUFA library.

BUG=396682

Review URL: https://codereview.chromium.org/423473008

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@287364 0039d316-1c4b-4281-b951-d872f2087c98
parent ee546065
......@@ -6,10 +6,12 @@
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "device/hid/hid_connection.h"
#include "device/hid/hid_service.h"
#include "device/test/usb_test_gadget.h"
#include "net/base/io_buffer.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -19,109 +21,112 @@ namespace {
using net::IOBufferWithSize;
const int kUSBLUFADemoVID = 0x03eb;
const int kUSBLUFADemoPID = 0x204f;
const uint64_t kReport = 0x0903a65d030f8ec9ULL;
int g_read_times = 0;
void Read(scoped_refptr<HidConnection> conn);
void OnRead(scoped_refptr<HidConnection> conn,
scoped_refptr<IOBufferWithSize> buffer,
bool success,
size_t bytes) {
EXPECT_TRUE(success);
if (success) {
g_read_times++;
EXPECT_EQ(8U, bytes);
if (bytes == 8) {
uint64_t* data = reinterpret_cast<uint64_t*>(buffer->data());
EXPECT_EQ(kReport, *data);
} else {
base::MessageLoop::current()->Quit();
}
} else {
LOG(ERROR) << "~";
g_read_times++;
}
class TestCompletionCallback {
public:
TestCompletionCallback()
: callback_(base::Bind(&TestCompletionCallback::SetResult,
base::Unretained(this))) {}
if (g_read_times < 3){
base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(Read, conn));
} else {
base::MessageLoop::current()->Quit();
void SetResult(bool success, size_t size) {
result_ = success;
transferred_ = size;
run_loop_.Quit();
}
}
void Read(scoped_refptr<HidConnection> conn) {
scoped_refptr<IOBufferWithSize> buffer(new IOBufferWithSize(8));
conn->Read(buffer, base::Bind(OnRead, conn, buffer));
}
void OnWriteNormal(bool success,
size_t bytes) {
ASSERT_TRUE(success);
base::MessageLoop::current()->Quit();
}
bool WaitForResult() {
run_loop_.Run();
return result_;
}
void WriteNormal(scoped_refptr<HidConnection> conn) {
scoped_refptr<IOBufferWithSize> buffer(new IOBufferWithSize(8));
*(int64_t*)buffer->data() = kReport;
const HidConnection::IOCallback& callback() const { return callback_; }
size_t transferred() const { return transferred_; }
conn->Write(0, buffer, base::Bind(OnWriteNormal));
}
private:
const HidConnection::IOCallback callback_;
base::RunLoop run_loop_;
bool result_;
size_t transferred_;
};
} // namespace
class HidConnectionTest : public testing::Test {
protected:
virtual void SetUp() OVERRIDE {
if (!UsbTestGadget::IsTestEnabled()) return;
message_loop_.reset(new base::MessageLoopForIO());
service_.reset(HidService::Create(message_loop_->message_loop_proxy()));
ASSERT_TRUE(service_);
test_gadget_ = UsbTestGadget::Claim();
ASSERT_TRUE(test_gadget_);
ASSERT_TRUE(test_gadget_->SetType(UsbTestGadget::HID_ECHO));
device_id_ = kInvalidHidDeviceId;
base::RunLoop run_loop;
message_loop_->PostDelayedTask(
FROM_HERE,
base::Bind(&HidConnectionTest::FindDevice,
base::Unretained(this), run_loop.QuitClosure(), 5),
base::TimeDelta::FromMilliseconds(250));
run_loop.Run();
ASSERT_NE(device_id_, kInvalidHidDeviceId);
}
void FindDevice(const base::Closure& done, int retries) {
std::vector<HidDeviceInfo> devices;
service_->GetDevices(&devices);
device_id_ = kInvalidHidDeviceId;
for (std::vector<HidDeviceInfo>::iterator it = devices.begin();
it != devices.end();
++it) {
if (it->vendor_id == kUSBLUFADemoVID &&
it->product_id == kUSBLUFADemoPID) {
it != devices.end();
++it) {
if (it->serial_number == test_gadget_->GetSerial()) {
device_id_ = it->device_id;
return;
break;
}
}
}
virtual void TearDown() OVERRIDE {
service_.reset(NULL);
message_loop_.reset(NULL);
if (device_id_ == kInvalidHidDeviceId && --retries > 0) {
message_loop_->PostDelayedTask(
FROM_HERE,
base::Bind(&HidConnectionTest::FindDevice, base::Unretained(this),
done, retries),
base::TimeDelta::FromMilliseconds(10));
} else {
message_loop_->PostTask(FROM_HERE, done);
}
}
HidDeviceId device_id_;
scoped_ptr<base::MessageLoopForIO> message_loop_;
scoped_ptr<HidService> service_;
scoped_ptr<UsbTestGadget> test_gadget_;
HidDeviceId device_id_;
};
TEST_F(HidConnectionTest, Create) {
scoped_refptr<HidConnection> connection = service_->Connect(device_id_);
ASSERT_TRUE(connection || device_id_ == kInvalidHidDeviceId);
}
TEST_F(HidConnectionTest, ReadWrite) {
if (!UsbTestGadget::IsTestEnabled()) return;
TEST_F(HidConnectionTest, Read) {
scoped_refptr<HidConnection> connection = service_->Connect(device_id_);
if (connection) {
message_loop_->PostTask(FROM_HERE, base::Bind(Read, connection));
message_loop_->Run();
}
}
scoped_refptr<HidConnection> conn = service_->Connect(device_id_);
ASSERT_TRUE(conn);
for (int i = 0; i < 8; ++i) {
scoped_refptr<IOBufferWithSize> write_buffer(new IOBufferWithSize(8));
*(int64_t*)write_buffer->data() = i;
TEST_F(HidConnectionTest, Write) {
scoped_refptr<HidConnection> connection = service_->Connect(device_id_);
TestCompletionCallback write_callback;
conn->Write(0, write_buffer, write_callback.callback());
ASSERT_TRUE(write_callback.WaitForResult());
ASSERT_EQ(8UL, write_callback.transferred());
if (connection) {
message_loop_->PostTask(FROM_HERE, base::Bind(WriteNormal, connection));
message_loop_->Run();
scoped_refptr<IOBufferWithSize> read_buffer(new IOBufferWithSize(8));
TestCompletionCallback read_callback;
conn->Read(read_buffer, read_callback.callback());
ASSERT_TRUE(read_callback.WaitForResult());
ASSERT_EQ(8UL, read_callback.transferred());
ASSERT_EQ(i, *(int64_t*)read_buffer->data());
}
}
......
......@@ -22,6 +22,7 @@ class UsbTestGadget {
DEFAULT = 0,
KEYBOARD,
MOUSE,
HID_ECHO,
};
virtual ~UsbTestGadget() {}
......
......@@ -61,6 +61,7 @@ static const struct UsbTestGadgetConfiguration kConfigurations[] = {
{ UsbTestGadget::DEFAULT, "/unconfigure", 0x2000 },
{ UsbTestGadget::KEYBOARD, "/keyboard/configure", 0x2001 },
{ UsbTestGadget::MOUSE, "/mouse/configure", 0x2002 },
{ UsbTestGadget::HID_ECHO, "/hid_echo/configure", 0x2003 },
};
class UsbTestGadgetImpl : public UsbTestGadget {
......
......@@ -11,6 +11,7 @@
'usb_gadget/gadget.py',
'usb_gadget/hid_constants.py',
'usb_gadget/hid_descriptors.py',
'usb_gadget/hid_echo_gadget.py',
'usb_gadget/hid_gadget.py',
'usb_gadget/keyboard_gadget.py',
'usb_gadget/linux_gadgetfs.py',
......
......@@ -9,6 +9,7 @@ import argparse
import netifaces
from tornado import ioloop
import hid_echo_gadget
import keyboard_gadget
import linux_gadgetfs
import mouse_gadget
......@@ -48,6 +49,7 @@ def main():
server.chip = linux_gadgetfs.LinuxGadgetfs(server.hardware)
server.SwitchGadget(server.default)
hid_echo_gadget.RegisterHandlers()
keyboard_gadget.RegisterHandlers()
mouse_gadget.RegisterHandlers()
......
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A HID-class echo device.
This module provides a HID feature and HID device that can be used as an
echo test for HID drivers. The device exposes vendor-specific input, output
and feature usages that transmit 8 bytes of data. Data written sent as an
output report is echoed as an input report. The value of the feature report
can be written and read with control transfers.
"""
import struct
import hid_constants
import hid_descriptors
import hid_gadget
import usb_constants
class EchoFeature(hid_gadget.HidFeature):
REPORT_DESC = hid_descriptors.ReportDescriptor(
hid_descriptors.UsagePage(0xFF00), # Vendor Defined
hid_descriptors.Usage(0),
hid_descriptors.Collection(
hid_constants.CollectionType.APPLICATION,
hid_descriptors.LogicalMinimum(0, force_length=1),
hid_descriptors.LogicalMaximum(255, force_length=2),
hid_descriptors.ReportSize(8),
hid_descriptors.ReportCount(8),
hid_descriptors.Usage(0),
hid_descriptors.Input(hid_descriptors.Data,
hid_descriptors.Variable,
hid_descriptors.Absolute),
hid_descriptors.Usage(0),
hid_descriptors.Output(hid_descriptors.Data,
hid_descriptors.Variable,
hid_descriptors.Absolute),
hid_descriptors.Usage(0),
hid_descriptors.Feature(hid_descriptors.Data,
hid_descriptors.Variable,
hid_descriptors.Absolute)
)
)
def __init__(self):
super(EchoFeature, self).__init__()
self._input_output_report = 0
self._feature_report = 0
def SetInputReport(self, data):
self._input_output_report, = struct.unpack('<Q', data)
self.SendReport(struct.pack('<Q', self._input_output_report))
return True
def SetOutputReport(self, data):
self._input_output_report, = struct.unpack('<Q', data)
self.SendReport(struct.pack('<Q', self._input_output_report))
return True
def SetFeatureReport(self, data):
self._feature_report, = struct.unpack('<Q', data)
return True
def GetInputReport(self):
return struct.pack('<Q', self._input_output_report)
def GetOutputReport(self):
return struct.pack('<Q', self._input_output_report)
def GetFeatureReport(self):
return struct.pack('<Q', self._feature_report)
class EchoGadget(hid_gadget.HidGadget):
def __init__(self):
self._feature = EchoFeature()
super(EchoGadget, self).__init__(
report_desc=EchoFeature.REPORT_DESC,
features={0: self._feature},
packet_size=8,
interval_ms=1,
out_endpoint=True,
vendor_id=usb_constants.VendorID.GOOGLE,
product_id=usb_constants.ProductID.GOOGLE_HID_ECHO_GADGET,
device_version=0x0100)
self.AddStringDescriptor(1, 'Google Inc.')
self.AddStringDescriptor(2, 'HID Echo Gadget')
def RegisterHandlers():
from tornado import web
class WebConfigureHandler(web.RequestHandler):
def post(self):
server.SwitchGadget(EchoGadget())
import server
server.app.add_handlers('.*$', [
(r'/hid_echo/configure', WebConfigureHandler),
])
......@@ -166,3 +166,4 @@ class ProductID(object):
GOOGLE_TEST_GADGET = 0x2000
GOOGLE_KEYBOARD_GADGET = 0x2001
GOOGLE_MOUSE_GADGET = 0x2002
GOOGLE_HID_ECHO_GADGET = 0x2003
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment