mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-23 15:32:25 +00:00
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:
parent
35314c8397
commit
6d1d8afa57
161
repos/os/run/usb_block.run
Normal file
161
repos/os/run/usb_block.run
Normal 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
|
71
repos/os/src/drivers/usb_block/README
Normal file
71
repos/os/src/drivers/usb_block/README
Normal 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.
|
257
repos/os/src/drivers/usb_block/cbw_csw.h
Normal file
257
repos/os/src/drivers/usb_block/cbw_csw.h
Normal 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_ */
|
803
repos/os/src/drivers/usb_block/main.cc
Normal file
803
repos/os/src/drivers/usb_block/main.cc
Normal 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); }
|
||||
}
|
427
repos/os/src/drivers/usb_block/scsi.h
Normal file
427
repos/os/src/drivers/usb_block/scsi.h
Normal 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_ */
|
4
repos/os/src/drivers/usb_block/target.mk
Normal file
4
repos/os/src/drivers/usb_block/target.mk
Normal file
@ -0,0 +1,4 @@
|
||||
TARGET = usb_block_drv
|
||||
SRC_CC = main.cc
|
||||
INC_DIR = $(PRG_DIR)
|
||||
LIBS = base config server
|
Loading…
Reference in New Issue
Block a user