;;; ;;; sb-usb.lisp ;;; ;;; Linux usbdevfs access for SBCL ;;; (cl:defpackage :sb-usb (:use :common-lisp :sb-alien) (:export "USB-DISCONNECT" "USB-CONNECT" "USB-CLAIM-INTERFACE" "USB-RELEASE-INTERFACE" "USB-GET-DRIVER" "USB-TIMEOUT" "RETRY" "USB-CONTROL" "USB-BULK")) (cl:in-package :sb-usb) ;;; Linux IOCTL definition helpers (eval-when (:compile-toplevel :load-toplevel :execute) ;; Damned IOCTL junk is defined in unsigned, but implemented in signed. :-/ (defun u32->s32 (foo) (if (logbitp 31 foo) (logior #x-80000000 foo) foo))) (defmacro encode-ioctl (mode type index &optional (struct nil struct-p)) (when (and (eq mode :io) struct-p) (error "Attempting to encode :IO IOCTL with a STRUCT")) (when (and (not (eq mode :io)) (not struct-p)) (error "Attempting to encode ~S IOCTL without a STRUCT" mode)) (let* ((mode-bits (ecase mode (:iowr 3) (:ior 2) (:iow 1) (:io 0))) (type-bits (char-code type)) (length-bits `(alien-size ,struct :bytes)) (non-length-bits (dpb mode-bits (byte 2 30) (dpb type-bits (byte 8 8) index)))) `(u32->s32 ,(if struct-p `(dpb ,length-bits (byte 14 16) ,non-length-bits) non-length-bits)))) (defmacro define-ioctl (name mode type index &optional (struct nil struct-p)) `(defconstant ,name (encode-ioctl ,mode ,type ,index ,@(when struct-p (list struct))))) ;;; USB IOCTL structures (define-alien-type usbdevfs-ctrltransfer (struct usbdevfs-ctrltransfer (request-type (unsigned 8)) (request (unsigned 8)) (value (unsigned 16)) (index (unsigned 16)) (length (unsigned 16)) (timeout (unsigned 32)) ;; in milliseconds (frames?) (data (* t)))) (define-alien-type usbdevfs-bulktransfer (struct usbdevfs-bulktransfer (endpoint unsigned-int) (length unsigned-int) (timeout unsigned-int) ;; in milliseconds (frames?) (data (* t)))) (define-alien-type usbdevfs-setinterface (struct usbdevfs-setinterface (interface unsigned-int) (altsetting unsigned-int))) (define-alien-type usbdevfs-disconnectsignal (struct usbdevfs-disconnectsignal (signr unsigned-int) (context (* t)))) (eval-when (:compile-toplevel :load-toplevel :execute) (defconstant +usbdevfs-maxdrivername+ 255)) (define-alien-type usbdevfs-getdriver (struct usbdevfs-getdriver (interface unsigned-int) (driver (array char #.(1+ +usbdevfs-maxdrivername+))))) (define-alien-type usbdevfs-connectinfo (struct usbdevfs-connectinfo (devnum unsigned-int) (slow unsigned-char))) ;; flags? (defconstant +usbdevfs-urb-short-not-ok+ 1) (defconstant +usbdevfs-urb-iso-asap+ 2) (defconstant +usbdevfs-urb-type-iso+ 0) (defconstant +usbdevfs-urb-type-interrupt+ 1) (defconstant +usbdevfs-urb-type-control+ 2) (defconstant +usbdevfs-urb-type-bulk+ 3) (define-alien-type usbdevfs-iso-packet-desc (struct usbdevfs-iso-packet-desc (length unsigned-int) (actual-length unsigned-int) (status unsigned-int))) (define-alien-type usbdevfs-urb (struct usbdevfs-urb (type unsigned-char) (endpoint unsigned-char) (status int) (flags unsigned-int) (buffer (* t)) (buffer-length int) (actual-length int) (start-frame int) (number-of-packets int) (error-count int) (signr unsigned-int) (usercontext (* t)) (iso-frame-desc (array usbdevfs-iso-packet-desc 0)))) (define-alien-type usbdevfs-ioctl (struct usbdevfs-ioctl (ifno int) (ioctl-code int) (data (* t)))) (define-alien-type usbdevfs-hub-portinfo (struct usbdevfs-hub-portinfo (nports char) (port (array char 127)))) ;;; USB IOCTLs ;; We're a userspace program, not a kernel, we don't need the 32-bit ;; specific ioctls, they're for compatability within the kernel ;; itself. (define-ioctl USBDEVFS_CONTROL :iowr #\U 0 usbdevfs-ctrltransfer) (define-ioctl USBDEVFS_BULK :iowr #\U 2 usbdevfs-bulktransfer) (define-ioctl USBDEVFS_RESETEP :ior #\U 3 unsigned-int) (define-ioctl USBDEVFS_SETINTERFACE :ior #\U 4 usbdevfs-setinterface) (define-ioctl USBDEVFS_SETCONFIGURATION :ior #\U 5 unsigned-int) (define-ioctl USBDEVFS_GETDRIVER :iow #\U 8 usbdevfs-getdriver) (define-ioctl USBDEVFS_SUBMITURB :ior #\U 10 usbdevfs-urb) ;;(define-ioctl USBDEVFS_SUBMITURB32 :ior #\U 10 usbdevfs-urb32) (define-ioctl USBDEVFS_DISCARDURB :io #\U 11) (define-ioctl USBDEVFS_REAPURB :iow #\U 12 (* t)) ;;(define-ioctl USBDEVFS_REAPURB32 :iow #\U 12 (unsigned 32)) (define-ioctl USBDEVFS_REAPURBNDELAY :iow #\U 13 (* t)) ;;(define-ioctl USBDEVFS_REAPURBNDELAY32 :iow #\U 13 (unsigned 32)) (define-ioctl USBDEVFS_DISCSIGNAL :ior #\U 14 usbdevfs-disconnectsignal) (define-ioctl USBDEVFS_CLAIMINTERFACE :ior #\U 15 unsigned-int) (define-ioctl USBDEVFS_RELEASEINTERFACE :ior #\U 16 unsigned-int) (define-ioctl USBDEVFS_CONNECTINFO :iow #\U 17 usbdevfs-connectinfo) (define-ioctl USBDEVFS_IOCTL :iowr #\U 18 usbdevfs-ioctl) ;;(define-ioctl USBDEVFS_IOCTL32 :iowr #\U 18 usbdevfs-ioctl32) (define-ioctl USBDEVFS_HUB_PORTINFO :ior #\U 19 usbdevfs-hub-portinfo) (define-ioctl USBDEVFS_RESET :io #\U 20) (define-ioctl USBDEVFS_CLEAR_HALT :ior #\U 21 unsigned-int) (define-ioctl USBDEVFS_DISCONNECT :io #\U 22) (define-ioctl USBDEVFS_CONNECT :io #\U 23) ;;; Extra syscall return values (defconstant ETIMEDOUT 110) ;;; USB wrappers ;; FIXME: CONNECT, DISCONNECT and GETDRIVER all take an interface ;; number parameter. This came as a surprise to me, but upon ;; reflection, aside from GETDRIVER, the IOCTL definitions make sense. ;; GETDRIVER is listed as a write-only interface, while CONNECT and ;; DISCONNECT pass the interface via the IOCTL structure. (defun usb-disconnect (dev) "Persuade the kernel driver to relinquish its claim upon a device." (with-alien ((ctrl usbdevfs-ioctl)) (setf (slot ctrl 'ioctl-code) USBDEVFS_DISCONNECT) (setf (slot ctrl 'ifno) 0) (setf (slot ctrl 'data) nil) (multiple-value-bind (successp error) (sb-unix:unix-ioctl (sb-sys:fd-stream-fd dev) USBDEVFS_IOCTL (alien-sap (addr ctrl))) (unless successp (error "USBDEVFS_DISCONNECT failed: ~S" (sb-int:strerror error)))))) (defun usb-connect (dev) "Request the kernel driver to reassert its claim upon a device." (with-alien ((ctrl usbdevfs-ioctl)) (setf (slot ctrl 'ioctl-code) USBDEVFS_CONNECT) (setf (slot ctrl 'ifno) 0) (setf (slot ctrl 'data) nil) (multiple-value-bind (successp error) (sb-unix:unix-ioctl (sb-sys:fd-stream-fd dev) USBDEVFS_IOCTL (alien-sap (addr ctrl))) (unless successp (error "USBDEVFS_CONNECT failed: ~S" (sb-int:strerror error)))))) (defun usb-claim-interface (dev interface) (with-alien ((iface unsigned-long)) (setf iface interface) (multiple-value-bind (successp error) (sb-unix:unix-ioctl (sb-sys:fd-stream-fd dev) USBDEVFS_CLAIMINTERFACE (alien-sap (addr iface))) (unless successp (error "USBDEVFS_CLAIMINTERFACE failed: ~S" (sb-int:strerror error)))))) (defun usb-release-interface (dev interface) (with-alien ((iface unsigned-long)) (setf iface interface) (multiple-value-bind (successp error) (sb-unix:unix-ioctl (sb-sys:fd-stream-fd dev) USBDEVFS_RELEASEINTERFACE (alien-sap (addr iface))) (unless successp (error "USBDEVFS_RELEASEINTERFACE failed: ~S" (sb-int:strerror error)))))) (defun usb-get-driver (dev) (with-alien ((driver usbdevfs-getdriver)) (setf (slot driver 'interface) 0) (multiple-value-bind (successp error) (sb-unix:unix-ioctl (sb-sys:fd-stream-fd dev) USBDEVFS_GETDRIVER (alien-sap (addr driver))) (unless successp (error "USBDEVFS_GETDRIVER failed: ~S" (sb-int:strerror error)))) (values (slot driver 'interface) (cast (slot driver 'driver) c-string)))) (define-condition usb-timeout (error) ()) (defun usb-control (dev request-type request value index data) (with-alien ((ctrl usbdevfs-ctrltransfer)) (setf (slot ctrl 'request-type) request-type (slot ctrl 'request) request (slot ctrl 'value) value (slot ctrl 'index) index (slot ctrl 'length) (length data) (slot ctrl 'timeout) 50 (slot ctrl 'data) (sb-sys:vector-sap (sb-kernel:%array-data-vector data))) (tagbody :retry (restart-case (multiple-value-bind (successp error) (sb-unix:unix-ioctl (sb-sys:fd-stream-fd dev) USBDEVFS_CONTROL (alien-sap (addr ctrl))) (unless successp (if (= error ETIMEDOUT) (error 'usb-timeout) (error "USBDEVFS_CONTROL failed: ~S" (sb-int:strerror error))))) (retry () (go :retry)))) (setf (fill-pointer data) (slot ctrl 'length)))) (defun usb-bulk (dev endpoint data &optional (offset 0)) (with-alien ((bulk usbdevfs-bulktransfer)) (setf (slot bulk 'endpoint) endpoint (slot bulk 'length) (- (length data) offset) (slot bulk 'timeout) 50 (slot bulk 'data) (sb-sys:sap+ (sb-sys:vector-sap (sb-kernel:%array-data-vector data)) offset)) (tagbody :retry (restart-case (multiple-value-bind (successp error) (sb-unix:unix-ioctl (sb-sys:fd-stream-fd dev) USBDEVFS_BULK (alien-sap (addr bulk))) (unless successp (if (= error ETIMEDOUT) (error 'usb-timeout) (error "USBDEVFS_CONTROL failed: ~S" (sb-int:strerror error))))) (retry () (go :retry)))) (setf (fill-pointer data) (+ (slot bulk 'length) offset)))) #| (with-alien ((urb usbdevfs-urb) (buf (array (unsigned 8) 8)) (ptr (* usbdevfs-urb))) (setf (slot urb 'type) +usbdevfs-urb-type-interrupt+) (setf (slot urb 'endpoint) #x81) (setf (slot urb 'flags) 0) (setf (slot urb 'buffer) (addr buf)) (setf (slot urb 'buffer-length) 8) (setf (slot urb 'signr) #xffffffff) (setf (slot urb 'actual-length) 0) (setf (slot urb 'number-of-packets) 0) (setf (slot urb 'usercontext) nil) (setf (slot urb 'error-count) 0) (setf (slot urb 'start-frame) 0) (setf (slot urb 'status) 0) (multiple-value-bind (successp error) (sb-unix:unix-ioctl (sb-sys:fd-stream-fd cl-user::*dev*) USBDEVFS_SUBMITURB (alien-sap (addr urb))) (unless successp (error "USBDEVFS_SUBMITURB failed: ~S" (sb-int:strerror error)))) (setf ptr nil) (multiple-value-bind (successp error) (sb-unix:unix-ioctl (sb-sys:fd-stream-fd cl-user::*dev*) USBDEVFS_REAPURB (alien-sap (addr ptr))) (unless successp (error "USBDEVFS_REAPURB failed: ~S" (sb-int:strerror error)))) (inspect (list urb buf ptr))) ;; top-left 6 00FF008C00390302 6 00FF008C00380302 6 0061008C00380302 6 00FF008B00380302 6 0061008B00380202 ;; top-right 6 00FF009E0FAA0302 6 0061009E0FAA0202 6 000000A70FAB0302 6 00FF00A80FAA0302 6 00FF00A70FAC0302 6 000000A70FAC0202 ;; bottom-left 6 00000F8F00400302 6 00020F8D00410302 6 00FF0F8A00420302 6 00FF0F8900420302 6 00000F8B00430302 6 00FF0F8B00440302 6 00FF0F8A00430302 6 00000F8A00430302 6 00FF0F8700420302 6 00000F8700420202 ;; bottom-right 6 00FF0F430FAB0302 6 00610F450FA90302 6 00000F440FA50302 6 00020F430FA10302 6 00FF0F440FA00302 6 00FF0F440F9F0302 6 00FF0F450F9E0302 6 00000F460FA00302 6 00610F450FA20302 6 00FF0F450FA20202 ;; drag, top edge, left-to-right 6 00FF008206080302 6 00FF008106090302 6 0061007F06090302 6 00FF007E06090302 6 00FF007E060C0302 6 00FF00BE060F0302 6 006100BE06160302 6 000000AC061C0302 6 0002009D06210302 6 00FF009006250302 6 00FF0087062A0302 6 00FF0081062E0302 6 0000007E06320302 6 0061007D06360302 6 0002007F06390302 6 00FF007E063D0302 6 0000007D06430302 6 0061007B064A0302 6 0002007906520302 6 00FF0079065B0302 6 0000007906630302 6 00610079066C0302 6 0002007A06750302 6 00FF007A067D0302 6 0000007906860302 6 00FF007906900302 6 0000007B069B0302 6 00FF007D06A90302 6 0000007E06B80302 6 00FF008106C90302 6 00FF008306DB0302 6 00FF008606EE0302 6 00FF008607000302 6 00FF0086070F0302 6 00000086071D0302 6 00FF0085072B0302 6 00FF008307390302 6 0000008307150302 6 0061008207210302 6 00FF008107380302 6 0000007F074D0302 6 0061007F074D0202 ;; Left edge, top-to-bottom 6 00000487004A0302 6 00FF0487004A0202 6 000004A300510302 6 000204A300510302 6 000204A600510302 6 00FF04A900510302 6 000004AD00520302 6 006104B200530302 6 00FF04B800550302 6 000004BD00560302 6 00FF04C300570302 6 000004CC00580302 6 000204D6005A0302 6 000004E1005C0302 6 000204EF005D0302 6 00FF0501005C0302 6 00FF0513005B0302 6 00FF052500580302 6 0000053600560302 6 00FF054600540302 6 0000055600510302 6 00FF0565004F0302 6 00FF0573004D0302 6 00000581004C0302 6 0061058F004B0302 6 00FF059C004A0302 6 000005AA004A0302 6 006105B800490302 6 000205C7004A0302 6 000005D4004A0302 6 00FF05E1004A0302 6 000205E1004A0202 ;;; Packet format looks like: ;; +-------+----------------------------------------------+ ;; | 00 | Unknown, always 00. | ;; | 01 | Pressure sensor (stylus down if 3, up if 2). | ;; | 02-03 | X-coordinate (0 - #xfff) | ;; | 04-05 | Y-coordinate (0 - #xfff) | ;; +-------+----------------------------------------------+ |# ;;; EOF