usb_block_drv: USB mass storage bulk-only driver

This driver uses the Usb session interface and provides a Block session
to its client. See _repos/os/src/drivers/usb_block/README' for more
information.

Fixes #1885.
This commit is contained in:
Josef Söntgen 2016-02-08 19:02:55 +01:00
parent 35314c8397
commit 6d1d8afa57
6 changed files with 1723 additions and 0 deletions

161
repos/os/run/usb_block.run Normal file
View File

@ -0,0 +1,161 @@
set use_qemu [have_include "power_on/qemu"]
#
# Build
#
set build_components {
core init
drivers/timer
drivers/usb
drivers/usb_block
server/report_rom
test/blk/cli
test/blk/bench
}
lappend_if [have_spec gpio] build_components drivers/gpio
source ${genode_dir}/repos/base/run/platform_drv.inc
append_platform_drv_build_components
build $build_components
create_boot_directory
#
# Generate config
#
set config {
<config verbose="yes">
<parent-provides>
<service name="ROM"/>
<service name="RAM"/>
<service name="IRQ"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
<service name="CAP"/>
<service name="PD"/>
<service name="RM"/>
<service name="CPU"/>
<service name="LOG"/>
<service name="SIGNAL"/>
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>}
append_platform_drv_config
append_if [have_spec gpio] config {
<start name="gpio_drv">
<resource name="RAM" quantum="4M"/>
<provides><service name="Gpio"/></provides>
<config/>
</start>}
append config {
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Timer"/> </provides>
</start>
<start name="report_rom">
<resource name="RAM" quantum="1M"/>
<provides> <service name="Report"/> <service name="ROM"/> </provides>
<config verbose="yes">
<policy report="usb_drv -> devices"/>
</config>
</start>
<start name="usb_drv">
<resource name="RAM" quantum="12M"/>
<provides> <service name="Usb"/> </provides>
<config uhci="yes" ehci="yes" xhci="yes">
<raw>
<report devices="no"/>}
append_if [expr !$use_qemu] config {
<!--
The order is important because the first policy always matches.
-->
<!-- zte open c needs interface="3" -->
<policy vendor="0x19d2" product="0x1350"/>
<!-- kingston -->
<policy vendor="0x0951" product="0x1666"/>
<!-- voyager gt stick -->
<policy vendor="0x1b1c" product="0x1a09"/>
<!-- usb3 hdd adapter -->
<policy vendor="0x174c" product="0x5106"/>
<!-- lenovo disc -->
<policy vendor="0x0984" product="0x0066"/>
}
append_if $use_qemu config {
<policy bus="0x001" dev="0x002"/> }
append config {
</raw>
</config>
<route>
<service name="Report"> <child name="report_rom"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="usb_block_drv">
<resource name="RAM" quantum="4M"/>
<provides> <service name="Block"/> </provides>
<config report="yes"/>
<route>
<service name="Usb"> <child name="usb_drv"/> </service>
<service name="Report"> <child name="report_rom"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="test-usb">
<resource name="RAM" quantum="4M"/>
<!-- <binary name="test-blk-bench"/> -->
<binary name="test-blk-cli"/>
<config>
<libc stdout="/dev/log">
<vfs> <dir name="dev"> <log/> </dir> </vfs>
</libc>
</config>
<route>
<service name="Block"> <child name="usb_block_drv"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
</config>}
install_config $config
#
# Boot modules
#
# generic modules
set boot_modules {
core init timer report_rom usb_drv usb_block_drv
test-blk-cli test-blk-bench
ld.lib.so libc.lib.so
}
append_platform_drv_boot_modules
build_boot_image $boot_modules
#
# Execute test case
#
set disk_image "bin/test.img"
set cmd "dd if=/dev/zero of=$disk_image bs=1M count=16"
if {$use_qemu} {
puts "creating disk image:\n$cmd"
catch { exec sh -c $cmd }
}
#
# Qemu opts for EHCI
#
append qemu_args " -m 128 -nographic -M pc -boot order=d "
append qemu_args " -drive if=none,id=disk,file=$disk_image "
append qemu_args " -device usb-ehci,id=ehci -device usb-storage,bus=ehci.0,drive=disk "
run_genode_until {.*child "test-usb" exited with exit value 0.*} 100

View File

@ -0,0 +1,71 @@
This directory contains an USB Mass Storage Bulk-Only driver. It uses
the Usb session interface to access the USB device and provides Genode's
Block service to its client.
Behavior
--------
This driver only supports USB Mass Storage Bulk-Only devices that use the
SCSI Block Commands set (direct-access). Devices using different command
sets, e.g, SD/HC devices or some external disc drives will not work properly
if at all. The following configuration snippets demonstrates how to use the
driver:
!<start name="usb_block_drv">
! <resource name="RAM" quantum="4M"/>
! <provides> <service name="Block"/> </provides>
! <config label="usb_stick" report="yes" writeable="yes" interface="0" lun="0"/>
! <route>
! <service name="Usb"> <child name="usb_drv"/> </service>
! <any-service> <parent/> <any-child/> </any-service>
! </route>
!</start>
The drivers 'config' node features a few attributes. First, there is the 'label'
attribute. This attribute specifies the label used when opening the Usb session
connection. A matching policy has to be configured at the USB host controller
driver:
!<start name="usb_drv">
! <resource name="RAM" quantum="8M"/>
! <provides><service name="Usb"/></provides>
! <config uhci="yes" ehci="yes" xhci="yes">
! <raw>
! <policy label="usb_block_drv -> usb_stick" vendor_id="0x13fe" product_id="0x5200"/>
! </raw>
! </config>
!</start>
For static configurations the 'label' value may be chosen freely or may even
be ommitted entirely. On the other hand it is best for dynamic configurations
to choose a unique label, e.g. 'usb-<bus>-<dev>', at run-time (this most likely
involves other components that generate configurations for the 'usb_block_drv'
as well as the 'usb_drv').
The second attribute is 'report'. If its value is 'yes' the driver
will generate a 'devices' report that contains meta information about the
USB device it is accessing and hence the Block session it is provding, e.g.:
!<devices>
! <device vendor="QEMU" product="QEMU USB HARDDRIVE" block_size="512" block_count="8192" writeable="no"/>
!</devices>
The report contains a 'device' node that includes the device's vendor name and
the product description as well as its block size and the number of blocks.
Although the parent node is called 'devices' the driver is only able to access
one and the same device and the report will therefore always contain one device
only.
In addition to other attributes that can be used to configure sepecific aspects
of the driver. The 'writeable' attribute denotes the permission of the Block
session client to write to the USB device. Independent thereof the driver will
query the device and will set the Block session operations accordingly. The
'interface' specifies the USB interface the driver should use. If the device
provides multiple SCSI devices the 'lun' attribute is used to select the right
one.
The configuration of the USB block driver cannot be changed at run-time. The
driver is either used in a static system configuration where it is configured
once or in case of a dynamic system configuration a new driver instance with
a proper configuration is created on demand.

View File

@ -0,0 +1,257 @@
/*
* \brief USB Command Block and Status Wrapper
* \author Josef Soentgen
* \date 2016-02-08
*/
/*
* Copyright (C) 2016 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _CBW_CSW_H_
#define _CBW_CSW_H_
#include <scsi.h>
namespace Usb {
struct Cbw;
struct Csw;
}
using Genode::uint8_t;
using Genode::uint32_t;
using Genode::uint64_t;
using Genode::size_t;
using Genode::addr_t;
/*****************************************************
** USB Command Block/Status Wrapper implementation **
*****************************************************/
struct Usb::Cbw : Genode::Mmio
{
enum { LENGTH = 31 };
enum { SIG = 0x43425355U };
struct Sig : Register<0x0, 32> { }; /* signature */
struct Tag : Register<0x4, 32> { }; /* transaction unique identifier */
struct Dtl : Register<0x8, 32> { }; /* data transfer length */
struct Flg : Register<0xc, 8> { }; /* flags */
struct Lun : Register<0xd, 8> { }; /* logical unit number */
struct Cbl : Register<0xe, 8> { }; /* command buffer length */
Cbw(addr_t addr, uint32_t t, uint32_t d,
uint8_t f, uint8_t l, uint8_t len) : Mmio(addr)
{
write<Sig>(SIG);
write<Tag>(t);
write<Dtl>(d);
write<Flg>(f);
write<Lun>(l);
write<Cbl>(len);
}
void dump()
{
PLOG("Sig: 0x%04x", read<Sig>());
PLOG("Tag: %u", read<Tag>());
PLOG("Dtl: %u", read<Dtl>());
PLOG("Flg: 0x%x", read<Flg>());
PLOG("Lun: %u", read<Lun>());
PLOG("Cbl: %u", read<Cbl>());
}
};
struct Test_unit_ready : Usb::Cbw,
Scsi::Test_unit_ready
{
Test_unit_ready(addr_t addr, uint32_t tag, uint8_t lun)
:
Cbw(addr, tag, 0, Usb::ENDPOINT_IN, lun,
Scsi::Test_unit_ready::LENGTH),
Scsi::Test_unit_ready(addr+15)
{ if (verbose_scsi) dump(); }
void dump()
{
PLOG("--- Dump TEST_UNIT_READY command --");
Cbw::dump();
Scsi::Cmd_6::dump();
}
};
struct Request_sense : Usb::Cbw, Scsi::Request_sense
{
Request_sense(addr_t addr, uint32_t tag, uint8_t lun)
:
Cbw(addr, tag, Scsi::Request_sense_response::LENGTH,
Usb::ENDPOINT_IN, lun, Scsi::Request_sense::LENGTH),
Scsi::Request_sense(addr+15)
{ if (verbose_scsi) dump(); }
void dump()
{
PLOG("--- Dump REQUEST_SENSE command --");
Cbw::dump();
Scsi::Cmd_6::dump();
}
};
struct Inquiry : Usb::Cbw, Scsi::Inquiry
{
Inquiry(addr_t addr, uint32_t tag, uint8_t lun)
:
Cbw(addr, tag, Scsi::Inquiry_response::LENGTH,
Usb::ENDPOINT_IN, lun, Scsi::Inquiry::LENGTH),
Scsi::Inquiry(addr+15)
{ if (verbose_scsi) dump(); }
void dump()
{
PLOG("--- Dump INQUIRY command --");
Cbw::dump();
Scsi::Cmd_6::dump();
}
};
struct Read_capacity_10 : Usb::Cbw, Scsi::Read_capacity_10
{
Read_capacity_10(addr_t addr, uint32_t tag, uint8_t lun)
:
Cbw(addr, tag, Scsi::Capacity_response_10::LENGTH,
Usb::ENDPOINT_IN, lun, Scsi::Read_capacity_10::LENGTH),
Scsi::Read_capacity_10(addr+15)
{ if (verbose_scsi) dump(); }
void dump()
{
PLOG("--- Dump READ_CAPACITY_10 command --");
Cbw::dump();
Scsi::Cmd_10::dump();
}
};
struct Read_10 : Usb::Cbw, Scsi::Read_10
{
Read_10(addr_t addr, uint32_t tag, uint8_t lun,
uint32_t lba, uint32_t len, uint32_t block_size)
:
Cbw(addr, tag, len * block_size,
Usb::ENDPOINT_IN, lun, Scsi::Read_10::LENGTH),
Scsi::Read_10(addr+15, lba, len)
{ if (verbose_scsi) dump(); }
void dump()
{
PLOG("--- Dump READ_10 command --");
Cbw::dump();
Scsi::Cmd_10::dump();
}
};
struct Write_10 : Usb::Cbw, Scsi::Write_10
{
Write_10(addr_t addr, uint32_t tag, uint8_t lun,
uint32_t lba, uint32_t len, uint32_t block_size)
:
Cbw(addr, tag, len * block_size,
Usb::ENDPOINT_OUT, lun, Scsi::Write_10::LENGTH),
Scsi::Write_10(addr+15, lba, len)
{ if (verbose_scsi) dump(); }
void dump()
{
PLOG("--- Dump WRITE_10 command --");
Cbw::dump();
Scsi::Cmd_10::dump();
}
};
struct Read_capacity_16 : Usb::Cbw, Scsi::Read_capacity_16
{
Read_capacity_16(addr_t addr, uint32_t tag, uint8_t lun)
:
Cbw(addr, tag, Scsi::Capacity_response_16::LENGTH,
Usb::ENDPOINT_IN, lun, Scsi::Read_capacity_16::LENGTH),
Scsi::Read_capacity_16(addr+15)
{ if (verbose_scsi) dump(); }
void dump()
{
PLOG("--- Dump READ_CAPACITY_16 command --");
Cbw::dump();
Scsi::Cmd_16::dump();
}
};
struct Read_16 : Usb::Cbw, Scsi::Read_16
{
Read_16(addr_t addr, uint32_t tag, uint8_t lun,
uint32_t lba, uint32_t len, uint32_t block_size)
:
Cbw(addr, tag, len * block_size,
Usb::ENDPOINT_IN, lun, Scsi::Read_16::LENGTH),
Scsi::Read_16(addr+15, lba, len)
{ if (verbose_scsi) dump(); }
void dump()
{
PLOG("--- Dump READ_16 command --");
Cbw::dump();
Scsi::Cmd_16::dump();
}
};
struct Write_16 : Usb::Cbw, Scsi::Write_16
{
Write_16(addr_t addr, uint32_t tag, uint8_t lun,
uint32_t lba, uint32_t len, uint32_t block_size)
:
Cbw(addr, tag, len * block_size,
Usb::ENDPOINT_OUT, lun, Scsi::Write_16::LENGTH),
Scsi::Write_16(addr+15, lba, len)
{ if (verbose_scsi) dump(); }
void dump()
{
PLOG("--- Dump WRITE_16 command --");
Cbw::dump();
Scsi::Cmd_16::dump();
}
};
struct Usb::Csw : Genode::Mmio
{
enum { LENGTH = 13 };
enum { SIG = 0x53425355U };
struct Sig : Register<0x0, 32> { }; /* signature */
struct Tag : Register<0x4, 32> { }; /* transaction identifier */
struct Dr : Register<0x8, 32> { }; /* data residue */
enum { PASSED = 0, FAILED = 1, PHASE_ERROR = 2 };
struct Sts : Register<0xc, 8> { }; /* status */
Csw(addr_t addr) : Mmio(addr) { }
uint32_t sig() const { return read<Sig>(); }
uint32_t tag() const { return read<Tag>(); }
uint32_t dr() const { return read<Dr>(); }
uint32_t sts() const { return read<Sts>(); }
};
#endif /* _CBW_CSW_H_ */

View File

@ -0,0 +1,803 @@
/*
* \brief Usb session to Block session translator
* \author Josef Soentgen
* \date 2016-02-08
*/
/*
* Copyright (C) 2016 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
/* Genode includes */
#include <base/allocator_avl.h>
#include <block/component.h>
#include <block/driver.h>
#include <block_session/connection.h>
#include <os/config.h>
#include <os/reporter.h>
#include <os/server.h>
#include <timer_session/connection.h>
#include <usb/usb.h>
static bool verbose_scsi = false;
/* local includes */
#include <cbw_csw.h>
namespace Usb {
using namespace Genode;
struct Block_driver;
}
/*********************************************************
** USB Mass Storage (BBB) Block::Driver implementation **
*********************************************************/
struct Usb::Block_driver : Usb::Completion,
Block::Driver
{
Server::Entrypoint &ep;
Genode::Signal_context_capability announce_sigh;
/*
* Pending block request
*/
struct Block_request
{
Block::Packet_descriptor packet;
Block::sector_t lba;
char *buffer;
size_t size;
bool read;
bool pending = false;
} req;
bool initialized = false;
bool device_plugged = false;
/**
* Handle stage change signal
*/
void handle_state_change(unsigned)
{
if (!usb.plugged()) {
PDBG("Device unplugged");
device_plugged = false;
return;
}
if (initialized) {
PERR("Device was already initialized");
return;
}
PDBG("Device plugged");
if (initialize())
Genode::Signal_transmitter(announce_sigh).submit();
}
Server::Signal_rpc_member<Block_driver> state_change_dispatcher = {
ep, *this, &Block_driver::handle_state_change };
/*
* Read Usb session label from the configuration
*/
static char const *get_label()
{
static Genode::String<256> usb_label;
try {
Genode::config()->xml_node().attribute("label").value(&usb_label);
return usb_label.string();
} catch (...) { }
return "usb_storage";
}
Genode::Allocator_avl alloc;
Usb::Connection usb { &alloc, get_label(), 2 * (1<<20), state_change_dispatcher };
Usb::Device device;
Genode::Reporter reporter { "devices" };
bool _report_device = false;
Block::Session::Operations _block_ops;
Block::sector_t _block_count;
Genode::size_t _block_size;
bool _writeable = false;
bool force_cmd_10 = false;
uint8_t active_interface = 0;
uint8_t active_lun = 0;
uint32_t active_tag = 0;
uint32_t new_tag() { return ++active_tag % 0xffffffu; }
enum Tags {
INQ_TAG = 0x01, RDY_TAG = 0x02, CAP_TAG = 0x04,
REQ_TAG = 0x08, SS_TAG = 0x10
};
enum Endpoints { IN = 0, OUT = 1 };
/*
* Completion used while initializing the device
*/
struct Init_completion : Usb::Completion
{
bool inquiry = false;
bool unit_ready = false;
bool read_capacity = false;
bool request_sense = false;
bool no_medium = false;
bool try_again = false;
Usb::Device &device;
uint8_t interface;
Block::sector_t block_count;
Genode::size_t block_size;
char vendor[Scsi::Inquiry_response::Vid::ITEMS+1];
char product[Scsi::Inquiry_response::Pid::ITEMS+1];
Init_completion(Usb::Device &device, uint8_t interface)
: device(device), interface(interface) { }
void complete(Packet_descriptor &p)
{
Interface iface = device.interface(interface);
if (p.type != Packet_descriptor::BULK) {
PERR("Can only handle BULK packets");
iface.release(p);
return;
}
if (!p.succeded) {
PERR("init complete error: packet not succeded");
iface.release(p);
return;
}
/* OUT transfer finished */
if (!p.is_read_transfer()) {
iface.release(p);
return;
}
int const actual_size = p.transfer.actual_size;
char * const data = reinterpret_cast<char*>(iface.content(p));
using namespace Scsi;
switch (actual_size) {
case 36: /* min INQUIRY data size */
case Inquiry_response::LENGTH:
{
Inquiry_response r((addr_t)data);
if (verbose_scsi) r.dump();
if (!r.sbc())
PWRN("Device does not use SCSI Block Commands and may not work");
r.get_id<Inquiry_response::Vid>(vendor, sizeof(vendor));
r.get_id<Inquiry_response::Pid>(product, sizeof(product));
break;
}
case Capacity_response_10::LENGTH:
{
Capacity_response_10 r((addr_t)data);
if (verbose_scsi) r.dump();
block_count = r.block_count();
block_size = r.block_size();
break;
}
case Capacity_response_16::LENGTH:
{
Capacity_response_16 r((addr_t)data);
if (verbose_scsi) r.dump();
block_count = r.block_count();
block_size = r.block_size();
break;
}
case Request_sense_response::LENGTH:
{
Request_sense_response r((addr_t)data);
if (verbose_scsi) r.dump();
uint8_t const asc = r.read<Request_sense_response::Asc>();
uint8_t const asq = r.read<Request_sense_response::Asq>();
enum { MEDIUM_NOT_PRESENT = 0x3a, NOT_READY_TO_READY_CHANGE = 0x28 };
switch (asc) {
case MEDIUM_NOT_PRESENT:
PERR("Not ready - medium not present");
no_medium = true;
break;
case NOT_READY_TO_READY_CHANGE: /* asq == 0x00 */
PWRN("Not ready - try again");
try_again = true;
break;
default:
PERR("Request_sense_response asc: 0x%02x asq: 0x%02x", asc, asq);
break;
}
break;
}
case Csw::LENGTH:
{
Csw csw((addr_t)data);
uint32_t const sig = csw.sig();
if (sig != Csw::SIG) {
PERR("CSW signature does not match: 0x%04x", sig);
break;
}
uint32_t const tag = csw.tag();
uint8_t const status = csw.sts();
if (status != Csw::PASSED) {
PERR("CSW failed: 0x%02x tag: %u", status, tag);
break;
}
inquiry |= tag & INQ_TAG;
unit_ready |= tag & RDY_TAG;
read_capacity |= tag & CAP_TAG;
request_sense |= tag & REQ_TAG;
break;
}
default: break;
}
iface.release(p);
}
} init { device, active_interface };
/**
* Send CBW
*/
void cbw(void *cb, Completion *c, bool block = false)
{
enum { CBW_VALID_SIZE = Cbw::LENGTH };
Usb::Interface &iface = device.interface(active_interface);
Usb::Endpoint &ep = iface.endpoint(OUT);
Usb::Packet_descriptor p = iface.alloc(CBW_VALID_SIZE);
memcpy(iface.content(p), cb, CBW_VALID_SIZE);
iface.bulk_transfer(p, ep, 0, block, c);
}
/**
* Receive CSW
*/
void csw(Completion *c, bool block = false)
{
enum { CSW_VALID_SIZE = Csw::LENGTH };
Usb::Interface &iface = device.interface(active_interface);
Usb::Endpoint &ep = iface.endpoint(IN);
Usb::Packet_descriptor p = iface.alloc(CSW_VALID_SIZE);
iface.bulk_transfer(p, ep, 0, block, c);
}
/**
* Receive response
*/
void resp(size_t size, Completion *c, bool block = false)
{
Usb::Interface &iface = device.interface(active_interface);
Usb::Endpoint &ep = iface.endpoint(IN);
Usb::Packet_descriptor p = iface.alloc(size);
iface.bulk_transfer(p, ep, 0, block, c);
}
/**
* Report block device
*/
void report_device(char const *vendor, char const *product,
Block::sector_t count, size_t size)
{
try {
Genode::Reporter::Xml_generator xml(reporter, [&] () {
xml.node("device", [&] () {
xml.attribute("vendor", vendor);
xml.attribute("product", product);
xml.attribute("block_count", count);
xml.attribute("block_size", size);
xml.attribute("writeable", _writeable);
});
});
} catch (...) { PWRN("Could not report block device"); }
}
/**
* Initialize device
*
* All USB transfers in this method are done synchronously. First we reset
* the device, than we query the max LUN. Afterwards we start sending CBWs.
*
* Since it might take some time for the device to get ready to use, we
* have to check the SCSI logical unit several times.
*/
bool initialize()
{
device.update_config();
Interface &iface = device.interface(active_interface);
try { iface.claim(); }
catch (Usb::Session::Interface_already_claimed) {
PERR("Device already claimed");
return false;
} catch (Usb::Session::Interface_not_found) {
PERR("Interface not found");
return false;
}
enum {
ICLASS_MASS_STORAGE = 8,
ISUBCLASS_SCSI = 6,
IPROTO_BULK_ONLY = 80
};
try {
Alternate_interface &alt_iface = iface.alternate_interface(0);
iface.set_alternate_interface(alt_iface);
if (alt_iface.iclass != ICLASS_MASS_STORAGE
|| alt_iface.isubclass != ISUBCLASS_SCSI
|| alt_iface.iprotocol != IPROTO_BULK_ONLY) {
PERR("No mass storage SCSI bulk-only device");
return false;
}
} catch (Usb::Session::Interface_not_found) {
PERR("Interface not found");
return false;
}
try {
/* reset */
Usb::Packet_descriptor p = iface.alloc(0);
iface.control_transfer(p, 0x21, 0xff, 0, active_interface, 100);
if (!p.succeded) {
PERR("Could not reset device");
throw -1;
}
/*
* Let us do GetMaxLUN and simply ignore the return value because none
* of the devices that were tested did infact report another value than 0.
*/
p = iface.alloc(1);
iface.control_transfer(p, 0xa1, 0xfe, 0, active_interface, 100);
uint8_t max_lun = *(uint8_t*)iface.content(p);
if (p.succeded && max_lun == 0) { max_lun = 1; }
iface.release(p);
/*
* Query device
*/
char cbw_buffer[Cbw::LENGTH];
/*
* We should probably execute the SCSI REPORT_LUNS command first
* but we will receive LOGICAL UNIT NOT SUPPORTED if we try to
* access an invalid unit. The user has to specify the LUN in
* the configuration anyway.
*/
/* Scsi::Opcode::INQUIRY */
Inquiry inq((addr_t)cbw_buffer, INQ_TAG, active_lun);
cbw(cbw_buffer, &init, true);
resp(Scsi::Inquiry_response::LENGTH, &init, true);
csw(&init, true);
if (!init.inquiry) {
PWRN("Inquiry_cmd failed");
throw -1;
}
/* Scsi::Opcode::TEST_UNIT_READY */
{
Timer::Connection timer;
/*
* It might take some time for devices to get ready (e.g. the ZTE Open C
* takes 3 retries to actually present us a medium and another try to
* let us use the medium.
*/
enum { MAX_RETRIES = 10 };
int retries;
for (retries = 0; retries < MAX_RETRIES; retries++) {
Test_unit_ready unit_ready((addr_t)cbw_buffer, RDY_TAG, active_lun);
cbw(cbw_buffer, &init, true);
csw(&init, true);
if (!init.unit_ready) {
Request_sense sense((addr_t)cbw_buffer, REQ_TAG, active_lun);
cbw(cbw_buffer, &init, true);
resp(Scsi::Request_sense_response::LENGTH, &init, true);
csw(&init, true);
if (!init.request_sense) {
PWRN("Request_sense failed");
throw -1;
}
if (init.no_medium) {
/* do nothing for now */
} else if (init.try_again) {
init.try_again = false;
} else break;
} else break;
timer.msleep(1000);
}
if (retries == MAX_RETRIES) {
PWRN("Test_unit_ready_cmd failed");
throw -1;
}
}
/* Scsi::Opcode::READ_CAPACITY_16 */
Read_capacity_16 read_cap((addr_t)cbw_buffer, CAP_TAG, active_lun);
cbw(cbw_buffer, &init, true);
resp(Scsi::Capacity_response_16::LENGTH, &init, true);
csw(&init, true);
if (!init.read_capacity) {
/* try Scsi::Opcode::READ_CAPACITY_10 next */
Read_capacity_10 read_cap((addr_t)cbw_buffer, CAP_TAG, active_lun);
cbw(cbw_buffer, &init, true);
resp(Scsi::Capacity_response_10::LENGTH, &init, true);
csw(&init, true);
if (!init.read_capacity) {
PWRN("Read_capacity_cmd failed");
throw -1;
}
PWRN("Device does not support CDB 16-byte commands, force 10-byte commands");
force_cmd_10 = true;
}
_block_size = init.block_size;
_block_count = init.block_count;
initialized = true;
device_plugged = true;
char vendor[32];
char product[32];
device.manufactorer_string.to_char(vendor, sizeof(vendor));
device.product_string.to_char(product, sizeof(product));
PINF("Found USB device: %s (%s) block size: %zu count: %llu",
vendor, product, _block_size, _block_count);
if (_report_device)
report_device(init.vendor, init.product,
init.block_count, init.block_size);
return true;
} catch (int) {
/* handle command failures */
PERR("Could not initialize storage device");
return false;
} catch (...) {
/* handle Usb::Session failures */
PERR("Could not initialize storage device");
throw;
}
return false;
}
/**
* Execute pending request
*
* Called after the CBW has been successfully received by the device
* to initiate read/write transaction.
*/
bool execute_pending_request()
{
Usb::Interface &iface = device.interface(active_interface);
Usb::Endpoint ep = iface.endpoint(req.read ? IN : OUT);
Usb::Packet_descriptor p = iface.alloc(req.size);
if (!req.read) memcpy(iface.content(p), req.buffer, req.size);
iface.bulk_transfer(p, ep, 0, false, this);
return true;
}
/**
* Acknowledge currently pending request
*
* After receiving the CSW ack the request at the Block session.
*/
void ack_pending_request(bool success = true)
{
/*
* Needs to be reset bevor calling ack_packet to prevent getting a new
* request imediately and throwing Request_congestion() in io() again.
*/
req.pending = false;
Block::Packet_descriptor p = req.packet;
ack_packet(p, success);
}
/**
* Handle packet completion
*
* This method is called several times while doing one transaction. First
* the CWB is sent, than the payload read or written. At the end, the CSW
* is requested.
*/
void complete(Packet_descriptor &p)
{
Interface iface = device.interface(active_interface);
if (p.type != Packet_descriptor::BULK) {
PERR("No BULK packet");
iface.release(p);
return;
}
if (!p.succeded) {
PERR("complete error: packet not succeded");
if (req.pending) {
PERR("req.pending: tag: %u is_read: %d buffer: %p lba: %llu size: %zu",
active_tag, req.read, req.buffer, req.lba, req.size);
ack_pending_request(false);
}
iface.release(p);
return;
}
static bool request_executed = false;
if (!p.is_read_transfer()) {
/* send read/write request */
if (req.pending) {
/*
* The CBW was successfully sent to the device, now read/write the
* actual content.
*/
if (!request_executed) {
request_executed = execute_pending_request();
} else {
/* the content was successfully written, get the CSW */
csw(this);
}
}
iface.release(p);
return;
}
int actual_size = p.transfer.actual_size;
if (actual_size < 0) {
PERR("Transfer actual size: %d", actual_size);
actual_size = 0;
}
/* the size indicates an IN I/O packet */
if ((uint32_t)actual_size >= _block_size) {
if (req.pending) {
/* the content was successfully read, get the CSW */
memcpy(req.buffer, iface.content(p), actual_size);
csw(this);
}
iface.release(p);
return;
}
/* when ending up here, we should have gotten an CSW packet */
if (actual_size != Csw::LENGTH)
PWRN("This is not the actual size you are looking for");
do {
Csw csw((addr_t)iface.content(p));
uint32_t const sig = csw.sig();
if (sig != Csw::SIG) {
PERR("CSW signature does not match: 0x%04x", sig);
break;
}
uint32_t const tag = csw.tag();
if (tag != active_tag) {
PERR("CSW tag mismatch. Got %u expected: %u", tag, active_tag);
break;
}
uint8_t const status = csw.sts();
if (status != Csw::PASSED) {
PERR("CSW failed: 0x%02x tag: %u req.read: %d req.buffer: %p "
"req.lba: %llu req.size: %zu", status, tag, req.read,
req.buffer, req.lba, req.size);
break;
}
uint32_t const dr = csw.dr();
if (dr) PWRN("CSW data residue: %u not considered", dr);
/* ack Block::Packet_descriptor */
request_executed = false;
ack_pending_request();
} while (0);
iface.release(p);
}
/**
* Parse configuration
*/
void parse_config()
{
Genode::Xml_node config = Genode::config()->xml_node();
_block_ops.set_operation(Block::Packet_descriptor::READ);
_writeable = config.attribute_value<bool>("writeable", false);
if (_writeable)
_block_ops.set_operation(Block::Packet_descriptor::WRITE);
_report_device = config.attribute_value<bool>("report", false);
active_interface = config.attribute_value<unsigned long>("interface", 0);
active_lun = config.attribute_value<unsigned long>("lun", 0);
verbose_scsi = config.attribute_value<bool>("verbose_scsi", false);
}
/**
* Constructor
*
* \param alloc allocator used by Usb::Connection
* \param ep Server::Endpoint
* \param sigh signal context used for annoucing Block service
*/
Block_driver(Genode::Allocator &alloc, Server::Entrypoint &ep,
Genode::Signal_context_capability sigh)
:
ep(ep), announce_sigh(sigh), alloc(Genode::env()->heap()),
device(Genode::env()->heap(), usb, ep)
{
parse_config();
reporter.enabled(true);
/* USB device gets initialized by handle_state_change() */
}
/**
* Send CBW
*/
void send_cbw(Block::sector_t lba, size_t len, bool read)
{
uint32_t const t = new_tag();
char cb[Cbw::LENGTH];
if (read) {
if (!force_cmd_10) Read_16 r((addr_t)cb, t, active_lun, lba, len, _block_size);
else Read_10 r((addr_t)cb, t, active_lun, lba, len, _block_size);
} else {
if (!force_cmd_10) Write_16 w((addr_t)cb, t, active_lun, lba, len, _block_size);
else Write_10 w((addr_t)cb, t, active_lun, lba, len, _block_size);
}
cbw(cb, this);
}
/**
* Perform IO/ request
*
* \param read set to true when reading, false when writting
* \param lba address of the starting block
* \param buffer source/destination buffer
* \param p Block::Packet_descriptor
*/
void io(bool read, Block::sector_t lba, size_t count,
char *buffer, Block::Packet_descriptor &p)
{
if (!device_plugged) throw Io_error();
if (lba+count > _block_count) throw Io_error();
if (req.pending) throw Request_congestion();
req.pending = true;
req.packet = p;
req.lba = lba;
req.size = count * _block_size;
req.buffer = buffer;
req.read = read;
send_cbw(lba, count, read);
}
/*******************************
** Block::Driver interface **
*******************************/
size_t block_size() override { return _block_size; }
Block::sector_t block_count() override { return _block_count; }
Block::Session::Operations ops() override { return _block_ops; }
void read(Block::sector_t lba, size_t count,
char *buffer, Block::Packet_descriptor &p) override {
io(true, lba, count, buffer, p); }
void write(Block::sector_t lba, size_t count,
char const *buffer, Block::Packet_descriptor &p) override {
io(false, lba, count, const_cast<char*>(buffer), p); }
void sync() override { /* maybe implement SYNCHRONIZE_CACHE_10/16? */ }
};
struct Main
{
Server::Entrypoint &ep;
void announce(unsigned)
{
Genode::env()->parent()->announce(ep.manage(root));
}
Server::Signal_rpc_member<Main> announce_dispatcher {
ep, *this, &Main::announce };
struct Factory : Block::Driver_factory
{
Genode::Allocator &alloc;
Server::Entrypoint &ep;
Genode::Signal_context_capability sigh;
Usb::Block_driver *driver = nullptr;
Factory(Genode::Allocator &alloc, Server::Entrypoint &ep,
Genode::Signal_context_capability sigh)
: alloc(alloc), ep(ep), sigh(sigh)
{
driver = new (Genode::env()->heap()) Usb::Block_driver(alloc, ep, sigh);
}
Block::Driver *create() { return driver; }
void destroy(Block::Driver *driver) { }
};
Factory factory { *Genode::env()->heap(), ep, announce_dispatcher };
Block::Root root;
Main(Server::Entrypoint &ep)
: ep(ep), root(ep, Genode::env()->heap(), factory) { }
};
/************
** Server **
************/
namespace Server {
char const *name() { return "usb_block_ep"; }
size_t stack_size() { return 2*1024*sizeof(long); }
void construct(Entrypoint &ep) { static Main main(ep); }
}

View File

@ -0,0 +1,427 @@
/*
* \brief SCSI Block Commands
* \author Josef Soentgen
* \date 2016-02-08
*/
/*
* Copyright (C) 2016 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#ifndef _SCSI_H_
#define _SCSI_H_
#include <base/stdint.h>
#include <util/endian.h>
#include <util/mmio.h>
namespace Scsi {
using namespace Genode;
uint16_t be16(uint16_t val);
uint32_t be32(uint32_t val);
uint64_t be64(uint64_t val);
/******************
* SCSI commands **
******************/
enum Opcode {
TEST_UNIT_READY = 0x00,
REQUEST_SENSE = 0x03,
INQUIRY = 0x12,
START_STOP = 0x1B,
READ_CAPACITY_10 = 0x25,
READ_10 = 0x28,
WRITE_10 = 0x2a,
READ_16 = 0x88,
WRITE_16 = 0x8a,
READ_CAPACITY_16 = 0x9e,
};
struct Inquiry_response;
struct Request_sense_response;
struct Capacity_response_10;
struct Capacity_response_16;
struct Cmd_6;
struct Test_unit_ready;
struct Request_sense;
struct Inquiry;
struct Start_stop;
struct Cmd_10;
struct Read_capacity_10;
struct Io_10;
struct Read_10;
struct Write_10;
struct Cmd_16;
struct Read_capacity_16;
struct Io_16;
struct Read_16;
struct Write_16;
}
/*******************
** Endian helper **
*******************/
Genode::uint16_t Scsi::be16(Genode::uint16_t val)
{
Genode::uint8_t *p = reinterpret_cast<Genode::uint8_t*>(&val);
return (p[1]<<0)|(p[0]<<8);
}
Genode::uint32_t Scsi::be32(Genode::uint32_t val)
{
Genode::uint8_t *p = reinterpret_cast<Genode::uint8_t*>(&val);
return (p[3]<<0)|(p[2]<<8)|(p[1]<<16)|(p[0]<<24);
}
Genode::uint64_t Scsi::be64(Genode::uint64_t val)
{
Genode::uint8_t *p = reinterpret_cast<Genode::uint8_t*>(&val);
return ((((Genode::uint64_t)(p[3]<<0)|(p[2]<<8)|(p[1]<<16)|(p[0]<<24))<<32)|
(((Genode::uint32_t)(p[7]<<0)|(p[6]<<8)|(p[5]<<16)|(p[4]<<24))));
}
/***************************
* SCSI command responses **
***************************/
struct Scsi::Inquiry_response : Genode::Mmio
{
enum { LENGTH = 36 /* default */ + 20 /* drive serial number and vendor unique */};
struct Dt : Register<0x0, 8> { }; /* device type */
struct Rm : Register<0x1, 8> { struct Rmb : Bitfield<7, 1> { }; }; /* removable media */
struct Ver : Register<0x2, 8> { }; /* version */
struct Rdf : Register<0x3, 8> { }; /* response data format */
struct Al : Register<0x4, 8> { }; /* additional ength */
struct Flg : Register<0x7, 8> { }; /* flags */
struct Vid : Register_array<0x8, 8, 8, 8> { }; /* vendor identification */
struct Pid : Register_array<0x10, 8, 16, 8> { }; /* product identification */
struct Rev : Register_array<0x20, 8, 4, 8> { }; /* product revision level */
Inquiry_response(addr_t addr) : Mmio(addr) { }
bool sbc() const { return read<Dt>() == 0x00; }
bool removable() const { return read<Rm::Rmb>(); }
template <typename ID>
bool get_id(char *dst, size_t len)
{
if (len < ID::ITEMS+1) return false;
for (uint32_t i = 0; i < ID::ITEMS; i++) dst[i] = read<ID>(i);
dst[ID::ITEMS] = 0;
return true;
}
void dump()
{
PLOG("--- Dump INQUIRY data ---");
PLOG("Dt: 0x%02x", read<Dt>());
PLOG("Rm::Rmb: %u ", read<Rm::Rmb>());
PLOG("Ver: 0x%02x", read<Ver>());
PLOG("Rdf: 0x%02x", read<Rdf>());
PLOG("Al: %u", read<Al>());
PLOG("Flg: 0x%02x", read<Flg>());
}
};
struct Scsi::Request_sense_response : Genode::Mmio
{
enum { LENGTH = 18 };
struct Rc : Register<0x0, 8>
{
struct V : Bitfield<6, 1> { }; /* valid bit */
struct Ec : Bitfield<0, 7> { }; /* error code */
}; /* response code */
struct Flg : Register<0x2, 8>
{
struct Sk : Bitfield<0, 4> { }; /* sense key */
}; /* flags */
struct Inf : Register<0x3, 32> { }; /* information BE */
struct Asl : Register<0x7, 8> { }; /* additional sense length */
struct Csi : Register<0x8, 32> { }; /* command specific information BE */
struct Asc : Register<0xc, 8> { }; /* additional sense code */
struct Asq : Register<0xd, 8> { }; /* additional sense code qualifier */
struct Fru : Register<0xe, 8> { }; /* field replaceable unit code */
struct Sks : Register<0xf, 32> { }; /* sense key specific (3 byte) */
Request_sense_response(addr_t addr) : Mmio(addr) { }
void dump()
{
PLOG("--- Dump REQUEST_SENSE data ---");
PLOG("Rc::V: %u", read<Rc::V>());
PLOG("Rc::Ec: 0x%02x", read<Rc::Ec>());
PLOG("Flg::Sk: 0x%02x", read<Flg::Sk>());
PLOG("Asc: 0x%02x", read<Asc>());
PLOG("Asq: 0x%02x", read<Asq>());
}
};
struct Scsi::Capacity_response_10 : Genode::Mmio
{
enum { LENGTH = 8 };
struct Bc : Register<0x0, 32> { };
struct Bs : Register<0x4, 32> { };
Capacity_response_10(addr_t addr) : Mmio(addr) { }
uint32_t block_count() const { return be32(read<Bc>()); }
uint32_t block_size() const { return be32(read<Bs>()); }
void dump()
{
PLOG("--- Dump READ_CAPACITY_10 data ---");
PLOG("Bc: 0x%04x", block_count());
PLOG("Bs: 0x%04x", block_size());
}
};
struct Scsi::Capacity_response_16 : Genode::Mmio
{
enum { LENGTH = 32 };
struct Bc : Register<0x0, 64> { };
struct Bs : Register<0x8, 32> { };
Capacity_response_16(addr_t addr) : Mmio(addr) { }
uint64_t block_count() const { return be64(read<Bc>()); }
uint32_t block_size() const { return be32(read<Bs>()); }
void dump()
{
PLOG("--- Dump READ_CAPACITY_16 data ---");
PLOG("Bc: 0x%08llx", block_count());
PLOG("Bs: 0x%04x", block_size());
}
};
/*************************
** CBD 6 byte commands **
*************************/
struct Scsi::Cmd_6 : Genode::Mmio
{
enum { LENGTH = 6 };
struct Op : Register<0x0, 8> { }; /* SCSI command */
struct Lba : Register<0x2, 16> { }; /* logical block address */
struct Len : Register<0x4, 8> { }; /* transfer length */
struct Ctl : Register<0x5, 8> { }; /* controll */
Cmd_6(addr_t addr) : Mmio(addr) { memset((void*)addr, 0, LENGTH); }
void dump()
{
PLOG("Op: 0x%02x", read<Op>());
PLOG("Lba: 0x%02x", be16(read<Lba>()));
PLOG("Len: %u", read<Len>());
PLOG("Ctl: 0x%02x", read<Ctl>());
}
};
struct Scsi::Test_unit_ready : Cmd_6
{
Test_unit_ready(addr_t addr) : Cmd_6(addr)
{
write<Cmd_6::Op>(Opcode::TEST_UNIT_READY);
}
};
struct Scsi::Request_sense : Cmd_6
{
Request_sense(addr_t addr) : Cmd_6(addr)
{
write<Cmd_6::Op>(Opcode::REQUEST_SENSE);
write<Cmd_6::Len>(Request_sense_response::LENGTH);
}
};
struct Scsi::Inquiry : Cmd_6
{
Inquiry(addr_t addr) : Cmd_6(addr)
{
write<Cmd_6::Op>(Opcode::INQUIRY);
write<Cmd_6::Len>(Inquiry_response::LENGTH);
}
};
/* not in use for now but might come in handy in the future */
struct Scsi::Start_stop : Genode::Mmio
{
enum { LENGTH = 6 };
struct Op : Register<0x0, 8> { }; /* SCSI command */
struct I : Register<0x1, 8> {
struct Immed : Bitfield<0, 1> { }; }; /* immediate */
struct Flg : Register<0x4, 8>
{
struct Pwc : Bitfield<4, 4> { }; /* power condition */
struct Loej : Bitfield<1, 1> { }; /* load eject */
struct St : Bitfield<0, 1> { }; /* start */
}; /* flags */
Start_stop(addr_t addr) : Mmio(addr)
{
memset((void*)addr, 0, LENGTH);
write<Op>(Opcode::START_STOP);
write<I::Immed>(1);
write<Flg::Pwc>(0);
write<Flg::Loej>(1);
write<Flg::St>(1);
}
void dump()
{
PLOG("Op: 0x%02x", read<Op>());
PLOG("I::Immed: %u", read<I::Immed>());
PLOG("Flg::Pwc: 0x%02x", read<Flg::Pwc>());
PLOG("Flg::Loej: %u", read<Flg::Loej>());
PLOG("Flg::St: %u", read<Flg::St>());
}
};
/**************************
** CBD 10 byte commands **
**************************/
struct Scsi::Cmd_10 : Genode::Mmio
{
enum { LENGTH = 10 };
struct Op : Register<0x0, 8> { }; /* SCSI command */
struct Lba : Register<0x2, 32> { }; /* logical block address */
struct Len : Register<0x7, 16> { }; /* transfer length */
struct Ctl : Register<0x9, 8> { }; /* controll */
Cmd_10(addr_t addr) : Mmio(addr) { memset((void*)addr, 0, LENGTH); }
void dump()
{
PLOG("Op: 0x%0x", read<Op>());
PLOG("Lba: 0x%0x", be32(read<Lba>()));
PLOG("Len: %u", be16(read<Len>()));
PLOG("Ctl: 0x%0x", read<Ctl>());
}
};
struct Scsi::Read_capacity_10 : Cmd_10
{
Read_capacity_10(addr_t addr) : Cmd_10(addr)
{
write<Cmd_10::Op>(Opcode::READ_CAPACITY_10);
}
};
struct Scsi::Io_10 : Cmd_10
{
Io_10(addr_t addr, uint32_t lba, uint16_t len) : Cmd_10(addr)
{
write<Cmd_10::Lba>(be32(lba));
write<Cmd_10::Len>(be16(len));
}
};
struct Scsi::Read_10 : Io_10
{
Read_10(addr_t addr, uint32_t lba, uint16_t len) : Io_10(addr, lba, len)
{
write<Cmd_10::Op>(Opcode::READ_10);
}
};
struct Scsi::Write_10 : Io_10
{
Write_10(addr_t addr, uint32_t lba, uint16_t len) : Io_10(addr, lba, len)
{
write<Cmd_10::Op>(Opcode::WRITE_10);
}
};
/***********************************
** CBD 16 long LBA byte commands **
***********************************/
struct Scsi::Cmd_16 : Genode::Mmio
{
enum { LENGTH = 16 };
struct Op : Register<0x0, 8> { }; /* SCSI command */
struct Lba : Register<0x2, 64> { }; /* logical block address */
struct Len : Register<0xa, 32> { }; /* transfer length */
struct Ctl : Register<0xf, 8> { }; /* controll */
Cmd_16(addr_t addr) : Mmio(addr) { memset((void*)addr, 0, LENGTH); }
void dump()
{
PLOG("Op: 0x%0x", read<Op>());
PLOG("Lba: 0x%0llx", be64(read<Lba>()));
PLOG("Len: %u", be32(read<Len>()));
PLOG("Ctl: 0x%0x", read<Ctl>());
}
};
struct Scsi::Read_capacity_16 : Cmd_16
{
Read_capacity_16(addr_t addr) : Cmd_16(addr)
{
write<Cmd_16::Op>(Opcode::READ_CAPACITY_16);
}
};
struct Scsi::Io_16 : Cmd_16
{
Io_16(addr_t addr, uint64_t lba, uint32_t len) : Cmd_16(addr)
{
write<Cmd_16::Lba>(be64(lba));
write<Cmd_16::Len>(be32(len));
}
};
struct Scsi::Read_16 : Io_16
{
Read_16(addr_t addr, uint64_t lba, uint32_t len) : Io_16(addr, lba, len)
{
write<Cmd_16::Op>(Opcode::READ_16);
}
};
struct Scsi::Write_16 : Io_16
{
Write_16(addr_t addr, uint64_t lba, uint32_t len) : Io_16(addr, lba, len)
{
write<Cmd_16::Op>(Opcode::WRITE_16);
}
};
#endif /* _SCSI_H_ */

View File

@ -0,0 +1,4 @@
TARGET = usb_block_drv
SRC_CC = main.cc
INC_DIR = $(PRG_DIR)
LIBS = base config server