From 34b4a96dbb5b4d0f23570ab22294215865af7f5e Mon Sep 17 00:00:00 2001 From: scw Date: Mon, 3 Apr 2006 08:15:48 +0000 Subject: [PATCH] Add a kernel driver and userland program for the Topfield TF5000PVR range of digital video recorders popular in Europe and Australia. These devices have a USB client port which can be used to upload and download recordings (and other files, such as MIPS binaries for execution on the DVR's CPU) to/from their internal hard disk, in addition to some other operations on files and directories. --- distrib/sets/lists/base/mi | 3 +- distrib/sets/lists/comp/mi | 3 +- distrib/sets/lists/man/mi | 6 +- etc/MAKEDEV.tmpl | 8 +- share/man/man4/Makefile | 4 +- share/man/man4/usb.4 | 7 +- share/man/man4/utoppy.4 | 312 ++++++ sys/conf/majors | 3 +- sys/dev/DEVNAMES | 3 +- sys/dev/usb/Makefile | 4 +- sys/dev/usb/files.usb | 7 +- sys/dev/usb/utoppy.c | 1917 ++++++++++++++++++++++++++++++++++++ sys/dev/usb/utoppy.h | 106 ++ usr.bin/Makefile | 4 +- usr.bin/utoppya/Makefile | 12 + usr.bin/utoppya/utoppya.1 | 169 ++++ usr.bin/utoppya/utoppya.c | 578 +++++++++++ 17 files changed, 3131 insertions(+), 15 deletions(-) create mode 100644 share/man/man4/utoppy.4 create mode 100644 sys/dev/usb/utoppy.c create mode 100644 sys/dev/usb/utoppy.h create mode 100644 usr.bin/utoppya/Makefile create mode 100644 usr.bin/utoppya/utoppya.1 create mode 100644 usr.bin/utoppya/utoppya.c diff --git a/distrib/sets/lists/base/mi b/distrib/sets/lists/base/mi index 8ece2e6fabbb..fab86c8f527b 100644 --- a/distrib/sets/lists/base/mi +++ b/distrib/sets/lists/base/mi @@ -1,4 +1,4 @@ -# $NetBSD: mi,v 1.610 2006/04/01 04:13:21 tnozaki Exp $ +# $NetBSD: mi,v 1.611 2006/04/03 08:15:49 scw Exp $ . base-sys-root ./altroot base-sys-root ./bin base-sys-root @@ -652,6 +652,7 @@ ./usr/bin/usbhidaction base-util-bin ./usr/bin/usbhidctl base-util-bin ./usr/bin/users base-util-bin +./usr/bin/utoppya base-util-bin ./usr/bin/uucp base-uucp-bin uucp ./usr/bin/uudecode base-util-bin ./usr/bin/uuencode base-util-bin diff --git a/distrib/sets/lists/comp/mi b/distrib/sets/lists/comp/mi index e8fa60f7170a..cc83319c0a7c 100644 --- a/distrib/sets/lists/comp/mi +++ b/distrib/sets/lists/comp/mi @@ -1,4 +1,4 @@ -# $NetBSD: mi,v 1.867 2006/03/30 00:00:24 rtr Exp $ +# $NetBSD: mi,v 1.868 2006/04/03 08:15:49 scw Exp $ ./etc/mtree/set.comp comp-sys-root ./usr/bin/addr2line comp-debug-bin bfd ./usr/bin/ar comp-util-bin bfd @@ -469,6 +469,7 @@ ./usr/include/dev/usb/usbcdc.h comp-obsolete obsolete ./usr/include/dev/usb/usbdevs.h comp-obsolete obsolete ./usr/include/dev/usb/usbhid.h comp-c-include +./usr/include/dev/usb/utoppy.h comp-c-include ./usr/include/dev/vinum/request.h comp-obsolete obsolete ./usr/include/dev/vinum/statetexts.h comp-obsolete obsolete ./usr/include/dev/vinum/vinumext.h comp-obsolete obsolete diff --git a/distrib/sets/lists/man/mi b/distrib/sets/lists/man/mi index 37a3b3db15e0..fe2d5dbc5ed2 100644 --- a/distrib/sets/lists/man/mi +++ b/distrib/sets/lists/man/mi @@ -1,4 +1,4 @@ -# $NetBSD: mi,v 1.872 2006/03/28 20:54:13 riz Exp $ +# $NetBSD: mi,v 1.873 2006/04/03 08:15:50 scw Exp $ ./etc/mtree/set.man man-sys-root ./usr/share/info/am-utils.info man-amd-info info ./usr/share/info/as.info man-computil-info bfd,info @@ -463,6 +463,7 @@ ./usr/share/man/cat1/usbhidaction.0 man-util-catman .cat ./usr/share/man/cat1/usbhidctl.0 man-util-catman .cat ./usr/share/man/cat1/users.0 man-util-catman .cat +./usr/share/man/cat1/utoppya.0 man-util-catman .cat ./usr/share/man/cat1/uucp.0 man-uucp-catman uucp,.cat ./usr/share/man/cat1/uudecode.0 man-uucp-catman .cat ./usr/share/man/cat1/uuencode.0 man-uucp-catman .cat @@ -1364,6 +1365,7 @@ ./usr/share/man/cat4/userconf.0 man-sys-catman .cat ./usr/share/man/cat4/usscanner.0 man-sys-catman .cat ./usr/share/man/cat4/ustir.0 man-sys-catman .cat +./usr/share/man/cat4/utoppy.0 man-sys-catman .cat ./usr/share/man/cat4/uvisor.0 man-sys-catman .cat ./usr/share/man/cat4/uvscom.0 man-sys-catman .cat ./usr/share/man/cat4/uyap.0 man-sys-catman .cat @@ -2776,6 +2778,7 @@ ./usr/share/man/man1/usbhidaction.1 man-util-man .man ./usr/share/man/man1/usbhidctl.1 man-util-man .man ./usr/share/man/man1/users.1 man-util-man .man +./usr/share/man/man1/utoppya.1 man-util-man .man ./usr/share/man/man1/uucp.1 man-uucp-man uucp,.man ./usr/share/man/man1/uudecode.1 man-uucp-man .man ./usr/share/man/man1/uuencode.1 man-uucp-man .man @@ -3677,6 +3680,7 @@ ./usr/share/man/man4/userconf.4 man-sys-man .man ./usr/share/man/man4/usscanner.4 man-sys-man .man ./usr/share/man/man4/ustir.4 man-sys-man .man +./usr/share/man/man4/utoppy.4 man-sys-man .man ./usr/share/man/man4/uvisor.4 man-sys-man .man ./usr/share/man/man4/uvscom.4 man-sys-man .man ./usr/share/man/man4/uyap.4 man-sys-man .man diff --git a/etc/MAKEDEV.tmpl b/etc/MAKEDEV.tmpl index 3f92d84cf3b8..774cb2d49dd8 100644 --- a/etc/MAKEDEV.tmpl +++ b/etc/MAKEDEV.tmpl @@ -1,5 +1,5 @@ #!/bin/sh - -# $NetBSD: MAKEDEV.tmpl,v 1.51 2006/03/25 17:19:02 christos Exp $ +# $NetBSD: MAKEDEV.tmpl,v 1.52 2006/04/03 08:15:50 scw Exp $ # # Copyright (c) 2003 The NetBSD Foundation, Inc. # All rights reserved. @@ -505,6 +505,7 @@ usbs) makedev ttyY0 ttyY1 makedev urio0 makedev uscanner0 uscanner1 + makedev utoppy0 utoppy1 makedev ugen0 ;; @@ -561,6 +562,11 @@ uscanner*) mkdev uscanner$unit c %uscanner_chr% $unit ;; +utoppy*) + unit=${i#utoppy} + mkdev utoppy$unit c %utoppy_chr% $unit + ;; + ttyY*) unit=${i#ttyY} mkdev ttyY$unit c %ucycom_chr% $(($unit + $dialin )) "" "" $u_uucp diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile index f1d964cfa543..7b083b9094c9 100644 --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -1,4 +1,4 @@ -# $NetBSD: Makefile,v 1.380 2006/03/28 20:50:57 riz Exp $ +# $NetBSD: Makefile,v 1.381 2006/04/03 08:15:49 scw Exp $ # @(#)Makefile 8.1 (Berkeley) 6/18/93 MAN= aac.4 ac97.4 acardide.4 aceride.4 acphy.4 adc.4 adt7467c.4 adv.4 \ @@ -53,7 +53,7 @@ MAN+= uaudio.4 ubsa.4 ucom.4 ucycom.4 udsbr.4 uftdi.4 ugen.4 uhid.4 \ umass.4 umct.4 umidi.4 umodem.4 ums.4 uplcom.4 urio.4 usb.4 \ uscanner.4 usscanner.4 ustir.4 uvisor.4 uvscom.4 uyap.4 \ aue.4 atu.4 axe.4 cdce.4 cue.4 kue.4 upl.4 url.4 udav.4 \ - ehci.4 ohci.4 slhci.4 uhci.4 + ehci.4 ohci.4 slhci.4 uhci.4 utoppy.4 # Ir devices MAN+= irframe.4 cir.4 irframetty.4 oboe.4 diff --git a/share/man/man4/usb.4 b/share/man/man4/usb.4 index 8dc0310c9763..0b5541746a97 100644 --- a/share/man/man4/usb.4 +++ b/share/man/man4/usb.4 @@ -1,4 +1,4 @@ -.\" $NetBSD: usb.4,v 1.80 2005/12/26 19:48:12 perry Exp $ +.\" $NetBSD: usb.4,v 1.81 2006/04/03 08:15:49 scw Exp $ .\" .\" Copyright (c) 1999-2005 The NetBSD Foundation, Inc. .\" All rights reserved. @@ -34,7 +34,7 @@ .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE .\" POSSIBILITY OF SUCH DAMAGE. .\" -.Dd August 29, 2005 +.Dd April 3, 2006 .Dt USB 4 .Os .Sh NAME @@ -185,6 +185,8 @@ driver for some USB scanners. driver for some SCSI-over-USB scanners. .It ustir driver for SigmaTel STIr4200 USB-IrDA bridges. +.It utoppy +driver for Topfield TF5000PVR range of digital video recorders. .It uvisor Handspring Visor driver. .It uvscom @@ -543,6 +545,7 @@ specifications can be found at: .Xr uscanner 4 , .Xr usscanner 4 , .Xr ustir 4 , +.Xr utoppy 4 , .Xr uvisor 4 , .Xr usbdevs 8 .Sh HISTORY diff --git a/share/man/man4/utoppy.4 b/share/man/man4/utoppy.4 new file mode 100644 index 000000000000..1f1f20cb8436 --- /dev/null +++ b/share/man/man4/utoppy.4 @@ -0,0 +1,312 @@ +.\" $NetBSD: utoppy.4,v 1.1 2006/04/03 08:15:49 scw Exp $ +.\" +.\" Copyright (c) 2006 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to The NetBSD Foundation +.\" by Steve C. Woodford. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by the NetBSD +.\" Foundation, Inc. and its contributors. +.\" 4. Neither the name of The NetBSD Foundation nor the names of its +.\" contributors may be used to endorse or promote products derived +.\" from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd April 3, 2006 +.Dt UTOPPY 4 +.Os +.Sh NAME +.Nm utoppy +.Nd USB driver for the Topfield TF5000PVR range of digital video recorders +.Sh SYNOPSIS +.Cd "utoppy* at uhub? port ?" +.Pp +.In dev/usb/utoppy.h +.Sh DESCRIPTION +The +.Nm +driver provides support for the Topfield TF5000PVR range of DVB recorders +(nicknamed +.Ql Toppy ) +which are popular in Europe and Australia. +These recorders have a +.Tn USB +device interface which can be used to transfer +recordings to and from the unit's hard disk. +The +.Tn USB +interface can also be used to upload binary images for execution +on the Toppy's MIPS cpu. +.Pp +The Toppy's +.Tn USB +protocol has not been officially documented by Topfield, +but the basic features have been reverse engineered by others in order +to write replacements for the official +.Ql Altair +download/upload program from Topfield. +.Pp +Existing replacements for Altair suffer from the fact that they are +ultimately built on top of +.Xr ugen 4 . +This has a number of detrimental side-effects: +.Bl -enum +.It +Performance suffers since all Toppy command packets have to cross the +user-kernel interface. +.It +The userland programs are full of clutter to deal with interpreting the +command/status packets, not to mention byte-swapping and host endian +issues. +.It +Signals can interrupt a data transfer at a critical point, leaving the +Toppy in an undefined state. +For example, interrupting a download with +.Ql Turbo +mode enabled will leave the Toppy completely unresponsive to the remote +control, and prevent timer-based recordings from starting. +.El +.Pp +The +.Nm +driver provides a clean and stable interface to the Toppy protocol, and +ensures that an interrupt caused by a signal does not leave the Toppy in +and undefined state. +.Sh UTOPPY INTERFACE +Use the following header file to get access to the +.Tn utoppy +specific structures and defines. +.Bd -literal +#include \*[Lt]dev/usb/utoppy.h\*[Gt] +.Ed +.Pp +The +.Nm +driver can be accessed through the +.Pa /dev/utoppyN +character device. +The primary means of controlling the driver is by issuing a series of +.Xr ioctl 2 +system calls followed by +.Xr read 2 +or +.Xr write 2 +system calls as appropriate. +.Pp +The following +.Xr ioctl 2 +commands are supported by the +.Nm +driver: +.Bl -tag -width xxxxxx +.It Dv UTOPPYIOTURBO Fa "int *mode" +This command can be used to enable or disable +.Ql Turbo +mode for subsequent +.Dv UTOPPYIOREADFILE +or +.Dv UTOPPYIOWRITEFILE +commands (see below). If +.Fa num +is non-zero, Turbo mode will be enabled. +Otherwise Turbo mode will be disabled. +In non-Turbo mode, the Toppy's +.Tn USB +interface is capable of sustaining around 5.6 Mbit/s during a file transfer. +With Turbo mode enabled, it can sustain just over 16 Mbit/s. +Of course, these figures are valid only if the Toppy is connected via a +.Tn USB +2.0 interface. +Performance using an older +.Tn USB +1 interface will be significantly lower. +.It Dv UTOPPYIOCANCEL Fa void +This command can be used to abort an in-progress +.Dv UTOPPYIOREADDIR , +.Dv UTOPPYIOREADFILE , +or +.Dv UTOPPYIOWRITEFILE +command. +.It Dv UTOPPYIOREBOOT Fa void +This command will cause the Toppy to reboot cleanly. +.It Dv UTOPPYIOSTATS Fa "struct utoppy_stats *stats" +This command retrieves statistics for the Toppy's hard disk. +.Bd -literal +struct utoppy_stats { + uint64_t us_hdd_size; /* Size of the disk, in bytes */ + uint64_t us_hdd_free; /* Free space, in bytes */ +}; +.Ed +.It UTOPPYIORENAME Fa "struct utoppy_rename *rename" +This command is used to rename a file or directory on the Toppy's +hard disk. +The full pathname to each file must be provided. +.Bd -literal +struct utoppy_rename { + char *ur_old_path; /* Path to existing file */ + char *ur_new_path; /* Path to new file */ +}; +.Ed +.It UTOPPYIOMKDIR Fa "char *path" +This command creates the directory specified by +.Fa path . +.It UTOPPYIODELETE Fa "char *path" +This command deletes the file or directory specified by +.Fa path . +.It UTOPPYIOREADDIR Fa "char *path" +This command initiates a read of the contents of the directory specified by +.Fa path . +After issuing this command, the directory contents must be read using +consecutive +.Xr read 2 +system calls. +Each +.Xr read 2 +will transfer one or more directory entries into the user-supplied buffer. +The buffer must be large enough to receive at least one directory entry. +When +.Xr read 2 +returns zero, all directory entries have been read. +.Pp +A directory entry is described using the following data structure: +.Bd -literal +struct utoppy_dirent { + char ud_path[UTOPPY_MAX_FILENAME_LEN + 1]; + enum { + UTOPPY_DIRENT_UNKNOWN, + UTOPPY_DIRENT_DIRECTORY, + UTOPPY_DIRENT_FILE + } ud_type; + off_t ud_size; + time_t ud_mtime; + uint32_t ud_attributes; +}; +.Ed +.Pp +The +.Va ud_path +field contains the name of the directory entry. +.Pp +The +.Va ud_type +field specifies whether the entry corresponds to a file or a sub-directory. +.Pp +The +.Va ud_size +field is valid for files only, and specifies the file's size in bytes. +.Pp +The +.Va ud_mtime +field describes the file or directory's modification time, specified as +seconds from the Unix epoch. +The timestamp is relative to the current timezone, so +.Xr localtime 3 +can be used to convert it into human readable form. +Note that the Toppy sets directory timestamps to a predefined value so +they are not particularly useful. +.Pp +The +.Va ud_attributes +field is not used at this time. +.It UTOPPYIOREADFILE Fa "struct utoppy_readfile *" +This command is used to initiate reading a file from the Toppy's hard disk. +The full pathname, together with the file offset at which to start reading, +is specified using the following data structure: +.Bd -literal +struct utoppy_readfile { + char *ur_path; + off_t ur_offset; +}; +.Ed +.Pp +After issuing this command, the file must be read using consecutive +.Xr read 2 +system calls. +When +.Xr read 2 +returns zero, the entire file has been read. +.It UTOPPYIOWRITEFILE Fa "struct utoppy_writefile *" +This command is used to initiate writing to a file on the Toppy's hard disk. +The file to be written is described using the following data structure: +.Bd -literal +struct utoppy_writefile { + char *uw_path; + off_t uw_offset; + off_t uw_size; + time_t uw_mtime; +}; +.Ed +.Pp +The +.Va uw_path +field specifies the full pathname of the file to be written. +.Pp +The +.Va uw_offset +field specifies the file offset at which to start writing, assuming the file +already exists. +Otherwise, +.Va uw_offset +must be zero. +.Pp +The protocol requires that the Toppy must be informed of a file's size in +advance of the file being written. +This is accomplished using the +.Va uw_size +field. +It may be possible to work around this limitation in a future version of +the driver. +.Pp +The +.Va uw_mtime +field specifies the file's timestamp expressed as seconds from the Unix epoch +in the local timezone. +.El +.Pp +Due to limitations with the protocol, a +.Nm +device can be opened by only one application at a time. +Also, only a single +.Dv UTOPPYIOREADDIR , +.Dv UTOPPYIOREADFILE , +or +.Dv UTOPPYIOWRITEFILE +command can be in progress at any given time. +.Sh FILES +.Bl -tag -width /dev/utoppy0 -compact +.It Pa /dev/utoppy0 +device node +.El +.Sh SEE ALSO +.Xr utoppya 1 , +.Xr usb 4 +.Sh HISTORY +The +.Nm +driver +appeared in +.Nx 4.0 . +.Sh AUTHORS +.An Steve C. Woodford Aq scw@netbsd.org diff --git a/sys/conf/majors b/sys/conf/majors index 8a49aab39286..01a77463500a 100644 --- a/sys/conf/majors +++ b/sys/conf/majors @@ -1,4 +1,4 @@ -# $NetBSD: majors,v 1.20 2006/02/25 17:16:45 christos Exp $ +# $NetBSD: majors,v 1.21 2006/04/03 08:15:48 scw Exp $ # # Device majors for Machine-Independent drivers. # @@ -23,3 +23,4 @@ device-major veriexec char 170 veriexec device-major fw char 171 ieee1394if device-major ucycom char 172 ucycom device-major gpio char 173 gpio +device-major utoppy char 174 utoppy diff --git a/sys/dev/DEVNAMES b/sys/dev/DEVNAMES index 366e7a0ff932..9ea1a964ae3e 100644 --- a/sys/dev/DEVNAMES +++ b/sys/dev/DEVNAMES @@ -1,4 +1,4 @@ -# $NetBSD: DEVNAMES,v 1.195 2006/03/23 16:20:59 he Exp $ +# $NetBSD: DEVNAMES,v 1.196 2006/04/03 08:15:50 scw Exp $ # # This file contains all used device names and defined attributes in # alphabetical order. New devices added to the system somewhere should first @@ -1329,6 +1329,7 @@ uscanner MI usscanner MI ustir MI ut vax +utoppy MI uu vax uvisor MI uvscom MI diff --git a/sys/dev/usb/Makefile b/sys/dev/usb/Makefile index b733eb645db1..04c267200d7b 100644 --- a/sys/dev/usb/Makefile +++ b/sys/dev/usb/Makefile @@ -1,4 +1,4 @@ -# $NetBSD: Makefile,v 1.7 2005/12/11 12:24:00 christos Exp $ +# $NetBSD: Makefile,v 1.8 2006/04/03 08:15:48 scw Exp $ # use 'make -f Makefile.usbdevs' to make usbdevs.h and usbdevs_data.h # _after_ you committed usbdevs. See comment in Makefile.usbdevs @@ -6,6 +6,6 @@ INCSDIR= /usr/include/dev/usb # Only install includes which are used by userland -INCS= ukyopon.h urio.h usb.h usbhid.h +INCS= ukyopon.h urio.h usb.h usbhid.h utoppy.h .include diff --git a/sys/dev/usb/files.usb b/sys/dev/usb/files.usb index 9d3c05af8ba3..f125cbc22a12 100644 --- a/sys/dev/usb/files.usb +++ b/sys/dev/usb/files.usb @@ -1,4 +1,4 @@ -# $NetBSD: files.usb,v 1.65 2005/12/11 12:24:00 christos Exp $ +# $NetBSD: files.usb,v 1.66 2006/04/03 08:15:48 scw Exp $ # # Config file and device description for machine-independent USB code. # Included by ports that need it. Ports that use it must provide @@ -262,3 +262,8 @@ file dev/usb/if_atu.c atu # Ralink Technology RT2500USB attach ral at uhub with ural file dev/usb/if_ural.c ural + +# Topfield digital PVRs +device utoppy +attach utoppy at uhub +file dev/usb/utoppy.c utoppy diff --git a/sys/dev/usb/utoppy.c b/sys/dev/usb/utoppy.c new file mode 100644 index 000000000000..886d6e153a13 --- /dev/null +++ b/sys/dev/usb/utoppy.c @@ -0,0 +1,1917 @@ +/* $NetBSD: utoppy.c,v 1.1 2006/04/03 08:15:48 scw Exp $ */ + +/*- + * Copyright (c) 2006 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Steve C. Woodford. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__KERNEL_RCSID(0, "$NetBSD: utoppy.c,v 1.1 2006/04/03 08:15:48 scw Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#undef UTOPPY_DEBUG +#ifdef UTOPPY_DEBUG +#define UTOPPY_DBG_OPEN 0x0001 +#define UTOPPY_DBG_CLOSE 0x0002 +#define UTOPPY_DBG_READ 0x0004 +#define UTOPPY_DBG_WRITE 0x0008 +#define UTOPPY_DBG_IOCTL 0x0010 +#define UTOPPY_DBG_SEND_PACKET 0x0020 +#define UTOPPY_DBG_RECV_PACKET 0x0040 +#define UTOPPY_DBG_ADDPATH 0x0080 +#define UTOPPY_DBG_READDIR 0x0100 +#define UTOPPY_DBG_DUMP 0x0200 +#define DPRINTF(l, m) \ + do { \ + if (utoppy_debug & l) \ + printf m; \ + } while (/*CONSTCOND*/0) +static int utoppy_debug = 0; +static void utoppy_dump_packet(const void *, size_t); +#define DDUMP_PACKET(p, l) \ + do { \ + if (utoppy_debug & UTOPPY_DBG_DUMP) \ + utoppy_dump_packet((p), (l)); \ + } while (/*CONSTCOND*/0) +#else +#define DPRINTF(l, m) /* nothing */ +#define DDUMP_PACKET(p, l) /* nothing */ +#endif + + +#define UTOPPY_CONFIG_NO 1 +#define UTOPPY_NUMENDPOINTS 2 + +#define UTOPPY_BSIZE 0xffff +#define UTOPPY_FRAG_SIZE 0x1000 +#define UTOPPY_HEADER_SIZE 8 +#define UTOPPY_SHORT_TIMEOUT (500) /* 0.5 seconds */ +#define UTOPPY_LONG_TIMEOUT (10 * 1000) /* 10 seconds */ + +/* Protocol Commands and Responses */ +#define UTOPPY_RESP_ERROR 0x0001 +#define UTOPPY_CMD_ACK 0x0002 +#define UTOPPY_RESP_SUCCESS UTOPPY_CMD_ACK +#define UTOPPY_CMD_CANCEL 0x0003 +#define UTOPPY_CMD_READY 0x0100 +#define UTOPPY_CMD_RESET 0x0101 +#define UTOPPY_CMD_TURBO 0x0102 +#define UTOPPY_CMD_STATS 0x1000 +#define UTOPPY_RESP_STATS_DATA 0x1001 +#define UTOPPY_CMD_READDIR 0x1002 +#define UTOPPY_RESP_READDIR_DATA 0x1003 +#define UTOPPY_RESP_READDIR_END 0x1004 +#define UTOPPY_CMD_DELETE 0x1005 +#define UTOPPY_CMD_RENAME 0x1006 +#define UTOPPY_CMD_MKDIR 0x1007 +#define UTOPPY_CMD_FILE 0x1008 +#define UTOPPY_FILE_WRITE 0 +#define UTOPPY_FILE_READ 1 +#define UTOPPY_RESP_FILE_HEADER 0x1009 +#define UTOPPY_RESP_FILE_DATA 0x100a +#define UTOPPY_RESP_FILE_END 0x100b + +enum utoppy_state { + UTOPPY_STATE_CLOSED, + UTOPPY_STATE_OPENING, + UTOPPY_STATE_IDLE, + UTOPPY_STATE_READDIR, + UTOPPY_STATE_READFILE, + UTOPPY_STATE_WRITEFILE +}; + +struct utoppy_softc { + USBBASEDEVICE sc_dev; + usbd_device_handle sc_udev; /* device */ + usbd_interface_handle sc_iface; /* interface */ + int sc_dying; + int sc_refcnt; + + enum utoppy_state sc_state; + u_int sc_turbo_mode; + + int sc_out; + usbd_pipe_handle sc_out_pipe; /* bulk out pipe */ + usbd_xfer_handle sc_out_xfer; + void *sc_out_buf; + void *sc_out_data; + uint64_t sc_wr_offset; + uint64_t sc_wr_size; + + int sc_in; + usbd_pipe_handle sc_in_pipe; /* bulk in pipe */ + usbd_xfer_handle sc_in_xfer; + void *sc_in_buf; + void *sc_in_data; + size_t sc_in_len; + u_int sc_in_offset; +}; + +struct utoppy_header { + uint16_t h_len; + uint16_t h_crc; + uint16_t h_cmd2; + uint16_t h_cmd; + uint8_t h_data[0]; +}; +#define UTOPPY_OUT_INIT(sc) \ + do { \ + struct utoppy_header *_h = sc->sc_out_data; \ + _h->h_len = 0; \ + } while (/*CONSTCOND*/0) + +#define UTOPPY_MJD_1970 40587u /* MJD value for Jan 1 00:00:00 1970 */ + +#define UTOPPY_FTYPE_DIR 1 +#define UTOPPY_FTYPE_FILE 2 + +#define UTOPPY_IN_DATA(sc) \ + ((void*)&(((uint8_t*)(sc)->sc_in_data)[(sc)->sc_in_offset+UTOPPY_HEADER_SIZE])) + +dev_type_open(utoppyopen); +dev_type_close(utoppyclose); +dev_type_read(utoppyread); +dev_type_write(utoppywrite); +dev_type_ioctl(utoppyioctl); + +const struct cdevsw utoppy_cdevsw = { + utoppyopen, utoppyclose, utoppyread, utoppywrite, utoppyioctl, + nostop, notty, nopoll, nommap, nokqfilter, +}; + +#define UTOPPYUNIT(n) (minor(n)) + +USB_DECLARE_DRIVER(utoppy); + +USB_MATCH(utoppy) +{ + USB_MATCH_START(utoppy, uaa); + + if (uaa->iface == NULL) + return (UMATCH_NONE); + + if (uaa->vendor == USB_VENDOR_TOPFIELD && + uaa->product == USB_PRODUCT_TOPFIELD_TF5000PVR) + return (UMATCH_VENDOR_PRODUCT); + + return (UMATCH_NONE); +} + +USB_ATTACH(utoppy) +{ + USB_ATTACH_START(utoppy, sc, uaa); + usbd_device_handle dev = uaa->device; + usb_endpoint_descriptor_t *ed; + char *devinfop; + u_int8_t epcount; + int i; + + devinfop = usbd_devinfo_alloc(dev, 0); + USB_ATTACH_SETUP; + printf("%s: %s\n", USBDEVNAME(sc->sc_dev), devinfop); + usbd_devinfo_free(devinfop); + + sc->sc_dying = 0; + sc->sc_refcnt = 0; + sc->sc_udev = dev; + + epcount = 0; + (void) usbd_endpoint_count(uaa->iface, &epcount); + if (epcount != UTOPPY_NUMENDPOINTS) { + printf("%s: Expected %d endpoints, got %d\n", + USBDEVNAME(sc->sc_dev), UTOPPY_NUMENDPOINTS, epcount); + USB_ATTACH_ERROR_RETURN; + } + + sc->sc_in = -1; + sc->sc_out = -1; + + for (i = 0; i < epcount; i++) { + ed = usbd_interface2endpoint_descriptor(uaa->iface, i); + if (ed == NULL) { + printf("%s: couldn't get ep %d\n", + USBDEVNAME(sc->sc_dev), i); + USB_ATTACH_ERROR_RETURN; + } + + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->sc_in = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->sc_out = ed->bEndpointAddress; + } + } + + if (sc->sc_out == -1 || sc->sc_in == -1) { + printf("%s: could not find bulk in/out endpoints\n", + USBDEVNAME(sc->sc_dev)); + sc->sc_dying = 1; + USB_ATTACH_ERROR_RETURN; + } + + sc->sc_iface = uaa->iface; + sc->sc_udev = dev; + + sc->sc_out_xfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_out_xfer == NULL) { + printf("%s: could not allocate bulk out xfer\n", + USBDEVNAME(sc->sc_dev)); + goto fail0; + } + + sc->sc_out_buf = usbd_alloc_buffer(sc->sc_out_xfer, UTOPPY_FRAG_SIZE); + if (sc->sc_out_buf == NULL) { + printf("%s: could not allocate bulk out buffer\n", + USBDEVNAME(sc->sc_dev)); + goto fail1; + } + + sc->sc_in_xfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_in_xfer == NULL) { + printf("%s: could not allocate bulk in xfer\n", + USBDEVNAME(sc->sc_dev)); + goto fail1; + } + + sc->sc_in_buf = usbd_alloc_buffer(sc->sc_in_xfer, UTOPPY_FRAG_SIZE); + if (sc->sc_in_buf == NULL) { + printf("%s: could not allocate bulk in buffer\n", + USBDEVNAME(sc->sc_dev)); + goto fail2; + } + + usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, + USBDEV(sc->sc_dev)); + + USB_ATTACH_SUCCESS_RETURN; + + fail2: usbd_free_xfer(sc->sc_in_xfer); + sc->sc_in_xfer = NULL; + + fail1: usbd_free_xfer(sc->sc_out_xfer); + sc->sc_out_xfer = NULL; + + fail0: sc->sc_dying = 1; + USB_ATTACH_ERROR_RETURN; +} + +int +utoppy_activate(device_ptr_t self, enum devact act) +{ + struct utoppy_softc *sc = (struct utoppy_softc *)self; + + switch (act) { + case DVACT_ACTIVATE: + return (EOPNOTSUPP); + + case DVACT_DEACTIVATE: + sc->sc_dying = 1; + break; + } + return (0); +} + +USB_DETACH(utoppy) +{ + USB_DETACH_START(utoppy, sc); + int maj, mn; + int s; + + sc->sc_dying = 1; + if (sc->sc_out_pipe != NULL) + usbd_abort_pipe(sc->sc_out_pipe); + if (sc->sc_in_pipe != NULL) + usbd_abort_pipe(sc->sc_in_pipe); + + if (sc->sc_in_xfer != NULL) + usbd_free_xfer(sc->sc_in_xfer); + if (sc->sc_out_xfer != NULL) + usbd_free_xfer(sc->sc_out_xfer); + + s = splusb(); + if (--sc->sc_refcnt >= 0) + usb_detach_wait(USBDEV(sc->sc_dev)); + splx(s); + + /* locate the major number */ + maj = cdevsw_lookup_major(&utoppy_cdevsw); + + /* Nuke the vnodes for any open instances (calls close). */ + mn = self->dv_unit; + vdevgone(maj, mn, mn, VCHR); + + usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, + USBDEV(sc->sc_dev)); + + return (0); +} + +static const uint16_t utoppy_crc16_lookup[] = { + 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, + 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, + 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, + 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, + 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, + 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, + 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, + 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, + 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240, + 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, + 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41, + 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, + 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, + 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, + 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640, + 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, + 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240, + 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, + 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, + 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, + 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, + 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, + 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, + 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, + 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, + 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440, + 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, + 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841, + 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, + 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41, + 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, + 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040 +}; + +#define UTOPPY_CRC16(ccrc,b) \ + (utoppy_crc16_lookup[((ccrc) ^ (b)) & 0xffu] ^ ((ccrc) >> 8)) + +static const int utoppy_usbdstatus_lookup[] = { + 0, /* USBD_NORMAL_COMPLETION */ + EINPROGRESS, /* USBD_IN_PROGRESS */ + EALREADY, /* USBD_PENDING_REQUESTS */ + EAGAIN, /* USBD_NOT_STARTED */ + EINVAL, /* USBD_INVAL */ + ENOMEM, /* USBD_NOMEM */ + ECONNRESET, /* USBD_CANCELLED */ + EFAULT, /* USBD_BAD_ADDRESS */ + EBUSY, /* USBD_IN_USE */ + EADDRNOTAVAIL, /* USBD_NO_ADDR */ + ENETDOWN, /* USBD_SET_ADDR_FAILED */ + EIO, /* USBD_NO_POWER */ + EMLINK, /* USBD_TOO_DEEP */ + EIO, /* USBD_IOERROR */ + ENXIO, /* USBD_NOT_CONFIGURED */ + ETIMEDOUT, /* USBD_TIMEOUT */ + EBADMSG, /* USBD_SHORT_XFER */ + EHOSTDOWN, /* USBD_STALLED */ + EINTR /* USBD_INTERRUPTED */ +}; + +static __inline int +utoppy_usbd_status2errno(usbd_status err) +{ + + if (err >= USBD_ERROR_MAX) + return (EFAULT); + return (utoppy_usbdstatus_lookup[err]); +} + +#ifdef UTOPPY_DEBUG +static const char * +utoppy_state_string(enum utoppy_state state) +{ + const char *str; + + switch (state) { + case UTOPPY_STATE_CLOSED: + str = "CLOSED"; + break; + case UTOPPY_STATE_OPENING: + str = "OPENING"; + break; + case UTOPPY_STATE_IDLE: + str = "IDLE"; + break; + case UTOPPY_STATE_READDIR: + str = "READ DIRECTORY"; + break; + case UTOPPY_STATE_READFILE: + str = "READ FILE"; + break; + case UTOPPY_STATE_WRITEFILE: + str = "WRITE FILE"; + break; + default: + str = "INVALID!"; + break; + } + + return (str); +} + +static void +utoppy_dump_packet(const void *b, size_t len) +{ + const uint8_t *buf = b, *l; + uint8_t c; + size_t i, j; + + if (len == 0) + return; + + len = min(len, 256); + + printf("00: "); + + for (i = 0, l = buf; i < len; i++) { + printf("%02x ", *buf++); + + if ((i % 16) == 15) { + for (j = 0; j < 16; j++) { + c = *l++; + if (c < ' ' || c > 0x7e) + c = '.'; + printf("%c", c); + } + + printf("\n"); + l = buf; + + if ((i + 1) < len) + printf("%02x: ", (u_int)i + 1); + } + } + + while ((i++ % 16) != 0) + printf(" "); + + if (l < buf) { + while (l < buf) { + c = *l++; + if (c < ' ' || c > 0x7e) + c = '.'; + printf("%c", c); + } + + printf("\n"); + } +} +#endif + +/* + * Very much like usbd_bulk_transfer(), except don't catch signals + */ +static void +utoppy_bulk_transfer_cb(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status status) +{ + + wakeup(xfer); +} + +static usbd_status +utoppy_bulk_transfer(usbd_xfer_handle xfer, usbd_pipe_handle pipe, + u_int16_t flags, u_int32_t timeout, void *buf, u_int32_t *size, + const char *lbl) +{ + usbd_status err; + int s, error; + + usbd_setup_xfer(xfer, pipe, 0, buf, *size, flags, timeout, + utoppy_bulk_transfer_cb); + s = splusb(); + err = usbd_transfer(xfer); + if (err != USBD_IN_PROGRESS) { + splx(s); + return (err); + } + error = tsleep((caddr_t)xfer, PZERO, lbl, 0); + splx(s); + if (error) { + usbd_abort_pipe(pipe); + return (USBD_INTERRUPTED); + } + usbd_get_xfer_status(xfer, NULL, NULL, size, &err); + return (err); +} + +static int +utoppy_send_packet(struct utoppy_softc *sc, uint16_t cmd, uint32_t timeout) +{ + struct utoppy_header *h; + usbd_status err; + uint32_t len; + uint16_t dlen, crc; + uint8_t *data, *e, t1, t2; + + h = sc->sc_out_data; + + DPRINTF(UTOPPY_DBG_SEND_PACKET, ("%s: utoppy_send_packet: cmd 0x%04x, " + "len %d\n", USBDEVNAME(sc->sc_dev), (u_int)cmd, h->h_len)); + + dlen = h->h_len; + len = dlen + UTOPPY_HEADER_SIZE; + + if (len & 1) + len++; + if ((len % 64) == 0) + len += 2; + + if (len >= UTOPPY_BSIZE) { + DPRINTF(UTOPPY_DBG_SEND_PACKET, ("%s: utoppy_send_packet: " + "packet too big (%d)\n", USBDEVNAME(sc->sc_dev), (int)len)); + return (EINVAL); + } + + h->h_len = htole16(dlen + UTOPPY_HEADER_SIZE); + h->h_cmd2 = 0; + h->h_cmd = htole16(cmd); + + /* The command word is part of the CRC */ + crc = UTOPPY_CRC16(0, 0); + crc = UTOPPY_CRC16(crc, 0); + crc = UTOPPY_CRC16(crc, cmd >> 8); + crc = UTOPPY_CRC16(crc, cmd); + + /* + * If there is data following the header, calculate the CRC and + * byte-swap as we go. + */ + if (dlen) { + data = h->h_data; + e = data + (dlen & ~1); + + do { + t1 = data[0]; + t2 = data[1]; + crc = UTOPPY_CRC16(crc, t1); + crc = UTOPPY_CRC16(crc, t2); + *data++ = t2; + *data++ = t1; + } while (data < e); + + if (dlen & 1) { + t1 = data[0]; + crc = UTOPPY_CRC16(crc, t1); + data[1] = t1; + } + } + + h->h_crc = htole16(crc); + data = sc->sc_out_data; + + DPRINTF(UTOPPY_DBG_SEND_PACKET, ("%s: utoppy_send_packet: total len " + "%d...\n", USBDEVNAME(sc->sc_dev), (int)len)); + DDUMP_PACKET(data, len); + + do { + uint32_t thislen; + + thislen = min(len, UTOPPY_FRAG_SIZE); + + memcpy(sc->sc_out_buf, data, thislen); + + err = utoppy_bulk_transfer(sc->sc_out_xfer, sc->sc_out_pipe, + USBD_NO_COPY, timeout, sc->sc_out_buf, &thislen, + "utoppytx"); + + if (thislen != min(len, UTOPPY_FRAG_SIZE)) { + DPRINTF(UTOPPY_DBG_SEND_PACKET, ("%s: " + "utoppy_send_packet: sent %ld, err %d\n", + USBDEVNAME(sc->sc_dev), (u_long)thislen, err)); + } + + if (err == 0) { + len -= thislen; + data += thislen; + } + } while (err == 0 && len); + + DPRINTF(UTOPPY_DBG_SEND_PACKET, ("%s: utoppy_send_packet: " + "usbd_bulk_transfer() returned %d.\n", USBDEVNAME(sc->sc_dev),err)); + + return (err ? utoppy_usbd_status2errno(err) : 0); +} + +static int +utoppy_recv_packet(struct utoppy_softc *sc, uint16_t *respp, uint32_t timeout) +{ + struct utoppy_header *h; + usbd_status err; + uint32_t len, thislen, requested, bytesleft; + uint16_t crc; + uint8_t *data, *e, t1, t2; + + data = sc->sc_in_data; + len = 0; + bytesleft = UTOPPY_BSIZE; + + DPRINTF(UTOPPY_DBG_RECV_PACKET, ("%s: utoppy_recv_packet: ...\n", + USBDEVNAME(sc->sc_dev))); + + do { + requested = thislen = min(bytesleft, UTOPPY_FRAG_SIZE); + + err = utoppy_bulk_transfer(sc->sc_in_xfer, sc->sc_in_pipe, + USBD_NO_COPY | USBD_SHORT_XFER_OK, timeout, sc->sc_in_buf, + &thislen, "utoppyrx"); + + DPRINTF(UTOPPY_DBG_RECV_PACKET, ("%s: utoppy_recv_packet: " + "usbd_bulk_transfer() returned %d, thislen %d, data %p\n", + USBDEVNAME(sc->sc_dev), err, (u_int)thislen, data)); + + if (err == 0) { + memcpy(data, sc->sc_in_buf, thislen); + DDUMP_PACKET(data, thislen); + len += thislen; + bytesleft -= thislen; + data += thislen; + } + } while (err == 0 && bytesleft && thislen == requested); + + if (err) + return (utoppy_usbd_status2errno(err)); + + h = sc->sc_in_data; + + DPRINTF(UTOPPY_DBG_RECV_PACKET, ("%s: utoppy_recv_packet: received %d " + "bytes in total to %p\n", USBDEVNAME(sc->sc_dev), (u_int)len, h)); + DDUMP_PACKET(h, len); + + if (len < UTOPPY_HEADER_SIZE || len < (uint32_t)le16toh(h->h_len)) { + DPRINTF(UTOPPY_DBG_RECV_PACKET, ("%s: utoppy_recv_packet: bad " + " length (len %d, h_len %d)\n", USBDEVNAME(sc->sc_dev), + (int)len, le16toh(h->h_len))); + return (EIO); + } + + len = h->h_len = le16toh(h->h_len); + h->h_crc = le16toh(h->h_crc); + *respp = h->h_cmd = le16toh(h->h_cmd); + h->h_cmd2 = le16toh(h->h_cmd2); + + /* + * To maximise data throughput when transferring files, acknowledge + * data blocks as soon as we receive them. If we detect an error + * later on, we can always cancel. + */ + if (*respp == UTOPPY_RESP_FILE_DATA) { + DPRINTF(UTOPPY_DBG_RECV_PACKET, ("%s: utoppy_recv_packet: " + "ACKing file data\n", USBDEVNAME(sc->sc_dev))); + + UTOPPY_OUT_INIT(sc); + err = utoppy_send_packet(sc, UTOPPY_CMD_ACK, + UTOPPY_SHORT_TIMEOUT); + if (err) { + DPRINTF(UTOPPY_DBG_RECV_PACKET, ("%s: " + "utoppy_recv_packet: failed to ACK file data: %d\n", + USBDEVNAME(sc->sc_dev), err)); + return (err); + } + } + + /* The command word is part of the CRC */ + crc = UTOPPY_CRC16(crc, h->h_cmd2 >> 8); + crc = UTOPPY_CRC16(0, h->h_cmd2); + crc = UTOPPY_CRC16(crc, h->h_cmd >> 8); + crc = UTOPPY_CRC16(crc, h->h_cmd); + + /* + * Extract any payload, byte-swapping and calculating the CRC16 + * as we go. + */ + if (len > UTOPPY_HEADER_SIZE) { + data = h->h_data; + e = data + ((len & ~1) - UTOPPY_HEADER_SIZE); + + while (data < e) { + t1 = data[0]; + t2 = data[1]; + crc = UTOPPY_CRC16(crc, t2); + crc = UTOPPY_CRC16(crc, t1); + *data++ = t2; + *data++ = t1; + } + + if (len & 1) { + t1 = data[1]; + crc = UTOPPY_CRC16(crc, t1); + *data = t1; + } + } + + sc->sc_in_len = (size_t) len - UTOPPY_HEADER_SIZE; + sc->sc_in_offset = 0; + + DPRINTF(UTOPPY_DBG_RECV_PACKET, ("%s: utoppy_recv_packet: len %d, " + "crc 0x%04x, hdrcrc 0x%04x\n", USBDEVNAME(sc->sc_dev), + (int)len, crc, h->h_crc)); + DDUMP_PACKET(h, len); + + return ((crc == h->h_crc) ? 0 : EBADMSG); +} + +static __inline void * +utoppy_current_ptr(void *b) +{ + struct utoppy_header *h = b; + + return (&h->h_data[h->h_len]); +} + +static __inline void +utoppy_advance_ptr(void *b, size_t len) +{ + struct utoppy_header *h = b; + + h->h_len += len; +} + +static __inline void +utoppy_add_8(struct utoppy_softc *sc, uint8_t v) +{ + struct utoppy_header *h = sc->sc_out_data; + uint8_t *p; + + p = utoppy_current_ptr(h); + *p = v; + utoppy_advance_ptr(h, sizeof(v)); +} + +static __inline void +utoppy_add_16(struct utoppy_softc *sc, uint16_t v) +{ + struct utoppy_header *h = sc->sc_out_data; + uint8_t *p; + + p = utoppy_current_ptr(h); + *p++ = (uint8_t)(v >> 8); + *p = (uint8_t)v; + utoppy_advance_ptr(h, sizeof(v)); +} + +static __inline void +utoppy_add_32(struct utoppy_softc *sc, uint32_t v) +{ + struct utoppy_header *h = sc->sc_out_data; + uint8_t *p; + + p = utoppy_current_ptr(h); + *p++ = (uint8_t)(v >> 24); + *p++ = (uint8_t)(v >> 16); + *p++ = (uint8_t)(v >> 8); + *p = (uint8_t)v; + utoppy_advance_ptr(h, sizeof(v)); +} + +static __inline void +utoppy_add_64(struct utoppy_softc *sc, uint64_t v) +{ + struct utoppy_header *h = sc->sc_out_data; + uint8_t *p; + + p = utoppy_current_ptr(h); + *p++ = (uint8_t)(v >> 56); + *p++ = (uint8_t)(v >> 48); + *p++ = (uint8_t)(v >> 40); + *p++ = (uint8_t)(v >> 32); + *p++ = (uint8_t)(v >> 24); + *p++ = (uint8_t)(v >> 16); + *p++ = (uint8_t)(v >> 8); + *p = (uint8_t)v; + utoppy_advance_ptr(h, sizeof(v)); +} + +static __inline void +utoppy_add_string(struct utoppy_softc *sc, const char *str, size_t len) +{ + struct utoppy_header *h = sc->sc_out_data; + char *p; + + p = utoppy_current_ptr(h); + memset(p, 0, len); + strncpy(p, str, len); + utoppy_advance_ptr(h, len); +} + +static int +utoppy_add_path(struct utoppy_softc *sc, const char *path, int putlen) +{ + struct utoppy_header *h = sc->sc_out_data; + uint8_t *p, *str, *s; + size_t len; + int err; + + p = utoppy_current_ptr(h); + + str = putlen ? (p + sizeof(uint16_t)) : p; + + err = copyinstr(path, str, UTOPPY_MAX_FILENAME_LEN, &len); + + DPRINTF(UTOPPY_DBG_ADDPATH, ("utoppy_add_path: err %d, len %d\n", + err, (int)len)); + + if (err) + return (err); + + if (len < 2) + return (EINVAL); + + /* + * copyinstr(9) has already copied the terminating NUL character, + * but we append another one in case we have to pad the length + * later on. + */ + str[len] = '\0'; + + /* + * The Toppy uses backslash as the directory separator, so convert + * all forward slashes. + */ + for (s = &str[len - 2]; s >= str; s--) + if (*s == '/') + *s = '\\'; + + if ((len + h->h_len) & 1) + len++; + + if (putlen) + utoppy_add_16(sc, len); + + utoppy_advance_ptr(h, len); + + DPRINTF(UTOPPY_DBG_ADDPATH, ("utoppy_add_path: final len %d\n", + (u_int)len)); + + return (0); +} + +static __inline int +utoppy_get_8(struct utoppy_softc *sc, uint8_t *vp) +{ + uint8_t *p; + + if (sc->sc_in_len < sizeof(*vp)) + return (1); + + p = UTOPPY_IN_DATA(sc); + *vp = *p; + sc->sc_in_offset += sizeof(*vp); + sc->sc_in_len -= sizeof(*vp); + return (0); +} + +static __inline int +utoppy_get_16(struct utoppy_softc *sc, uint16_t *vp) +{ + uint16_t v; + uint8_t *p; + + if (sc->sc_in_len < sizeof(v)) + return (1); + + p = UTOPPY_IN_DATA(sc); + v = *p++; + v = (v << 8) | *p; + *vp = v; + sc->sc_in_offset += sizeof(v); + sc->sc_in_len -= sizeof(v); + return (0); +} + +static __inline int +utoppy_get_32(struct utoppy_softc *sc, uint32_t *vp) +{ + uint32_t v; + uint8_t *p; + + if (sc->sc_in_len < sizeof(v)) + return (1); + + p = UTOPPY_IN_DATA(sc); + v = *p++; + v = (v << 8) | *p++; + v = (v << 8) | *p++; + v = (v << 8) | *p; + *vp = v; + sc->sc_in_offset += sizeof(v); + sc->sc_in_len -= sizeof(v); + return (0); +} + +static __inline int +utoppy_get_64(struct utoppy_softc *sc, uint64_t *vp) +{ + uint64_t v; + uint8_t *p; + + if (sc->sc_in_len < sizeof(v)) + return (1); + + p = UTOPPY_IN_DATA(sc); + v = *p++; + v = (v << 8) | *p++; + v = (v << 8) | *p++; + v = (v << 8) | *p++; + v = (v << 8) | *p++; + v = (v << 8) | *p++; + v = (v << 8) | *p++; + v = (v << 8) | *p; + *vp = v; + sc->sc_in_offset += sizeof(v); + sc->sc_in_len -= sizeof(v); + return (0); +} + +static __inline int +utoppy_get_string(struct utoppy_softc *sc, char *str, size_t len) +{ + char *p; + + if (sc->sc_in_len < len) + return (1); + + memset(str, 0, len); + p = UTOPPY_IN_DATA(sc); + strncpy(str, p, len); + sc->sc_in_offset += len; + sc->sc_in_len -= len; + return (0); +} + +static int +utoppy_command(struct utoppy_softc *sc, uint16_t cmd, int timeout, + uint16_t *presp) +{ + int err; + + err = utoppy_send_packet(sc, cmd, timeout); + if (err) + return (err); + + err = utoppy_recv_packet(sc, presp, timeout); + if (err == EBADMSG) { + UTOPPY_OUT_INIT(sc); + utoppy_send_packet(sc, UTOPPY_RESP_ERROR, timeout); + } + + return (err); +} + +static int +utoppy_timestamp_decode(struct utoppy_softc *sc, time_t *tp) +{ + uint16_t mjd; + uint8_t hour, minute, sec; + uint32_t rv; + + if (utoppy_get_16(sc, &mjd) || utoppy_get_8(sc, &hour) || + utoppy_get_8(sc, &minute) || utoppy_get_8(sc, &sec)) + return (1); + + if (mjd == 0xffffu && hour == 0xffu && minute == 0xffu && sec == 0xffu){ + *tp = 0; + return (0); + } + + rv = (mjd < UTOPPY_MJD_1970) ? UTOPPY_MJD_1970 : (uint32_t) mjd; + + /* Calculate seconds since 1970 */ + rv = (rv - UTOPPY_MJD_1970) * 60 * 60 * 24; + + /* Add in the hours, minutes, and seconds */ + rv += (uint32_t)hour * 60 * 60; + rv += (uint32_t)minute * 60; + rv += sec; + *tp = (time_t)rv; + + return (0); +} + +static void +utoppy_timestamp_encode(struct utoppy_softc *sc, time_t t) +{ + u_int mjd, hour, minute; + + mjd = t / (60 * 60 * 24); + t -= mjd * 60 * 60 * 24; + + hour = t / (60 * 60); + t -= hour * 60 * 60; + + minute = t / 60; + t -= minute * 60; + + utoppy_add_16(sc, mjd + UTOPPY_MJD_1970); + utoppy_add_8(sc, hour); + utoppy_add_8(sc, minute); + utoppy_add_8(sc, t); +} + +static int +utoppy_turbo_mode(struct utoppy_softc *sc, int state) +{ + uint16_t r; + int err; + + UTOPPY_OUT_INIT(sc); + utoppy_add_32(sc, state); + + err = utoppy_command(sc, UTOPPY_CMD_TURBO, UTOPPY_SHORT_TIMEOUT, &r); + if (err) + return (err); + + return ((r == UTOPPY_RESP_SUCCESS) ? 0 : EIO); +} + +static int +utoppy_check_ready(struct utoppy_softc *sc) +{ + uint16_t r; + int err; + + UTOPPY_OUT_INIT(sc); + + err = utoppy_command(sc, UTOPPY_CMD_READY, UTOPPY_LONG_TIMEOUT, &r); + if (err) + return (err); + + return ((r == UTOPPY_RESP_SUCCESS) ? 0 : EIO); +} + +static int +utoppy_cancel(struct utoppy_softc *sc) +{ + uint16_t r; + int err, i; + + /* + * Issue the cancel command serveral times. the Toppy doesn't + * always respond to the first. + */ + for (i = 0; i < 3; i++) { + UTOPPY_OUT_INIT(sc); + err = utoppy_command(sc, UTOPPY_CMD_CANCEL, + UTOPPY_SHORT_TIMEOUT, &r); + if (err == 0 && r == UTOPPY_RESP_SUCCESS) + break; + err = ETIMEDOUT; + } + + if (err) + return (err); + + /* + * Make sure turbo mode is off, otherwise the Toppy will not + * respond to remote control input. + */ + (void) utoppy_turbo_mode(sc, 0); + + sc->sc_state = UTOPPY_STATE_IDLE; + return (0); +} + +static int +utoppy_stats(struct utoppy_softc *sc, struct utoppy_stats *us) +{ + uint32_t hsize, hfree; + uint16_t r; + int err; + + UTOPPY_OUT_INIT(sc); + err = utoppy_command(sc, UTOPPY_CMD_STATS, UTOPPY_LONG_TIMEOUT, &r); + if (err) + return (err); + + if (r != UTOPPY_RESP_STATS_DATA) + return (EIO); + + if (utoppy_get_32(sc, &hsize) || utoppy_get_32(sc, &hfree)) + return (EIO); + + us->us_hdd_size = hsize; + us->us_hdd_size *= 1024; + us->us_hdd_free = hfree; + us->us_hdd_free *= 1024; + + return (0); +} + +static int +utoppy_readdir_next(struct utoppy_softc *sc) +{ + uint16_t resp; + int err; + + DPRINTF(UTOPPY_DBG_READDIR, ("%s: utoppy_readdir_next: running...\n", + USBDEVNAME(sc->sc_dev))); + + /* + * Fetch the next READDIR response + */ + err = utoppy_recv_packet(sc, &resp, UTOPPY_LONG_TIMEOUT); + if (err) { + DPRINTF(UTOPPY_DBG_READDIR, ("%s: utoppy_readdir_next: " + "utoppy_recv_packet() returned %d\n", + USBDEVNAME(sc->sc_dev), err)); + if (err == EBADMSG) { + UTOPPY_OUT_INIT(sc); + utoppy_send_packet(sc, UTOPPY_RESP_ERROR, + UTOPPY_LONG_TIMEOUT); + } + utoppy_cancel(sc); + return (err); + } + + DPRINTF(UTOPPY_DBG_READDIR, ("%s: utoppy_readdir_next: " + "utoppy_recv_packet() returned %d, len %ld\n", + USBDEVNAME(sc->sc_dev), err, (u_long)sc->sc_in_len)); + + switch (resp) { + case UTOPPY_RESP_READDIR_DATA: + DPRINTF(UTOPPY_DBG_READDIR, ("%s: utoppy_readdir_next: " + "UTOPPY_RESP_READDIR_DATA\n", USBDEVNAME(sc->sc_dev))); + + UTOPPY_OUT_INIT(sc); + err = utoppy_send_packet(sc, UTOPPY_CMD_ACK, + UTOPPY_LONG_TIMEOUT); + if (err) { + DPRINTF(UTOPPY_DBG_READDIR, ("%s: utoppy_readdir_next: " + "utoppy_send_packet(ACK) returned %d\n", + USBDEVNAME(sc->sc_dev), err)); + utoppy_cancel(sc); + return (err); + } + sc->sc_state = UTOPPY_STATE_READDIR; + sc->sc_in_offset = 0; + break; + + case UTOPPY_RESP_READDIR_END: + DPRINTF(UTOPPY_DBG_READDIR, ("%s: utoppy_readdir_next: " + "UTOPPY_RESP_READDIR_END\n", USBDEVNAME(sc->sc_dev))); + + UTOPPY_OUT_INIT(sc); + utoppy_send_packet(sc, UTOPPY_CMD_ACK, UTOPPY_SHORT_TIMEOUT); + sc->sc_state = UTOPPY_STATE_IDLE; + sc->sc_in_len = 0; + break; + + default: + DPRINTF(UTOPPY_DBG_READDIR, ("%s: utoppy_readdir_next: " + "bad response: 0x%x\n", USBDEVNAME(sc->sc_dev), resp)); + sc->sc_state = UTOPPY_STATE_IDLE; + sc->sc_in_len = 0; + return (EIO); + } + + return (0); +} + +static size_t +utoppy_readdir_decode(struct utoppy_softc *sc, struct utoppy_dirent *ud) +{ + uint8_t ftype; + + DPRINTF(UTOPPY_DBG_READDIR, ("%s: utoppy_readdir_decode: bytes left" + " %d\n", USBDEVNAME(sc->sc_dev), (int)sc->sc_in_len)); + + if (utoppy_timestamp_decode(sc, &ud->ud_mtime) || + utoppy_get_8(sc, &ftype) || utoppy_get_64(sc, &ud->ud_size) || + utoppy_get_string(sc, ud->ud_path, UTOPPY_MAX_FILENAME_LEN + 1) || + utoppy_get_32(sc, &ud->ud_attributes)) { + DPRINTF(UTOPPY_DBG_READDIR, ("%s: utoppy_readdir_decode: no " + "more to decode\n", USBDEVNAME(sc->sc_dev))); + return (0); + } + + switch (ftype) { + case UTOPPY_FTYPE_DIR: + ud->ud_type = UTOPPY_DIRENT_DIRECTORY; + break; + case UTOPPY_FTYPE_FILE: + ud->ud_type = UTOPPY_DIRENT_FILE; + break; + default: + ud->ud_type = UTOPPY_DIRENT_UNKNOWN; + break; + } + + DPRINTF(UTOPPY_DBG_READDIR, ("%s: utoppy_readdir_decode: %s '%s', " + "size %lld, time 0x%08lx, attr 0x%08x\n", USBDEVNAME(sc->sc_dev), + (ftype == UTOPPY_FTYPE_DIR) ? "DIR" : + ((ftype == UTOPPY_FTYPE_FILE) ? "FILE" : "UNKNOWN"), ud->ud_path, + ud->ud_size, (u_long)ud->ud_mtime, ud->ud_attributes)); + + return (1); +} + +static int +utoppy_readfile_next(struct utoppy_softc *sc) +{ + uint64_t off; + uint16_t resp; + int err; + + err = utoppy_recv_packet(sc, &resp, UTOPPY_LONG_TIMEOUT); + if (err) { + DPRINTF(UTOPPY_DBG_READ, ("%s: utoppy_readfile_next: " + "utoppy_recv_packet() returned %d\n", + USBDEVNAME(sc->sc_dev), err)); + utoppy_cancel(sc); + return (err); + } + + switch (resp) { + case UTOPPY_RESP_FILE_HEADER: + /* ACK it */ + UTOPPY_OUT_INIT(sc); + err = utoppy_send_packet(sc, UTOPPY_CMD_ACK, + UTOPPY_LONG_TIMEOUT); + if (err) { + DPRINTF(UTOPPY_DBG_READ, ("%s: utoppy_readfile_next: " + "utoppy_send_packet(UTOPPY_CMD_ACK) returned %d\n", + USBDEVNAME(sc->sc_dev), err)); + utoppy_cancel(sc); + return (err); + } + + sc->sc_in_len = 0; + DPRINTF(UTOPPY_DBG_READ, ("%s: utoppy_readfile_next: " + "FILE_HEADER done\n", USBDEVNAME(sc->sc_dev))); + break; + + case UTOPPY_RESP_FILE_DATA: + /* Already ACK'd */ + if (utoppy_get_64(sc, &off)) { + DPRINTF(UTOPPY_DBG_READ, ("%s: utoppy_readfile_next: " + "UTOPPY_RESP_FILE_DATA did not provide offset\n", + USBDEVNAME(sc->sc_dev))); + utoppy_cancel(sc); + return (EBADMSG); + } + + DPRINTF(UTOPPY_DBG_READ, ("%s: utoppy_readfile_next: " + "UTOPPY_RESP_FILE_DATA: offset %lld, bytes left %ld\n", + USBDEVNAME(sc->sc_dev), off, (u_long)sc->sc_in_len)); + break; + + case UTOPPY_RESP_FILE_END: + DPRINTF(UTOPPY_DBG_READ, ("%s: utoppy_readfile_next: " + "UTOPPY_RESP_FILE_END: sending ACK\n", + USBDEVNAME(sc->sc_dev))); + UTOPPY_OUT_INIT(sc); + utoppy_send_packet(sc, UTOPPY_CMD_ACK, UTOPPY_SHORT_TIMEOUT); + /*FALLTHROUGH*/ + + case UTOPPY_RESP_SUCCESS: + sc->sc_state = UTOPPY_STATE_IDLE; + (void) utoppy_turbo_mode(sc, 0); + DPRINTF(UTOPPY_DBG_READ, ("%s: utoppy_readfile_next: all " + "done\n", USBDEVNAME(sc->sc_dev))); + break; + + case UTOPPY_RESP_ERROR: + default: + DPRINTF(UTOPPY_DBG_READ, ("%s: utoppy_readfile_next: bad " + "response code 0x%0x\n", USBDEVNAME(sc->sc_dev), resp)); + utoppy_cancel(sc); + return (EIO); + } + + return (0); +} + +int +utoppyopen(dev_t dev, int flag, int mode, struct lwp *l) +{ + struct utoppy_softc *sc; + int error = 0; + + USB_GET_SC_OPEN(utoppy, UTOPPYUNIT(dev), sc); + + if (sc == NULL || sc->sc_iface == NULL || sc->sc_dying) + return (ENXIO); + + if (sc->sc_state != UTOPPY_STATE_CLOSED) { + DPRINTF(UTOPPY_DBG_OPEN, ("%s: utoppyopen: already open\n", + USBDEVNAME(sc->sc_dev))); + return (EBUSY); + } + + DPRINTF(UTOPPY_DBG_OPEN, ("%s: utoppyopen: opening...\n", + USBDEVNAME(sc->sc_dev))); + + sc->sc_refcnt++; + sc->sc_state = UTOPPY_STATE_OPENING; + sc->sc_turbo_mode = 0; + sc->sc_out_pipe = NULL; + sc->sc_in_pipe = NULL; + + if (usbd_open_pipe(sc->sc_iface, sc->sc_out, 0, &sc->sc_out_pipe)) { + DPRINTF(UTOPPY_DBG_OPEN, ("%s: utoppyopen: usbd_open_pipe(OUT) " + "failed\n", USBDEVNAME(sc->sc_dev))); + error = EIO; + goto done; + } + + if (usbd_open_pipe(sc->sc_iface, sc->sc_in, 0, &sc->sc_in_pipe)) { + DPRINTF(UTOPPY_DBG_OPEN, ("%s: utoppyopen: usbd_open_pipe(IN) " + "failed\n", USBDEVNAME(sc->sc_dev))); + error = EIO; + usbd_close_pipe(sc->sc_out_pipe); + sc->sc_out_pipe = NULL; + } + + sc->sc_out_data = malloc(UTOPPY_BSIZE + 1, M_DEVBUF, M_WAITOK); + if (sc->sc_out_data == NULL) { + error = ENOMEM; + goto error; + } + + sc->sc_in_data = malloc(UTOPPY_BSIZE + 1, M_DEVBUF, M_WAITOK); + if (sc->sc_in_data == NULL) { + free(sc->sc_out_data, M_DEVBUF); + sc->sc_out_data = NULL; + error = ENOMEM; + goto error; + } + + if ((error = utoppy_cancel(sc)) != 0) + goto error; + + if ((error = utoppy_check_ready(sc)) != 0) { + DPRINTF(UTOPPY_DBG_OPEN, ("%s: utoppyopen: utoppy_check_ready()" + " returned %d\n", USBDEVNAME(sc->sc_dev), error)); + error: + usbd_abort_pipe(sc->sc_out_pipe); + usbd_close_pipe(sc->sc_out_pipe); + sc->sc_out_pipe = NULL; + usbd_abort_pipe(sc->sc_in_pipe); + usbd_close_pipe(sc->sc_in_pipe); + sc->sc_in_pipe = NULL; + } + + done: + sc->sc_state = error ? UTOPPY_STATE_CLOSED : UTOPPY_STATE_IDLE; + + DPRINTF(UTOPPY_DBG_OPEN, ("%s: utoppyopen: done. error %d, new state " + "'%s'\n", USBDEVNAME(sc->sc_dev), error, + utoppy_state_string(sc->sc_state))); + + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(USBDEV(sc->sc_dev)); + + return (error); +} + +int +utoppyclose(dev_t dev, int flag, int mode, struct lwp *l) +{ + struct utoppy_softc *sc; + usbd_status err; + + USB_GET_SC(utoppy, UTOPPYUNIT(dev), sc); + + DPRINTF(UTOPPY_DBG_CLOSE, ("%s: utoppyclose: closing...\n", + USBDEVNAME(sc->sc_dev))); + + if (sc->sc_state < UTOPPY_STATE_IDLE) { + /* We are being forced to close before the open completed. */ + DPRINTF(UTOPPY_DBG_CLOSE, ("%s: utoppyclose: not properly open:" + " %s\n", USBDEVNAME(sc->sc_dev), + utoppy_state_string(sc->sc_state))); + return (0); + } + + (void) utoppy_cancel(sc); + + if (sc->sc_out_pipe != NULL) { + if ((err = usbd_abort_pipe(sc->sc_out_pipe)) != 0) + printf("usbd_abort_pipe(OUT) returned %d\n", err); + if ((err = usbd_close_pipe(sc->sc_out_pipe)) != 0) + printf("usbd_close_pipe(OUT) returned %d\n", err); + sc->sc_out_pipe = NULL; + } + + if (sc->sc_in_pipe != NULL) { + if ((err = usbd_abort_pipe(sc->sc_in_pipe)) != 0) + printf("usbd_abort_pipe(IN) returned %d\n", err); + if ((err = usbd_close_pipe(sc->sc_in_pipe)) != 0) + printf("usbd_close_pipe(IN) returned %d\n", err); + sc->sc_in_pipe = NULL; + } + + if (sc->sc_out_data) { + free(sc->sc_out_data, M_DEVBUF); + sc->sc_out_data = NULL; + } + + if (sc->sc_in_data) { + free(sc->sc_in_data, M_DEVBUF); + sc->sc_in_data = NULL; + } + + sc->sc_state = UTOPPY_STATE_CLOSED; + + DPRINTF(UTOPPY_DBG_CLOSE, ("%s: utoppyclose: done.\n", + USBDEVNAME(sc->sc_dev))); + + return (0); +} + +int +utoppyread(dev_t dev, struct uio *uio, int flags) +{ + struct utoppy_softc *sc; + struct utoppy_dirent ud; + size_t len; + int err; + + USB_GET_SC(utoppy, UTOPPYUNIT(dev), sc); + + if (sc->sc_dying) + return (EIO); + + sc->sc_refcnt++; + + DPRINTF(UTOPPY_DBG_READ, ("%s: utoppyread: reading: state '%s'\n", + USBDEVNAME(sc->sc_dev), utoppy_state_string(sc->sc_state))); + + switch (sc->sc_state) { + case UTOPPY_STATE_READDIR: + err = 0; + while (err == 0 && uio->uio_resid >= sizeof(ud) && + sc->sc_state != UTOPPY_STATE_IDLE) { + if (utoppy_readdir_decode(sc, &ud) == 0) + err = utoppy_readdir_next(sc); + else + if ((err = uiomove(&ud, sizeof(ud), uio)) != 0) + utoppy_cancel(sc); + } + break; + + case UTOPPY_STATE_READFILE: + err = 0; + while (err == 0 && uio->uio_resid > 0 && + sc->sc_state != UTOPPY_STATE_IDLE) { + DPRINTF(UTOPPY_DBG_READ, ("%s: utoppyread: READFILE: " + "resid %ld, bytes_left %ld\n", + USBDEVNAME(sc->sc_dev), (u_long)uio->uio_resid, + (u_long)sc->sc_in_len)); + + if (sc->sc_in_len == 0 && + (err = utoppy_readfile_next(sc)) != 0) { + DPRINTF(UTOPPY_DBG_READ, ("%s: utoppyread: " + "READFILE: utoppy_readfile_next returned " + "%d\n", USBDEVNAME(sc->sc_dev), err)); + break; + } + + len = min(uio->uio_resid, sc->sc_in_len); + if (len) { + err = uiomove(UTOPPY_IN_DATA(sc), len, uio); + if (err == 0) { + sc->sc_in_offset += len; + sc->sc_in_len -= len; + } + } + } + break; + + case UTOPPY_STATE_IDLE: + err = 0; + break; + + case UTOPPY_STATE_WRITEFILE: + err = EBUSY; + break; + + default: + err = EIO; + break; + } + + DPRINTF(UTOPPY_DBG_READ, ("%s: utoppyread: done. err %d, state '%s'\n", + USBDEVNAME(sc->sc_dev), err, utoppy_state_string(sc->sc_state))); + + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(USBDEV(sc->sc_dev)); + + return (err); +} + +int +utoppywrite(dev_t dev, struct uio *uio, int flags) +{ + struct utoppy_softc *sc; + uint16_t resp; + size_t len; + int err; + + USB_GET_SC(utoppy, UTOPPYUNIT(dev), sc); + + if (sc->sc_dying) + return (EIO); + + switch(sc->sc_state) { + case UTOPPY_STATE_WRITEFILE: + break; + + case UTOPPY_STATE_IDLE: + return (0); + + default: + return (EIO); + } + + sc->sc_refcnt++; + err = 0; + + DPRINTF(UTOPPY_DBG_WRITE, ("%s: utoppywrite: PRE-WRITEFILE: resid %ld, " + "wr_size %lld, wr_offset %lld\n", USBDEVNAME(sc->sc_dev), + (u_long)uio->uio_resid, sc->sc_wr_size, sc->sc_wr_offset)); + + while (sc->sc_state == UTOPPY_STATE_WRITEFILE && + (len = min(uio->uio_resid, sc->sc_wr_size)) != 0) { + + len = min(len, UTOPPY_BSIZE - (UTOPPY_HEADER_SIZE + + sizeof(uint64_t) + 3)); + + DPRINTF(UTOPPY_DBG_WRITE, ("%s: utoppywrite: uiomove(%ld)\n", + USBDEVNAME(sc->sc_dev), (u_long)len)); + + UTOPPY_OUT_INIT(sc); + utoppy_add_64(sc, sc->sc_wr_offset); + + err = uiomove(utoppy_current_ptr(sc->sc_out_data), len, uio); + if (err) { + DPRINTF(UTOPPY_DBG_WRITE, ("%s: utoppywrite: uiomove() " + "returned %d\n", USBDEVNAME(sc->sc_dev), err)); + break; + } + + utoppy_advance_ptr(sc->sc_out_data, len); + + err = utoppy_command(sc, UTOPPY_RESP_FILE_DATA, + UTOPPY_LONG_TIMEOUT, &resp); + if (err) { + DPRINTF(UTOPPY_DBG_WRITE, ("%s: utoppywrite: " + "utoppy_command(UTOPPY_RESP_FILE_DATA) " + "returned %d\n", USBDEVNAME(sc->sc_dev), err)); + break; + } + if (resp != UTOPPY_RESP_SUCCESS) { + DPRINTF(UTOPPY_DBG_WRITE, ("%s: utoppywrite: " + "utoppy_command(UTOPPY_RESP_FILE_DATA) returned " + "bad response 0x%x\n", USBDEVNAME(sc->sc_dev), + resp)); + utoppy_cancel(sc); + err = EIO; + break; + } + + sc->sc_wr_offset += len; + sc->sc_wr_size -= len; + } + + DPRINTF(UTOPPY_DBG_WRITE, ("%s: utoppywrite: POST-WRITEFILE: resid %ld," + " wr_size %lld, wr_offset %lld, err %d\n", USBDEVNAME(sc->sc_dev), + (u_long)uio->uio_resid, sc->sc_wr_size, sc->sc_wr_offset, err)); + + if (err == 0 && sc->sc_wr_size == 0) { + DPRINTF(UTOPPY_DBG_WRITE, ("%s: utoppywrite: sending " + "FILE_END...\n", USBDEVNAME(sc->sc_dev))); + UTOPPY_OUT_INIT(sc); + err = utoppy_command(sc, UTOPPY_RESP_FILE_END, + UTOPPY_LONG_TIMEOUT, &resp); + if (err) { + DPRINTF(UTOPPY_DBG_WRITE, ("%s: utoppywrite: " + "utoppy_command(UTOPPY_RESP_FILE_END) returned " + "%d\n", USBDEVNAME(sc->sc_dev), err)); + + utoppy_cancel(sc); + } + + sc->sc_state = UTOPPY_STATE_IDLE; + DPRINTF(UTOPPY_DBG_WRITE, ("%s: utoppywrite: state %s\n", + USBDEVNAME(sc->sc_dev), utoppy_state_string(sc->sc_state))); + } + + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(USBDEV(sc->sc_dev)); + + return (err); +} + +int +utoppyioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct lwp *l) +{ + struct utoppy_softc *sc; + struct utoppy_rename *ur; + struct utoppy_readfile *urf; + struct utoppy_writefile *uw; + char uwf[UTOPPY_MAX_FILENAME_LEN + 1], *uwfp; + uint16_t resp; + int err; + + USB_GET_SC(utoppy, UTOPPYUNIT(dev), sc); + + if (sc->sc_dying) + return (EIO); + + DPRINTF(UTOPPY_DBG_IOCTL, ("%s: utoppyioctl: cmd 0x%08lx, state '%s'\n", + USBDEVNAME(sc->sc_dev), cmd, utoppy_state_string(sc->sc_state))); + + if (sc->sc_state != UTOPPY_STATE_IDLE && cmd != UTOPPYIOCANCEL) { + DPRINTF(UTOPPY_DBG_IOCTL, ("%s: utoppyioctl: still busy.\n", + USBDEVNAME(sc->sc_dev))); + return (EBUSY); + } + + sc->sc_refcnt++; + + switch (cmd) { + case UTOPPYIOTURBO: + err = 0; + sc->sc_turbo_mode = *((int *)data) ? 1 : 0; + DPRINTF(UTOPPY_DBG_IOCTL, ("%s: utoppyioctl: UTOPPYIOTURBO: " + "%s\n", USBDEVNAME(sc->sc_dev), sc->sc_turbo_mode ? "On" : + "Off")); + break; + + case UTOPPYIOCANCEL: + DPRINTF(UTOPPY_DBG_IOCTL, ("%s: utoppyioctl: UTOPPYIOCANCEL\n", + USBDEVNAME(sc->sc_dev))); + err = utoppy_cancel(sc); + break; + + case UTOPPYIOREBOOT: + DPRINTF(UTOPPY_DBG_IOCTL, ("%s: utoppyioctl: UTOPPYIOREBOOT\n", + USBDEVNAME(sc->sc_dev))); + UTOPPY_OUT_INIT(sc); + err = utoppy_command(sc, UTOPPY_CMD_RESET, UTOPPY_LONG_TIMEOUT, + &resp); + if (err) + break; + + if (resp != UTOPPY_RESP_SUCCESS) + err = EIO; + break; + + case UTOPPYIOSTATS: + DPRINTF(UTOPPY_DBG_IOCTL, ("%s: utoppyioctl: UTOPPYIOSTATS\n", + USBDEVNAME(sc->sc_dev))); + err = utoppy_stats(sc, (struct utoppy_stats *)data); + break; + + case UTOPPYIORENAME: + DPRINTF(UTOPPY_DBG_IOCTL, ("%s: utoppyioctl: UTOPPYIORENAME\n", + USBDEVNAME(sc->sc_dev))); + ur = (struct utoppy_rename *)data; + UTOPPY_OUT_INIT(sc); + + if ((err = utoppy_add_path(sc, ur->ur_old_path, 1)) != 0) + break; + if ((err = utoppy_add_path(sc, ur->ur_new_path, 1)) != 0) + break; + + err = utoppy_command(sc, UTOPPY_CMD_RENAME, UTOPPY_LONG_TIMEOUT, + &resp); + if (err) + break; + + if (resp != UTOPPY_RESP_SUCCESS) + err = EIO; + break; + + case UTOPPYIOMKDIR: + DPRINTF(UTOPPY_DBG_IOCTL, ("%s: utoppyioctl: UTOPPYIOMKDIR\n", + USBDEVNAME(sc->sc_dev))); + UTOPPY_OUT_INIT(sc); + err = utoppy_add_path(sc, *((const char **)data), 1); + if (err) + break; + + err = utoppy_command(sc, UTOPPY_CMD_MKDIR, UTOPPY_LONG_TIMEOUT, + &resp); + if (err) + break; + + if (resp != UTOPPY_RESP_SUCCESS) + err = EIO; + break; + + case UTOPPYIODELETE: + DPRINTF(UTOPPY_DBG_IOCTL, ("%s: utoppyioctl: UTOPPYIODELETE\n", + USBDEVNAME(sc->sc_dev))); + UTOPPY_OUT_INIT(sc); + err = utoppy_add_path(sc, *((const char **)data), 0); + if (err) + break; + + err = utoppy_command(sc, UTOPPY_CMD_DELETE, UTOPPY_LONG_TIMEOUT, + &resp); + if (err) + break; + + if (resp != UTOPPY_RESP_SUCCESS) + err = EIO; + break; + + case UTOPPYIOREADDIR: + DPRINTF(UTOPPY_DBG_IOCTL, ("%s: utoppyioctl: UTOPPYIOREADDIR\n", + USBDEVNAME(sc->sc_dev))); + UTOPPY_OUT_INIT(sc); + err = utoppy_add_path(sc, *((const char **)data), 0); + if (err) { + DPRINTF(UTOPPY_DBG_READDIR, ("%s: utoppyioctl: " + "utoppy_add_path() returned %d\n", + USBDEVNAME(sc->sc_dev), err)); + break; + } + + err = utoppy_send_packet(sc, UTOPPY_CMD_READDIR, + UTOPPY_LONG_TIMEOUT); + if (err != 0) { + DPRINTF(UTOPPY_DBG_READDIR, ("%s: utoppyioctl: " + "UTOPPY_CMD_READDIR returned %d\n", + USBDEVNAME(sc->sc_dev), err)); + break; + } + + err = utoppy_readdir_next(sc); + if (err) { + DPRINTF(UTOPPY_DBG_READDIR, ("%s: utoppyioctl: " + "utoppy_readdir_next() returned %d\n", + USBDEVNAME(sc->sc_dev), err)); + } + break; + + case UTOPPYIOREADFILE: + urf = (struct utoppy_readfile *)data; + + DPRINTF(UTOPPY_DBG_IOCTL,("%s: utoppyioctl: UTOPPYIOREADFILE " + "%s, offset %lld\n", USBDEVNAME(sc->sc_dev), urf->ur_path, + urf->ur_offset)); + + if ((err = utoppy_turbo_mode(sc, sc->sc_turbo_mode)) != 0) + break; + + UTOPPY_OUT_INIT(sc); + utoppy_add_8(sc, UTOPPY_FILE_READ); + + if ((err = utoppy_add_path(sc, urf->ur_path, 1)) != 0) + break; + + utoppy_add_64(sc, urf->ur_offset); + + sc->sc_state = UTOPPY_STATE_READFILE; + sc->sc_in_offset = 0; + + err = utoppy_send_packet(sc, UTOPPY_CMD_FILE, + UTOPPY_LONG_TIMEOUT); + if (err == 0) + err = utoppy_readfile_next(sc); + break; + + case UTOPPYIOWRITEFILE: + uw = (struct utoppy_writefile *)data; + + DPRINTF(UTOPPY_DBG_IOCTL,("%s: utoppyioctl: UTOPPYIOWRITEFILE " + "%s, size %lld, offset %lld\n", USBDEVNAME(sc->sc_dev), + uw->uw_path, uw->uw_size, uw->uw_offset)); + + if ((err = utoppy_turbo_mode(sc, sc->sc_turbo_mode)) != 0) + break; + + UTOPPY_OUT_INIT(sc); + utoppy_add_8(sc, UTOPPY_FILE_WRITE); + uwfp = utoppy_current_ptr(sc->sc_out_data); + + if ((err = utoppy_add_path(sc, uw->uw_path, 1)) != 0) { + DPRINTF(UTOPPY_DBG_WRITE,("%s: utoppyioctl: add_path() " + "returned %d\n", USBDEVNAME(sc->sc_dev), err)); + break; + } + + strncpy(uwf, &uwfp[2], sizeof(uwf)); + utoppy_add_64(sc, uw->uw_offset); + + err = utoppy_command(sc, UTOPPY_CMD_FILE, UTOPPY_LONG_TIMEOUT, + &resp); + if (err) { + DPRINTF(UTOPPY_DBG_WRITE,("%s: utoppyioctl: " + "utoppy_command(UTOPPY_CMD_FILE) returned " + "%d\n", USBDEVNAME(sc->sc_dev), err)); + break; + } + if (resp != UTOPPY_RESP_SUCCESS) { + DPRINTF(UTOPPY_DBG_WRITE,("%s: utoppyioctl: " + "utoppy_command(UTOPPY_CMD_FILE) returned " + "bad response 0x%x\n", USBDEVNAME(sc->sc_dev), + resp)); + err = EIO; + break; + } + + UTOPPY_OUT_INIT(sc); + utoppy_timestamp_encode(sc, uw->uw_mtime); + utoppy_add_8(sc, UTOPPY_FTYPE_FILE); + utoppy_add_64(sc, uw->uw_size); + utoppy_add_string(sc, uwf, sizeof(uwf)); + utoppy_add_32(sc, 0); + + err = utoppy_command(sc, UTOPPY_RESP_FILE_HEADER, + UTOPPY_LONG_TIMEOUT, &resp); + if (err) { + DPRINTF(UTOPPY_DBG_WRITE,("%s: utoppyioctl: " + "utoppy_command(UTOPPY_RESP_FILE_HEADER) " + "returned %d\n", USBDEVNAME(sc->sc_dev), err)); + break; + } + if (resp != UTOPPY_RESP_SUCCESS) { + DPRINTF(UTOPPY_DBG_WRITE,("%s: utoppyioctl: " + "utoppy_command(UTOPPY_RESP_FILE_HEADER) " + "returned bad response 0x%x\n", + USBDEVNAME(sc->sc_dev), resp)); + err = EIO; + break; + } + + sc->sc_wr_offset = uw->uw_offset; + sc->sc_wr_size = uw->uw_size; + sc->sc_state = UTOPPY_STATE_WRITEFILE; + + DPRINTF(UTOPPY_DBG_WRITE,("%s: utoppyioctl: Changing state to " + "%s. wr_offset %lld, wr_size %lld\n", + USBDEVNAME(sc->sc_dev), utoppy_state_string(sc->sc_state), + sc->sc_wr_offset, sc->sc_wr_size)); + break; + + default: + DPRINTF(UTOPPY_DBG_IOCTL,("%s: utoppyioctl: Invalid cmd\n", + USBDEVNAME(sc->sc_dev))); + err = ENODEV; + break; + } + + DPRINTF(UTOPPY_DBG_IOCTL,("%s: utoppyioctl: done. err %d, state '%s'\n", + USBDEVNAME(sc->sc_dev), err, utoppy_state_string(sc->sc_state))); + + if (err) + utoppy_cancel(sc); + + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(USBDEV(sc->sc_dev)); + + return (err); +} diff --git a/sys/dev/usb/utoppy.h b/sys/dev/usb/utoppy.h new file mode 100644 index 000000000000..7757898191f1 --- /dev/null +++ b/sys/dev/usb/utoppy.h @@ -0,0 +1,106 @@ +/* $NetBSD: utoppy.h,v 1.1 2006/04/03 08:15:48 scw Exp $ */ + +/*- + * Copyright (c) 2006 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Steve C. Woodford. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _DEV_USB_UTOPPY_H_ +#define _DEV_USB_UTOPPY_H_ + +#include + +#define UTOPPY_MAX_FILENAME_LEN 95 +#define UTOPPY_MAX_PATHNAME_LEN ((UTOPPY_MAX_FILENAME_LEN + 1) * 6) + +/* Set/clear turbo mode */ +#define UTOPPYIOTURBO _IOW('t', 1, int) + +/* Cancel previous op */ +#define UTOPPYIOCANCEL _IO('t', 2) + +/* Reboot the toppy */ +#define UTOPPYIOREBOOT _IO('t', 3) + +/* Get status of Toppy's hard disk drive */ +#define UTOPPYIOSTATS _IOR('t', 4, struct utoppy_stats) +struct utoppy_stats { + uint64_t us_hdd_size; + uint64_t us_hdd_free; +}; + +/* Rename a file/directory */ +#define UTOPPYIORENAME _IOW('t', 5, struct utoppy_rename) +struct utoppy_rename { + char *ur_old_path; + char *ur_new_path; +}; + +/* Create a directory */ +#define UTOPPYIOMKDIR _IOW('t', 6, char *) + +/* Delete a file/directory */ +#define UTOPPYIODELETE _IOW('t', 7, char *) + +/* Initiate reading of the contents of a directory */ +#define UTOPPYIOREADDIR _IOW('t', 8, char *) +struct utoppy_dirent { + char ud_path[UTOPPY_MAX_FILENAME_LEN + 1]; + enum { + UTOPPY_DIRENT_UNKNOWN, + UTOPPY_DIRENT_DIRECTORY, + UTOPPY_DIRENT_FILE + } ud_type; + off_t ud_size; + time_t ud_mtime; + uint32_t ud_attributes; +}; + +/* Initiate reading from a specific file */ +#define UTOPPYIOREADFILE _IOW('t', 9, struct utoppy_readfile) +struct utoppy_readfile { + char *ur_path; + off_t ur_offset; +}; + +/* Initiate writing to a new file */ +#define UTOPPYIOWRITEFILE _IOW('t', 10, struct utoppy_writefile) +struct utoppy_writefile { + char *uw_path; + off_t uw_offset; + off_t uw_size; + time_t uw_mtime; +}; + +#endif /* _DEV_USB_UTOPPY_H_ */ diff --git a/usr.bin/Makefile b/usr.bin/Makefile index 3285ddc87604..df2c5607a665 100644 --- a/usr.bin/Makefile +++ b/usr.bin/Makefile @@ -1,4 +1,4 @@ -# $NetBSD: Makefile,v 1.149 2006/01/24 19:01:45 elad Exp $ +# $NetBSD: Makefile,v 1.150 2006/04/03 08:15:48 scw Exp $ # from: @(#)Makefile 8.3 (Berkeley) 1/7/94 .include @@ -24,7 +24,7 @@ SUBDIR= apply apropos asa at audio awk banner basename biff \ shuffle sockstat soelim sort spell split stat su systat tail talk \ tcopy tee telnet tftp time tip tn3270 top touch tput \ tr true tset tsort tty ul uname unexpand unifdef \ - uniq units unvis usbhidaction usbhidctl users \ + uniq units unvis usbhidaction usbhidctl users utoppya \ uudecode uuencode uuidgen vacation vgrind vi vis vmstat vndcompress w \ wall wc what whatis whereis who whois window \ write xargs xinstall xlint xstr yacc yes diff --git a/usr.bin/utoppya/Makefile b/usr.bin/utoppya/Makefile new file mode 100644 index 000000000000..334c3690a24c --- /dev/null +++ b/usr.bin/utoppya/Makefile @@ -0,0 +1,12 @@ +# $NetBSD: Makefile,v 1.1 2006/04/03 08:15:48 scw Exp $ + +.include + +PROG= utoppya +SRCS= utoppya.c progressbar.c + +CPPFLAGS+=-I${NETBSDSRCDIR}/usr.bin/ftp -DSTANDALONE_PROGRESS + +.PATH: ${NETBSDSRCDIR}/usr.bin/ftp + +.include diff --git a/usr.bin/utoppya/utoppya.1 b/usr.bin/utoppya/utoppya.1 new file mode 100644 index 000000000000..d3703dc5de99 --- /dev/null +++ b/usr.bin/utoppya/utoppya.1 @@ -0,0 +1,169 @@ +.\" $NetBSD: utoppya.1,v 1.1 2006/04/03 08:15:49 scw Exp $ +.\" +.\" Copyright (c) 2006 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to The NetBSD Foundation +.\" by Steve C. Woodford. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by the NetBSD +.\" Foundation, Inc. and its contributors. +.\" 4. Neither the name of The NetBSD Foundation nor the names of its +.\" contributors may be used to endorse or promote products derived +.\" from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd April 3, 2006 +.Dt UTOPPYA 1 +.Os +.Sh NAME +.Nm utoppya +.Nd Topfield TF5000PVR file manipulation program +.Sh SYNOPSIS +.Nm +.Op Fl f Ar device +.Ar command ... +.Sh DESCRIPTION +.Nm +is the userland interface to the +.Xr utoppy 4 +device driver. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl f +Communicate with +.Ar device +instead of the default +.Pa /dev/utoppy0 . +.El +.Pp +Using the services of the +.Xr utoppy 4 +driver, +.Nm +can perform the following operations, specified by the +.Ar command +operand: +.Bl -tag -width Ds +.It Cm df +Display disk size and free space +.It Cm ls Op Ar directory +List the files in the specified +.Ar directory . +Default is the Toppy's root directory. +.It Cm mkdir Ao Ar directory Ac +Make the specified +.Ar directory . +You must supply the full pathname to +.Ar directory . +.It Cm rm Ao Ar pathname Ac +Delete the file or directory specified by +.Ar pathname . +.It Cm rename Ao Ar source Ac Ao Ar target Ac +Rename the file or directory specified by the +.Ar source +operand to the destination file or directory specified by the +.Ar target +operand. +.It Cm get Oo Fl prt Oc Ao Ar toppyfile Ac Oo Ar localfile Oc +Copy +.Ar toppyfile +from the Toppy to +.Ar localfile +on the local filesystem. +If +.Ar localfile +is omitted, the file will be copied into the current directory and will be +named using the last component of the +.Ar toppyfile +operand. +If +.Ar localfile +is -, then +.Ar toppyfile +will be copied to the standard output. +.Pp +The following options are available for the +.Nm get +command: +.Bl -tag -width Ds +.It Fl p +Display a progress bar. +.It Fl r +This option is useful if you wish to resume a previously interrupted +.Nm get +command. +Instead of restarting from the beginning of the file, the transfer will +resume where it left off. +.It Fl t +Enable +.Ql Turbo +mode. +This instructs the Toppy to drop everything and concentrate on transferring +the file as quickly as possible. +You will be able to watch live TV, but all other functions, including +changing channel via the remote control, will be inoperative. +.El +.It Cm put Oo Fl prt Oc Ao Ar localfile Ac Ao Ar toppyfile Ac +Copy +.Ar localfile +on the local filesystem to +.Ar toppyfile +on the Toppy. +If +.Ar toppyfile +specifies a directory on the Toppy, the last component of the +.Ar localfile +operand will be appended to the +.Ar toppyfile +operand. +.Pp +The options described for the +.Nm get +command (above) also apply to the +.Nm put +command. +.El +.Sh FILES +.Pa /dev/utoppy0 +The default Topfield TF5000PVR instance. +.Sh SEE ALSO +.Xr utoppy 4 , +.Xr usb 4 +.Sh HISTORY +The +.Nm +command first appeared in +.Nx 4.0 +and was inspired by +.Ql ftpd-topfield +written by Steve Bennett +.Aq msteveb at ozemail.com.au +and +.Ql puppy +written by Peter Urbanec +.Aq toppy at urbanec.net +.Sh AUTHORS +.An Steve C. Woodford Aq scw@netbsd.org diff --git a/usr.bin/utoppya/utoppya.c b/usr.bin/utoppya/utoppya.c new file mode 100644 index 000000000000..f913ee31896e --- /dev/null +++ b/usr.bin/utoppya/utoppya.c @@ -0,0 +1,578 @@ +/* $NetBSD: utoppya.c,v 1.1 2006/04/03 08:15:48 scw Exp $ */ + +/*- + * Copyright (c) 2006 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Steve C. Woodford. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define GLOBAL +#include "progressbar.h" + +#define _PATH_DEV_UTOPPY "/dev/utoppy0" + +/* + * This looks weird for a reason. The toppy protocol allows for data to be + * transferred in 65535-byte chunks only. Anything more than this has to be + * split within the driver. The following value leaves enough space for the + * packet header plus some alignmnent slop. + */ +#define TOPPY_IO_SIZE 0xffec + +static int toppy_fd; + +static void cmd_df(int, char **); +static void cmd_ls(int, char **); +static void cmd_rm(int, char **); +static void cmd_mkdir(int, char **); +static void cmd_rename(int, char **); +static void cmd_get(int, char **); +static void cmd_put(int, char **); + +static struct toppy_command { + const char *tc_cmd; + void (*tc_handler)(int, char **); +} toppy_commands[] = { + {"df", cmd_df}, + {"ls", cmd_ls}, + {"get", cmd_get}, + {"mkdir", cmd_mkdir}, + {"put", cmd_put}, + {"rename", cmd_rename}, + {"rm", cmd_rm}, + {NULL, NULL} +}; + +static void +usage(void) +{ + + fprintf(stderr, "usage: %s [-f ] ...\n", + getprogname()); + + exit(EX_USAGE); +} + +int +main(int argc, char *argv[]) +{ + struct toppy_command *tc; + const char *devpath; + int ch; + + setprogname(argv[0]); + devpath = _PATH_DEV_UTOPPY; + + while ((ch = getopt(argc, argv, "f:")) != -1) { + switch (ch) { + case 'f': + devpath = optarg; + break; + + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc == 0) + usage(); + + for (tc = toppy_commands; tc->tc_cmd != NULL; tc++) + if (strcasecmp(argv[0], tc->tc_cmd) == 0) + break; + + if (tc->tc_cmd == NULL) + errx(EX_USAGE, "'%s' is not a valid command", argv[0]); + + if ((toppy_fd = open(devpath, O_RDWR)) < 0) + err(EX_OSERR, "open(%s)", devpath); + + (*tc->tc_handler)(argc, argv); + + close(toppy_fd); + + return (0); +} + +static int +find_toppy_dirent(const char *path, struct utoppy_dirent *udp) +{ + struct utoppy_dirent ud; + char *d, *b, dir[FILENAME_MAX]; + ssize_t l; + + strncpy(dir, path, sizeof(dir)); + b = basename(dir); + d = dirname(dir); + if (strcmp(b, "/") == 0 || strcmp(b, ".") == 0 || strcmp(d, ".") == 0) + errx(EX_USAGE, "'%s' is not a valid Toppy pathname", path); + + if (ioctl(toppy_fd, UTOPPYIOREADDIR, &d) < 0) + err(EX_OSERR, "ioctl(UTOPPYIOREADDIR, %s)", d); + + if (udp == NULL) + udp = &ud; + + while ((l = read(toppy_fd, udp, sizeof(*udp))) == sizeof(*udp)) { + if (strcmp(b, udp->ud_path) == 0) + break; + } + + if (l < 0) + err(EX_OSERR, "read(TOPPYDIR, %s)", d); + + if (l == 0) + return (0); + + while (read(toppy_fd, &ud, sizeof(ud)) > 0) + ; + + return (1); +} + +static void +cmd_df(int argc, char **argv) +{ + struct utoppy_stats us; + + if (ioctl(toppy_fd, UTOPPYIOSTATS, &us) < 0) + err(EX_OSERR, "ioctl(UTOPPYIOSTATS)"); + + printf("Hard Disk Size: %lld MB\n", us.us_hdd_size / (1024 * 1024)); + printf("Hard Disk Free: %lld MB\n", us.us_hdd_free / (1024 * 1024)); +} + +static void +cmd_ls(int argc, char **argv) +{ + struct utoppy_dirent ud; + struct tm *tm; + char *dir, *ext, dirbuf[2], ex, ft, tmbuf[32]; + ssize_t l; + + if (argc == 1) { + dirbuf[0] = '/'; + dirbuf[1] = '\0'; + dir = dirbuf; + } else + if (argc == 2) + dir = argv[1]; + else + errx(EX_USAGE, "usage: ls [toppy-pathname]"); + + if (ioctl(toppy_fd, UTOPPYIOREADDIR, &dir) < 0) + err(EX_OSERR, "ioctl(UTOPPYIOREADDIR, %s)", dir); + + while ((l = read(toppy_fd, &ud, sizeof(ud))) == sizeof(ud)) { + switch (ud.ud_type) { + default: + ft = '?'; + break; + + case UTOPPY_DIRENT_DIRECTORY: + ft = 'd'; + break; + + case UTOPPY_DIRENT_FILE: + ft = '-'; + break; + } + + if ((ext = strrchr(ud.ud_path, '.')) != NULL && + strcasecmp(ext, ".tap") == 0) + ex = 'x'; + else + ex = '-'; + + tm = localtime(&ud.ud_mtime); + strftime(tmbuf, sizeof(tmbuf), "%b %e %G %R", tm); + + printf("%crw%c %11lld %s %s\n", ft, ex, ud.ud_size, tmbuf, + ud.ud_path); + } + + if (l < 0) + err(EX_OSERR, "read(utoppy_dirent)"); +} + +static void +cmd_rm(int argc, char **argv) +{ + char *path; + + if (argc != 2) + errx(EX_USAGE, "usage: rm "); + + path = argv[1]; + + if (ioctl(toppy_fd, UTOPPYIODELETE, &path) < 0) + err(EX_OSERR, "ioctl(UTOPPYIODELETE, %s)", path); +} + +static void +cmd_mkdir(int argc, char **argv) +{ + char *path; + + if (argc != 2) + errx(EX_USAGE, "usage: mkdir "); + + path = argv[1]; + + if (find_toppy_dirent(path, NULL)) + errx(EX_DATAERR, "'%s' already exists", path); + + if (ioctl(toppy_fd, UTOPPYIOMKDIR, &path) < 0) + err(EX_OSERR, "ioctl(UTOPPYIOMKDIR, %s)", path); +} + +static void +cmd_rename(int argc, char **argv) +{ + struct utoppy_dirent ud; + struct utoppy_rename ur; + char *oldpath, *newpath, *o, *n; + + if (argc != 3) + errx(EX_USAGE, "usage: rename "); + + o = oldpath = argv[1]; + n = newpath = argv[2]; + + for (o = oldpath; *o != '\0'; o++) + if (*o == '\\') + *o = '/'; + for (n = newpath; *n != '\0'; n++) + if (*n == '\\') + *n = '/'; + + for (o = oldpath; *o && *o == '\\'; o++) + ; + for (n = newpath; *n && *n == '\\'; n++) + ; + + if (strcmp(n, o) == 0) + errx(EX_DATAERR, "'%s' and '%s' refer to the same file\n", + oldpath, newpath); + + if (find_toppy_dirent(oldpath, &ud) == 0) + errx(EX_DATAERR, "'%s' does not exist on the Toppy", oldpath); + + if (ud.ud_type != UTOPPY_DIRENT_FILE) + errx(EX_DATAERR, "%s: not a regular file", oldpath); + + if (find_toppy_dirent(newpath, &ud)) + errx(EX_DATAERR, "'%s' already exists", newpath); + + ur.ur_old_path = o; + ur.ur_new_path = n; + + if (ioctl(toppy_fd, UTOPPYIORENAME, &ur) < 0) + err(EX_OSERR, "ioctl(UTOPPYIORENAME, %s, %s)", oldpath, + newpath); +} + + +static void +init_progress(FILE *to, char *f, off_t fsize, off_t restart) +{ + struct ttysize ts; + + if (ioctl(fileno(to), TIOCGSIZE, &ts) == -1) + ttywidth = 80; + else + ttywidth = ts.ts_cols; + + ttyout = to; + progress = 1; + bytes = 0; + filesize = fsize; + restart_point = restart; + prefix = f; +} + +static void +cmd_get(int argc, char **argv) +{ + struct utoppy_readfile ur; + struct utoppy_dirent ud; + struct stat st; + char *dst, dstbuf[FILENAME_MAX]; + uint8_t *buf; + ssize_t l; + size_t rv; + int ch, turbo_mode = 0, reget = 0, progbar = 0; + FILE *ofp, *to; + + optind = 1; + optreset = 1; + + while ((ch = getopt(argc, argv, "prt")) != -1) { + switch (ch) { + case 'p': + progbar = 1; + break; + case 'r': + reget = 1; + break; + case 't': + turbo_mode = 1; + break; + default: + get_usage: + errx(EX_USAGE, "usage: get [-prt] " + "[file | directory]"); + } + } + argc -= optind; + argv += optind; + + if (argc == 1) + dst = basename(argv[0]); + else + if (argc == 2) { + dst = argv[1]; + if (stat(dst, &st) == 0 && S_ISDIR(st.st_mode)) { + snprintf(dstbuf, sizeof(dstbuf), "%s/%s", dst, + basename(argv[0])); + dst = dstbuf; + } + } else + goto get_usage; + + ur.ur_path = argv[0]; + ur.ur_offset = 0; + + if ((buf = malloc(TOPPY_IO_SIZE)) == NULL) + err(EX_OSERR, "malloc(TOPPY_IO_SIZE)"); + + if (strcmp(dst, "-") == 0) { + ofp = stdout; + to = stderr; + if (reget) + warnx("Ignoring -r option in combination with stdout"); + } else { + to = stdout; + + if (reget) { + if (stat(dst, &st) < 0) { + if (errno != ENOENT) + err(EX_OSERR, "stat(%s)", dst); + } else + if (!S_ISREG(st.st_mode)) + errx(EX_DATAERR, "-r only works with regular " + "files"); + else + ur.ur_offset = st.st_size; + } + + if ((ofp = fopen(dst, reget ? "a" : "w")) == NULL) + err(EX_OSERR, "fopen(%s)", dst); + } + + if (progbar) { + if (find_toppy_dirent(ur.ur_path, &ud) == 0) + ud.ud_size = 0; + init_progress(to, dst, ud.ud_size, ur.ur_offset); + } + + if (ioctl(toppy_fd, UTOPPYIOTURBO, &turbo_mode) < 0) + err(EX_OSERR, "ioctl(UTOPPYIOTURBO, %d)", turbo_mode); + + if (ioctl(toppy_fd, UTOPPYIOREADFILE, &ur) < 0) + err(EX_OSERR, "ioctl(UTOPPYIOREADFILE, %s)", ur.ur_path); + + if (progbar) + progressmeter(-1); + + for (;;) { + while ((l = read(toppy_fd, buf, TOPPY_IO_SIZE)) < 0 && + errno == EINTR) + ; + + if (l <= 0) + break; + + rv = fwrite(buf, 1, l, ofp); + + if (rv != l) { + if (ofp != stdout) + fclose(ofp); + progressmeter(1); + err(EX_OSERR, "fwrite(%s)", dst); + } + bytes += l; + } + + if (progbar) + progressmeter(1); + + if (ofp != stdout) + fclose(ofp); + + if (l < 0) + err(EX_OSERR, "read(TOPPY: ur.ur_path)"); + + free(buf); +} + +static void +cmd_put(int argc, char **argv) +{ + struct utoppy_writefile uw; + struct utoppy_dirent ud; + struct stat st; + char dstbuf[FILENAME_MAX]; + char *src; + void *buf; + ssize_t rv; + size_t l; + int ch, turbo_mode = 0, reput = 0, progbar = 0; + FILE *ifp; + + optind = 1; + optreset = 1; + + while ((ch = getopt(argc, argv, "prt")) != -1) { + switch (ch) { + case 'p': + progbar = 1; + break; + case 'r': + reput = 1; + break; + case 't': + turbo_mode = 1; + break; + default: + put_usage: + errx(EX_USAGE, "usage: put [-prt] " + ""); + } + } + argc -= optind; + argv += optind; + + if (argc != 2) + goto put_usage; + + src = argv[0]; + uw.uw_path = argv[1]; + + if (stat(src, &st) < 0) + err(EX_OSERR, "%s", src); + + if (!S_ISREG(st.st_mode)) + errx(EX_DATAERR, "'%s' is not a regular file", src); + + uw.uw_size = st.st_size; + uw.uw_mtime = st.st_mtime; + uw.uw_offset = 0; + + if (find_toppy_dirent(uw.uw_path, &ud)) { + if (ud.ud_type == UTOPPY_DIRENT_DIRECTORY) { + snprintf(dstbuf, sizeof(dstbuf), "%s/%s", uw.uw_path, + basename(src)); + uw.uw_path = dstbuf; + } else + if (ud.ud_type != UTOPPY_DIRENT_FILE) + errx(EX_DATAERR, "'%s' is not a regular file.", + uw.uw_path); + else + if (reput) { + if (ud.ud_size > uw.uw_size) + errx(EX_DATAERR, "'%s' is already larger than " + "'%s'", uw.uw_path, src); + + uw.uw_size -= ud.ud_size; + uw.uw_offset = ud.ud_size; + } + } + + if ((buf = malloc(TOPPY_IO_SIZE)) == NULL) + err(EX_OSERR, "malloc(TOPPY_IO_SIZE)"); + + if ((ifp = fopen(src, "r")) == NULL) + err(EX_OSERR, "fopen(%s)", src); + + if (ioctl(toppy_fd, UTOPPYIOTURBO, &turbo_mode) < 0) + err(EX_OSERR, "ioctl(UTOPPYIOTURBO, %d)", turbo_mode); + + if (ioctl(toppy_fd, UTOPPYIOWRITEFILE, &uw) < 0) + err(EX_OSERR, "ioctl(UTOPPYIOWRITEFILE, %s)", uw.uw_path); + + if (progbar) + init_progress(stdout, src, st.st_size, uw.uw_offset); + + if (progbar) + progressmeter(-1); + + while ((l = fread(buf, 1, TOPPY_IO_SIZE, ifp)) > 0) { + rv = write(toppy_fd, buf, l); + if (rv != l) { + fclose(ifp); + if (progbar) + progressmeter(1); + err(EX_OSERR, "write(TOPPY: %s)", uw.uw_path); + } + bytes += l; + } + + if (progbar) + progressmeter(1); + + if (ferror(ifp)) + err(EX_OSERR, "fread(%s)", src); + + fclose(ifp); + free(buf); +}