/* $NetBSD: utoppy.c,v 1.24.2.1 2017/04/05 19:54:21 snj 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. * * 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.24.2.1 2017/04/05 19:54:21 snj Exp $"); #ifdef _KERNEL_OPT #include "opt_usb.h" #endif #include #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 { device_t sc_dev; struct usbd_device *sc_udev; /* device */ struct usbd_interface *sc_iface; /* interface */ int sc_dying; int sc_refcnt; enum utoppy_state sc_state; u_int sc_turbo_mode; int sc_out; struct usbd_pipe *sc_out_pipe; /* bulk out pipe */ struct usbd_xfer *sc_out_xfer; void *sc_out_buf; void *sc_out_data; uint64_t sc_wr_offset; uint64_t sc_wr_size; int sc_in; struct usbd_pipe *sc_in_pipe; /* bulk in pipe */ struct usbd_xfer *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 = { .d_open = utoppyopen, .d_close = utoppyclose, .d_read = utoppyread, .d_write = utoppywrite, .d_ioctl = utoppyioctl, .d_stop = nostop, .d_tty = notty, .d_poll = nopoll, .d_mmap = nommap, .d_kqfilter = nokqfilter, .d_discard = nodiscard, .d_flag = D_OTHER }; #define UTOPPYUNIT(n) (minor(n)) int utoppy_match(device_t, cfdata_t, void *); void utoppy_attach(device_t, device_t, void *); int utoppy_detach(device_t, int); int utoppy_activate(device_t, enum devact); extern struct cfdriver utoppy_cd; CFATTACH_DECL_NEW(utoppy, sizeof(struct utoppy_softc), utoppy_match, utoppy_attach, utoppy_detach, utoppy_activate); int utoppy_match(device_t parent, cfdata_t match, void *aux) { struct usb_attach_arg *uaa = aux; if (uaa->uaa_vendor == USB_VENDOR_TOPFIELD && uaa->uaa_product == USB_PRODUCT_TOPFIELD_TF5000PVR) return UMATCH_VENDOR_PRODUCT; return UMATCH_NONE; } void utoppy_attach(device_t parent, device_t self, void *aux) { struct utoppy_softc *sc = device_private(self); struct usb_attach_arg *uaa = aux; struct usbd_device *dev = uaa->uaa_device; struct usbd_interface *iface; usb_endpoint_descriptor_t *ed; char *devinfop; uint8_t epcount; int i; sc->sc_dev = self; aprint_naive("\n"); aprint_normal("\n"); devinfop = usbd_devinfo_alloc(dev, 0); aprint_normal_dev(self, "%s\n", devinfop); usbd_devinfo_free(devinfop); sc->sc_dying = 0; sc->sc_refcnt = 0; sc->sc_udev = dev; if (usbd_set_config_index(dev, 0, 1) || usbd_device2interface_handle(dev, 0, &iface)) { aprint_error_dev(self, "Configuration failed\n"); return; } epcount = 0; (void) usbd_endpoint_count(iface, &epcount); if (epcount != UTOPPY_NUMENDPOINTS) { aprint_error_dev(self, "Expected %d endpoints, got %d\n", UTOPPY_NUMENDPOINTS, epcount); return; } sc->sc_in = -1; sc->sc_out = -1; for (i = 0; i < epcount; i++) { ed = usbd_interface2endpoint_descriptor(iface, i); if (ed == NULL) { aprint_error_dev(self, "couldn't get ep %d\n", i); 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) { aprint_error_dev(self, "could not find bulk in/out endpoints\n"); sc->sc_dying = 1; return; } sc->sc_iface = iface; sc->sc_udev = dev; 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: usbd_open_pipe(OUT) failed\n", device_xname(sc->sc_dev))); aprint_error_dev(self, "could not open OUT pipe\n"); sc->sc_dying = 1; return; } if (usbd_open_pipe(sc->sc_iface, sc->sc_in, 0, &sc->sc_in_pipe)) { DPRINTF(UTOPPY_DBG_OPEN, ("%s: usbd_open_pipe(IN) failed\n", device_xname(sc->sc_dev))); aprint_error_dev(self, "could not open IN pipe\n"); usbd_close_pipe(sc->sc_out_pipe); sc->sc_out_pipe = NULL; sc->sc_dying = 1; return; } int error; error = usbd_create_xfer(sc->sc_out_pipe, UTOPPY_FRAG_SIZE, 0, 0, &sc->sc_out_xfer); if (error) { aprint_error_dev(self, "could not allocate bulk out xfer\n"); goto fail0; } error = usbd_create_xfer(sc->sc_in_pipe, UTOPPY_FRAG_SIZE, USBD_SHORT_XFER_OK, 0, &sc->sc_in_xfer); if (error) { aprint_error_dev(self, "could not allocate bulk in xfer\n"); goto fail1; } sc->sc_out_buf = usbd_get_buffer(sc->sc_out_xfer); sc->sc_in_buf = usbd_get_buffer(sc->sc_in_xfer); usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, sc->sc_dev); return; fail1: usbd_destroy_xfer(sc->sc_out_xfer); sc->sc_out_xfer = NULL; fail0: sc->sc_dying = 1; return; } int utoppy_activate(device_t self, enum devact act) { struct utoppy_softc *sc = device_private(self); switch (act) { case DVACT_DEACTIVATE: sc->sc_dying = 1; return 0; default: return EOPNOTSUPP; } } int utoppy_detach(device_t self, int flags) { struct utoppy_softc *sc = device_private(self); 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_destroy_xfer(sc->sc_in_xfer); if (sc->sc_out_xfer != NULL) usbd_destroy_xfer(sc->sc_out_xfer); if (sc->sc_out_pipe != NULL) usbd_close_pipe(sc->sc_out_pipe); if (sc->sc_in_pipe != NULL) usbd_close_pipe(sc->sc_in_pipe); s = splusb(); if (--sc->sc_refcnt >= 0) usb_detach_waitold(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 = device_unit(self); vdevgone(maj, mn, mn, VCHR); usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, 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 static usbd_status utoppy_bulk_transfer(struct usbd_xfer *xfer, struct usbd_pipe *pipe, uint16_t flags, uint32_t timeout, void *buf, uint32_t *size) { usbd_status err; usbd_setup_xfer(xfer, 0, buf, *size, flags, timeout, NULL); err = usbd_sync_transfer_sig(xfer); usbd_get_xfer_status(xfer, NULL, NULL, size, NULL); 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", device_xname(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", device_xname(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", device_xname(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, 0, timeout, sc->sc_out_buf, &thislen); if (thislen != min(len, UTOPPY_FRAG_SIZE)) { DPRINTF(UTOPPY_DBG_SEND_PACKET, ("%s: " "utoppy_send_packet: sent %ld, err %d\n", device_xname(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", device_xname(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", device_xname(sc->sc_dev))); do { requested = thislen = min(bytesleft, UTOPPY_FRAG_SIZE); err = utoppy_bulk_transfer(sc->sc_in_xfer, sc->sc_in_pipe, USBD_SHORT_XFER_OK, timeout, sc->sc_in_buf, &thislen); DPRINTF(UTOPPY_DBG_RECV_PACKET, ("%s: utoppy_recv_packet: " "usbd_bulk_transfer() returned %d, thislen %d, data %p\n", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(sc->sc_dev), err)); return err; } } /* The command word is part of the CRC */ crc = UTOPPY_CRC16(0, h->h_cmd2 >> 8); crc = UTOPPY_CRC16(crc, 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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(sc->sc_dev))); break; case UTOPPY_RESP_ERROR: default: DPRINTF(UTOPPY_DBG_READ, ("%s: utoppy_readfile_next: bad " "response code 0x%0x\n", device_xname(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; sc = device_lookup_private(&utoppy_cd, UTOPPYUNIT(dev)); if (sc == NULL) return ENXIO; 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", device_xname(sc->sc_dev))); return EBUSY; } DPRINTF(UTOPPY_DBG_OPEN, ("%s: utoppyopen: opening...\n", device_xname(sc->sc_dev))); sc->sc_refcnt++; sc->sc_state = UTOPPY_STATE_OPENING; sc->sc_turbo_mode = 0; sc->sc_out_data = kmem_alloc(UTOPPY_BSIZE + 1, KM_SLEEP); if (sc->sc_out_data == NULL) { error = ENOMEM; goto error; } sc->sc_in_data = kmem_alloc(UTOPPY_BSIZE + 1, KM_SLEEP); if (sc->sc_in_data == NULL) { kmem_free(sc->sc_out_data, UTOPPY_BSIZE + 1); 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", device_xname(sc->sc_dev), error)); } error: sc->sc_state = error ? UTOPPY_STATE_CLOSED : UTOPPY_STATE_IDLE; DPRINTF(UTOPPY_DBG_OPEN, ("%s: utoppyopen: done. error %d, new state " "'%s'\n", device_xname(sc->sc_dev), error, utoppy_state_string(sc->sc_state))); if (--sc->sc_refcnt < 0) usb_detach_wakeupold(sc->sc_dev); return error; } int utoppyclose(dev_t dev, int flag, int mode, struct lwp *l) { struct utoppy_softc *sc; usbd_status err; sc = device_lookup_private(&utoppy_cd, UTOPPYUNIT(dev)); DPRINTF(UTOPPY_DBG_CLOSE, ("%s: utoppyclose: closing...\n", device_xname(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", device_xname(sc->sc_dev), utoppy_state_string(sc->sc_state))); return 0; } if (sc->sc_out_data) (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); 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); sc->sc_in_pipe = NULL; } if (sc->sc_out_data) { kmem_free(sc->sc_out_data, UTOPPY_BSIZE + 1); sc->sc_out_data = NULL; } if (sc->sc_in_data) { kmem_free(sc->sc_in_data, UTOPPY_BSIZE + 1); sc->sc_in_data = NULL; } sc->sc_state = UTOPPY_STATE_CLOSED; DPRINTF(UTOPPY_DBG_CLOSE, ("%s: utoppyclose: done.\n", device_xname(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; sc = device_lookup_private(&utoppy_cd, UTOPPYUNIT(dev)); if (sc->sc_dying) return EIO; sc->sc_refcnt++; DPRINTF(UTOPPY_DBG_READ, ("%s: utoppyread: reading: state '%s'\n", device_xname(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", device_xname(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", device_xname(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", device_xname(sc->sc_dev), err, utoppy_state_string(sc->sc_state))); if (--sc->sc_refcnt < 0) usb_detach_wakeupold(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; sc = device_lookup_private(&utoppy_cd, UTOPPYUNIT(dev)); 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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(sc->sc_dev), err)); utoppy_cancel(sc); } sc->sc_state = UTOPPY_STATE_IDLE; DPRINTF(UTOPPY_DBG_WRITE, ("%s: utoppywrite: state %s\n", device_xname(sc->sc_dev), utoppy_state_string(sc->sc_state))); } if (--sc->sc_refcnt < 0) usb_detach_wakeupold(sc->sc_dev); return err; } int utoppyioctl(dev_t dev, u_long cmd, void *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; sc = device_lookup_private(&utoppy_cd, UTOPPYUNIT(dev)); if (sc->sc_dying) return EIO; DPRINTF(UTOPPY_DBG_IOCTL, ("%s: utoppyioctl: cmd 0x%08lx, state '%s'\n", device_xname(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", device_xname(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", device_xname(sc->sc_dev), sc->sc_turbo_mode ? "On" : "Off")); break; case UTOPPYIOCANCEL: DPRINTF(UTOPPY_DBG_IOCTL, ("%s: utoppyioctl: UTOPPYIOCANCEL\n", device_xname(sc->sc_dev))); err = utoppy_cancel(sc); break; case UTOPPYIOREBOOT: DPRINTF(UTOPPY_DBG_IOCTL, ("%s: utoppyioctl: UTOPPYIOREBOOT\n", device_xname(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", device_xname(sc->sc_dev))); err = utoppy_stats(sc, (struct utoppy_stats *)data); break; case UTOPPYIORENAME: DPRINTF(UTOPPY_DBG_IOCTL, ("%s: utoppyioctl: UTOPPYIORENAME\n", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(sc->sc_dev), err)); break; } err = utoppy_readdir_next(sc); if (err) { DPRINTF(UTOPPY_DBG_READDIR, ("%s: utoppyioctl: " "utoppy_readdir_next() returned %d\n", device_xname(sc->sc_dev), err)); } break; case UTOPPYIOREADFILE: urf = (struct utoppy_readfile *)data; DPRINTF(UTOPPY_DBG_IOCTL,("%s: utoppyioctl: UTOPPYIOREADFILE " "%s, offset %lld\n", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(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", device_xname(sc->sc_dev))); err = ENODEV; break; } DPRINTF(UTOPPY_DBG_IOCTL,("%s: utoppyioctl: done. err %d, state '%s'\n", device_xname(sc->sc_dev), err, utoppy_state_string(sc->sc_state))); if (err) utoppy_cancel(sc); if (--sc->sc_refcnt < 0) usb_detach_wakeupold(sc->sc_dev); return err; }