{-# LANGUAGE CPP
, NoImplicitPrelude
, DeriveDataTypeable
, BangPatterns
, ScopedTypeVariables
#-}
#if __GLASGOW_HASKELL__ >= 704
{-# LANGUAGE Unsafe #-}
#endif
#ifdef HAS_EVENT_MANAGER
{-# LANGUAGE PatternGuards #-}
#endif
#ifdef GENERICS
{-# LANGUAGE DeriveGeneric #-}
#endif
module System.USB.Base where
import Prelude ( Num, (+), (-), (*), Integral, fromIntegral, div
, Enum, fromEnum, error, String )
import Foreign.C.Types ( CUChar, CInt, CUInt )
import Foreign.C.String ( CStringLen, peekCString )
import Foreign.Marshal.Alloc ( alloca )
import Foreign.Marshal.Array ( allocaArray )
import Foreign.Marshal.Utils ( toBool, fromBool )
import Foreign.Storable ( peek, peekElemOff )
import Foreign.Ptr ( Ptr, castPtr, plusPtr, nullPtr, freeHaskellFunPtr )
import Foreign.ForeignPtr ( ForeignPtr, withForeignPtr, touchForeignPtr )
import Control.Applicative ( (<*>) )
import Control.Exception ( Exception, throwIO, bracket, bracket_
, onException, assert )
import Control.Concurrent.MVar ( MVar, newEmptyMVar, takeMVar, putMVar )
import Control.Monad ( (>>=), (=<<), return, when )
import Control.Arrow ( (&&&) )
import Data.Function ( ($), (.), on )
import Data.Data ( Data )
import Data.Typeable ( Typeable )
import Data.Maybe ( Maybe(Nothing, Just), maybe, fromMaybe )
import Data.Monoid ( Monoid, mempty
#if !MIN_VERSION_base(4,11,0)
, mappend
#endif
)
#if MIN_VERSION_base(4,9,0)
import Data.Semigroup ( Semigroup, (<>) )
#endif
import Data.List
import Data.Int ( Int )
import Data.Word ( Word8, Word16 )
import Data.Eq ( Eq, (==), (/=) )
import Data.Ord ( Ord, (<), (>) )
import Data.Bool ( Bool(False, True), not, otherwise, (&&) )
import Data.Bits ( Bits, (.&.), (.|.), setBit, testBit, shiftL, shiftR )
import Data.Version ( Version(..)
#if MIN_VERSION_base(4,8,0)
, makeVersion
#endif
)
import System.IO ( IO )
import System.IO.Unsafe ( unsafePerformIO )
import Text.Show ( Show, show )
import Text.Read ( Read )
import Text.Printf ( printf )
#if MIN_VERSION_base(4,2,0)
import Data.Functor ( fmap, (<$>) )
#else
import Control.Monad ( fmap )
import Control.Applicative ( (<$>) )
#endif
#if __GLASGOW_HASKELL__ < 700
import Prelude ( fromInteger, negate )
import Control.Monad ( (>>), fail )
#endif
import qualified Data.Foldable as Foldable ( forM_ )
import qualified Foreign.Concurrent as FC ( newForeignPtr )
import qualified Data.ByteString as B ( ByteString, packCStringLen, drop, length )
import qualified Data.ByteString.Internal as BI ( createAndTrim, createAndTrim' )
import qualified Data.ByteString.Unsafe as BU ( unsafeUseAsCStringLen )
import Data.Text ( Text )
import qualified Data.Text.Encoding as TE ( decodeUtf16LE )
import Data.Vector ( Vector )
import qualified Data.Vector.Generic as VG ( convert, map )
import Bindings.Libusb
import Utils ( bits, between, genToEnum, genFromEnum, peekVector, mapPeekArray
, allocaPeek, ifM
)
import qualified Utils as V ( uncons )
#ifdef HAS_EVENT_MANAGER
import Prelude ( undefined, seq )
import Foreign.C.Types ( CShort, CChar, CLong )
import Foreign.Marshal.Alloc ( allocaBytes, mallocBytes, reallocBytes, free )
import Foreign.Marshal.Array ( peekArray0, copyArray, advancePtr )
import Foreign.Storable ( Storable, sizeOf, poke, pokeElemOff )
import Foreign.Ptr ( nullFunPtr )
#if MIN_VERSION_base(4,4,0)
import Foreign.ForeignPtr.Unsafe ( unsafeForeignPtrToPtr )
#else
import Foreign.ForeignPtr ( unsafeForeignPtrToPtr )
#endif
import Control.Monad ( mapM_, forM_, unless )
import Data.IORef ( IORef, newIORef, atomicModifyIORef, readIORef, writeIORef )
import System.Posix.Types ( Fd(Fd) )
import Control.Exception ( uninterruptibleMask_ )
import Control.Concurrent.MVar
( newMVar
, withMVar
#if MIN_VERSION_base(4,7,0)
, withMVarMasked
#endif
)
import System.IO ( hPutStrLn, stderr )
#if MIN_VERSION_base(4,8,0) && !MIN_VERSION_base(4,8,1)
import Unsafe.Coerce ( unsafeCoerce )
#endif
#if MIN_VERSION_base(4,4,0)
import GHC.Event
#else
import System.Event
#endif
( EventManager
, IOCallback
, FdKey
, registerFd, unregisterFd
, registerTimeout, unregisterTimeout
#if MIN_VERSION_base(4,7,0)
, getSystemTimerManager
#endif
#if MIN_VERSION_base(4,8,1)
, Lifetime(MultiShot)
#endif
)
import Data.IntMap ( IntMap )
import qualified Data.IntMap as IntMap ( empty, insert, updateLookupWithKey, elems )
import qualified Data.ByteString.Internal as BI ( create, memcpy, toForeignPtr )
import qualified Data.Vector.Unboxed as Unboxed ( Vector )
import qualified Data.Vector.Storable as Storable ( Vector )
import qualified Data.Vector.Generic as VG ( empty, length, sum, foldM_, unsafeFreeze)
import qualified Data.Vector.Generic.Mutable as VGM ( unsafeNew, unsafeWrite )
import qualified Poll ( toEvent )
import SystemEventManager ( getSystemEventManager )
import Utils ( pokeVector )
#include "EventConfig.h"
#define HAVE_ONE_SHOT \
(__GLASGOW_HASKELL__ >= 708 && !MIN_VERSION_base(4,8,1) && \
!(defined(darwin_HOST_OS) || defined(ios_HOST_OS)) && \
(defined(HAVE_EPOLL) || defined(HAVE_KQUEUE)))
#endif
#ifdef GENERICS
import GHC.Generics ( Generic )
#define COMMON_INSTANCES Show, Read, Eq, Data, Typeable, Generic
#else
#define COMMON_INSTANCES Show, Read, Eq, Data, Typeable
#endif
#if MIN_VERSION_base(4,3,0)
import Control.Exception ( mask, mask_ )
#else
import Control.Exception ( blocked, block, unblock )
import Data.Function ( id )
mask :: ((IO a -> IO a) -> IO b) -> IO b
mask io = do
b <- blocked
if b
then io id
else block $ io unblock
mask_ :: IO a -> IO a
mask_ = block
#endif
#if defined(HAS_EVENT_MANAGER) && !MIN_VERSION_base(4,7,0)
{-# INLINE withMVarMasked #-}
withMVarMasked :: MVar a -> (a -> IO b) -> IO b
withMVarMasked m io =
mask_ $ do
a <- takeMVar m
b <- io a `onException` putMVar m a
putMVar m a
return b
#endif
data Ctx = Ctx
{
#ifdef HAS_EVENT_MANAGER
ctxGetWait :: !(Maybe Wait),
#endif
getCtxFrgnPtr :: !(ForeignPtr C'libusb_context)
} deriving Typeable
instance Eq Ctx where (==) = (==) `on` getCtxFrgnPtr
withCtxPtr :: Ctx -> (Ptr C'libusb_context -> IO a) -> IO a
withCtxPtr = withForeignPtr . getCtxFrgnPtr
libusb_init :: IO (Ptr C'libusb_context)
libusb_init = alloca $ \ctxPtrPtr -> do
handleUSBException $ c'libusb_init ctxPtrPtr
peek ctxPtrPtr
newCtxNoEventManager :: (ForeignPtr C'libusb_context -> Ctx) -> IO Ctx
newCtxNoEventManager ctx = mask_ $ do
ctxPtr <- libusb_init
ctx <$> FC.newForeignPtr ctxPtr
(c'libusb_exit ctxPtr)
#ifndef HAS_EVENT_MANAGER
newCtx :: IO Ctx
newCtx = newCtxNoEventManager Ctx
#else
type Wait = Timeout -> Lock -> Ptr C'libusb_transfer -> IO ()
newCtx :: IO Ctx
newCtx = newCtx' $ \e -> hPutStrLn stderr $
thisModule ++ ": libusb_handle_events_timeout returned error: " ++ show e
newCtx' :: (USBException -> IO ()) -> IO Ctx
newCtx' handleError = do
mbEvtMgr <- getSystemEventManager
case mbEvtMgr of
Nothing -> newCtxNoEventManager $ Ctx Nothing
Just evtMgr -> mask_ $ do
ctxPtr <- libusb_init
zeroTimevalPtr <- mallocZeroTimeval
newCtxWithEventManager evtMgr zeroTimevalPtr ctxPtr
where
mallocZeroTimeval :: IO (Ptr C'timeval)
mallocZeroTimeval = do
p <- mallocBytes (2 * (sizeOf (undefined :: CLong)))
pokeElemOff (castPtr p) 0 (0 :: CLong)
pokeElemOff (castPtr p) 1 (0 :: CLong)
return (castPtr p)
newCtxWithEventManager :: EventManager
-> Ptr C'timeval
-> Ptr C'libusb_context
-> IO Ctx
newCtxWithEventManager evtMgr zeroTimevalPtr ctxPtr = do
finalizeRegisterPollFds <- registerPollFds
wait <- determineWait
let finalize :: IO ()
finalize = do
finalizeRegisterPollFds
free zeroTimevalPtr
c'libusb_exit ctxPtr
ctxFrgnPtr <- FC.newForeignPtr ctxPtr finalize
return Ctx{ ctxGetWait = Just wait
, getCtxFrgnPtr = ctxFrgnPtr
}
where
registerPollFds :: IO (IO ())
registerPollFds = do
fdKeyMapRef <- newIORef IntMap.empty
registerInitialPollFds fdKeyMapRef
unregisterPollFdCallbacks <- registerPollFdCallbacks fdKeyMapRef
return $ do
unregisterPollFdCallbacks
unregisterAllPollFds fdKeyMapRef
where
registerInitialPollFds :: IORef (IntMap FdKey) -> IO ()
registerInitialPollFds fdKeyMapRef = do
pollFdPtrLst <- c'libusb_get_pollfds ctxPtr
pollFdPtrs <- peekArray0 nullPtr pollFdPtrLst
forM_ pollFdPtrs $ \pollFdPtr -> do
C'libusb_pollfd fd evt <- peek pollFdPtr
register fdKeyMapRef fd evt
free pollFdPtrLst
registerPollFdCallbacks :: IORef (IntMap FdKey) -> IO (IO ())
registerPollFdCallbacks fdKeyMapRef = do
pollFdAddedFP <- mk'libusb_pollfd_added_cb pollFdAddedCallback
pollFdRemovedFP <- mk'libusb_pollfd_removed_cb pollFdRemovedCallback
c'libusb_set_pollfd_notifiers ctxPtr pollFdAddedFP pollFdRemovedFP nullPtr
return $ do
c'libusb_set_pollfd_notifiers ctxPtr nullFunPtr nullFunPtr nullPtr
freeHaskellFunPtr pollFdAddedFP
freeHaskellFunPtr pollFdRemovedFP
where
pollFdAddedCallback :: CInt -> CShort -> Ptr () -> IO ()
pollFdAddedCallback fd evt _userData = mask_ $ do
register fdKeyMapRef fd evt
pollFdRemovedCallback :: CInt -> Ptr () -> IO ()
pollFdRemovedCallback fd _userData = mask_ $ do
(newFdKeyMap, fdKey) <- atomicModifyIORef fdKeyMapRef $ \fdKeyMap ->
let (Just fdKey, newFdKeyMap) =
IntMap.updateLookupWithKey (\_ _ -> Nothing)
(fromIntegral fd)
fdKeyMap
in (newFdKeyMap, (newFdKeyMap, fdKey))
newFdKeyMap `seq` unregisterFd evtMgr fdKey
unregisterAllPollFds :: IORef (IntMap FdKey) -> IO ()
unregisterAllPollFds fdKeyMapRef =
readIORef fdKeyMapRef >>=
mapM_ (unregisterFd evtMgr) . IntMap.elems
register :: IORef (IntMap FdKey) -> CInt -> CShort -> IO ()
register fdKeyMapRef fd evt = registerAndInsert
where
registerAndInsert :: IO ()
registerAndInsert = do
fdKey <- registerFd evtMgr callback (Fd fd) (Poll.toEvent evt)
#if MIN_VERSION_base(4,8,1)
MultiShot
#elif MIN_VERSION_base(4,8,0)
(unsafeCoerce False)
#endif
newFdKeyMap <- atomicModifyIORef fdKeyMapRef $ \fdKeyMap ->
let newFdKeyMap = IntMap.insert (fromIntegral fd) fdKey fdKeyMap
in (newFdKeyMap, newFdKeyMap)
newFdKeyMap `seq` return ()
callback :: IOCallback
callback _fdKey _ev = do
#if HAVE_ONE_SHOT
registerAndInsert
#endif
handleEvents
handleEvents :: IO ()
handleEvents = do
err <- c'libusb_handle_events_timeout ctxPtr zeroTimevalPtr
when (err /= c'LIBUSB_SUCCESS) $
if err == c'LIBUSB_ERROR_INTERRUPTED
then handleEvents
else handleError $ convertUSBException err
determineWait :: IO Wait
determineWait = do
timeoutsHandled <- toBool <$> c'libusb_pollfds_handle_timeouts ctxPtr
if timeoutsHandled
then return $ \_timeout -> autoTimeout
else do
#if MIN_VERSION_base(4,7,0)
timerMgr <- getSystemTimerManager
#else
let timerMgr = evtMgr
#endif
return $ manualTimeout timerMgr
where
autoTimeout lock transPtr =
acquire lock
`onException`
(uninterruptibleMask_ $ do
handleUSBException $ c'libusb_cancel_transfer transPtr
acquire lock)
manualTimeout timerMgr timeout lock transPtr
| timeout == noTimeout = autoTimeout lock transPtr
| otherwise = do
timeoutKey <- registerTimeout timerMgr (timeout * 1000) handleEvents
acquire lock
`onException`
(uninterruptibleMask_ $ do
unregisterTimeout timerMgr timeoutKey
handleUSBException $ c'libusb_cancel_transfer transPtr
acquire lock)
getWait :: DeviceHandle -> Maybe Wait
getWait = ctxGetWait . getCtx . getDevice
requireWait :: DeviceHandle -> String -> IO Wait
requireWait devHndl funName =
case getWait devHndl of
Nothing -> moduleError $ funName ++
" is only supported when using the threaded runtime. " ++
"Please build your program with -threaded."
Just wait -> return wait
#endif
setDebug :: Ctx -> Verbosity -> IO ()
setDebug ctx verbosity = withCtxPtr ctx $ \ctxPtr ->
c'libusb_set_debug ctxPtr $
marshallVerbosity verbosity
data Verbosity =
PrintNothing
| PrintErrors
| PrintWarnings
| PrintInfo
| PrintDebug
deriving (Enum, Ord, COMMON_INSTANCES)
marshallVerbosity :: Verbosity -> CInt
marshallVerbosity PrintNothing = c'LIBUSB_LOG_LEVEL_NONE
marshallVerbosity PrintErrors = c'LIBUSB_LOG_LEVEL_ERROR
marshallVerbosity PrintWarnings = c'LIBUSB_LOG_LEVEL_WARNING
marshallVerbosity PrintInfo = c'LIBUSB_LOG_LEVEL_INFO
marshallVerbosity PrintDebug = c'LIBUSB_LOG_LEVEL_DEBUG
data LibusbVersion = LibusbVersion
{ major :: Word16
, minor :: Word16
, micro :: Word16
, nano :: Word16
, rc :: String
} deriving (COMMON_INSTANCES)
libusbVersion :: LibusbVersion
libusbVersion = unsafePerformIO $ do
ptr <- c'libusb_get_version
LibusbVersion <$> peek (p'libusb_version'major ptr)
<*> peek (p'libusb_version'minor ptr)
<*> peek (p'libusb_version'micro ptr)
<*> peek (p'libusb_version'nano ptr)
<*> (peek (p'libusb_version'rc ptr) >>= peekCString)
toVersion :: LibusbVersion -> Version
toVersion (LibusbVersion maj min mic nan _rcTag) =
#if MIN_VERSION_base(4,8,0)
makeVersion branch
#else
Version { versionBranch = branch
, versionTags = if null _rcTag then [] else [_rcTag]
}
#endif
where
branch :: [Int]
branch = map fromIntegral [maj, min, mic, nan]
data Capability = HasCapability
| HasHotplug
| HasHidAccess
| SupportsDetachKernelDriver
deriving (Enum, Ord, COMMON_INSTANCES)
marshallCapability :: Capability -> C'libusb_capability
marshallCapability HasCapability = c'LIBUSB_CAP_HAS_CAPABILITY
marshallCapability HasHotplug = c'LIBUSB_CAP_HAS_HOTPLUG
marshallCapability HasHidAccess = c'LIBUSB_CAP_HAS_HID_ACCESS
marshallCapability SupportsDetachKernelDriver = c'LIBUSB_CAP_SUPPORTS_DETACH_KERNEL_DRIVER
hasCapability :: Ctx -> Capability -> Bool
hasCapability _ctx = toBool . c'libusb_has_capability . marshallCapability
data Device = Device
{ getCtx :: !Ctx
, getDevFrgnPtr :: !(ForeignPtr C'libusb_device)
} deriving Typeable
instance Eq Device where (==) = (==) `on` getDevFrgnPtr
instance Show Device where
show d = printf "Bus %03d Device %03d" (busNumber d) (deviceAddress d)
withDevicePtr :: Device -> (Ptr C'libusb_device -> IO a) -> IO a
withDevicePtr (Device ctx devFP ) f = do
x <- withForeignPtr devFP f
touchForeignPtr $ getCtxFrgnPtr ctx
return x
mkDev :: Ctx -> Ptr C'libusb_device -> IO Device
mkDev ctx devPtr =
Device ctx <$> FC.newForeignPtr devPtr (c'libusb_unref_device devPtr)
getDevices :: Ctx -> IO (Vector Device)
getDevices ctx =
withCtxPtr ctx $ \ctxPtr ->
alloca $ \devPtrArrayPtr -> mask $ \restore -> do
numDevs <- checkUSBException $ c'libusb_get_device_list ctxPtr
devPtrArrayPtr
devPtrArray <- peek devPtrArrayPtr
let freeDevPtrArray = c'libusb_free_device_list devPtrArray 0
devs <- restore (mapPeekArray (mkDev ctx) numDevs devPtrArray)
`onException` freeDevPtrArray
freeDevPtrArray
return devs
newtype HotplugEvent = HotplugEvent {unHotplugEvent :: C'libusb_hotplug_event}
#if MIN_VERSION_base(4,9,0)
instance Semigroup HotplugEvent where
ev1 <> ev2 = HotplugEvent $ unHotplugEvent ev1 .|. unHotplugEvent ev2
#endif
instance Monoid HotplugEvent where
mempty = HotplugEvent 0
#if !MIN_VERSION_base(4,11,0)
ev1 `mappend` ev2 = HotplugEvent $ unHotplugEvent ev1 .|. unHotplugEvent ev2
#endif
deviceArrived :: HotplugEvent
deviceArrived = HotplugEvent c'LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED
deviceLeft :: HotplugEvent
deviceLeft = HotplugEvent c'LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT
matchDeviceArrived :: HotplugEvent -> Bool
matchDeviceArrived = isEvent c'LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED
matchDeviceLeft :: HotplugEvent -> Bool
matchDeviceLeft = isEvent c'LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT
isEvent :: C'libusb_hotplug_event -> (HotplugEvent -> Bool)
isEvent c'ev = \ev -> unHotplugEvent ev .&. c'ev == c'ev
newtype HotplugFlag = HotplugFlag {unHotplugFlag :: C'libusb_hotplug_flag}
#if MIN_VERSION_base(4,9,0)
instance Semigroup HotplugFlag where
flg1 <> flg2 = HotplugFlag $ unHotplugFlag flg1 .|. unHotplugFlag flg2
#endif
instance Monoid HotplugFlag where
mempty = HotplugFlag 0
#if !MIN_VERSION_base(4,11,0)
flg1 `mappend` flg2 = HotplugFlag $ unHotplugFlag flg1 .|. unHotplugFlag flg2
#endif
enumerate :: HotplugFlag
enumerate = HotplugFlag c'LIBUSB_HOTPLUG_ENUMERATE
type HotplugCallback = Device -> HotplugEvent -> IO CallbackRegistrationStatus
data CallbackRegistrationStatus = KeepCallbackRegistered
| DeregisterThisCallback
deriving (COMMON_INSTANCES)
data HotplugCallbackHandle = HotplugCallbackHandle
Ctx
(MVar (Maybe C'libusb_hotplug_callback_fn))
C'libusb_hotplug_callback_handle
registerHotplugCallback
:: Ctx
-> HotplugEvent
-> HotplugFlag
-> Maybe VendorId
-> Maybe ProductId
-> Maybe Word8
-> HotplugCallback
-> IO HotplugCallbackHandle
registerHotplugCallback ctx
hotplugEvent
hotplugFlag
mbVendorId
mbProductId
mbDevClass
hotplugCallback = do
(mbCbFnPtrMv :: MVar (Maybe C'libusb_hotplug_callback_fn)) <- newEmptyMVar
let cb :: Ptr C'libusb_context
-> Ptr C'libusb_device
-> C'libusb_hotplug_event
-> Ptr ()
-> IO CInt
cb _ctxPtr devPtr ev _userData = do
dev <- mask_ $ c'libusb_ref_device devPtr >>= mkDev ctx
status <- hotplugCallback dev (HotplugEvent ev)
case status of
KeepCallbackRegistered -> return 0
DeregisterThisCallback -> mask_ $ do
mbCbFnPtr <- takeMVar mbCbFnPtrMv
Foldable.forM_ mbCbFnPtr freeHaskellFunPtr
putMVar mbCbFnPtrMv Nothing
return 1
withCtxPtr ctx $ \ctxPtr ->
alloca $ \(hotplugCallbackHandlePtr :: Ptr C'libusb_hotplug_callback_handle) ->
mask_ $ do
cbFnPtr <- mk'libusb_hotplug_callback_fn cb
putMVar mbCbFnPtrMv $ Just cbFnPtr
handleUSBException (c'libusb_hotplug_register_callback
ctxPtr
(unHotplugEvent hotplugEvent)
(unHotplugFlag hotplugFlag)
(unmarshallMatch mbVendorId)
(unmarshallMatch mbProductId)
(unmarshallMatch mbDevClass)
cbFnPtr
nullPtr
hotplugCallbackHandlePtr)
`onException` freeHaskellFunPtr cbFnPtr
HotplugCallbackHandle ctx mbCbFnPtrMv <$>
peek hotplugCallbackHandlePtr
where
unmarshallMatch :: Integral a => Maybe a -> CInt
unmarshallMatch = maybe c'LIBUSB_HOTPLUG_MATCH_ANY fromIntegral
deregisterHotplugCallback :: HotplugCallbackHandle -> IO ()
deregisterHotplugCallback (HotplugCallbackHandle ctx mbCbFnPtrMv handle) = mask_ $ do
mbCbFnPtr <- takeMVar mbCbFnPtrMv
Foldable.forM_ mbCbFnPtr $ \cbFnPtr ->
withCtxPtr ctx $ \ctxPtr -> do
c'libusb_hotplug_deregister_callback ctxPtr handle
`onException` putMVar mbCbFnPtrMv mbCbFnPtr
freeHaskellFunPtr cbFnPtr
putMVar mbCbFnPtrMv Nothing
busNumber :: Device -> Word8
busNumber dev = unsafePerformIO $ withDevicePtr dev c'libusb_get_bus_number
portNumber :: Device -> Word8
portNumber dev = unsafePerformIO $ withDevicePtr dev c'libusb_get_port_number
portNumbers :: Device
-> Int
-> Maybe (Vector Word8)
portNumbers dev m = unsafePerformIO $
withDevicePtr dev $ \devPtr ->
allocaArray m $ \ptr -> do
n <- c'libusb_get_port_numbers devPtr ptr (fromIntegral m)
if n == c'LIBUSB_ERROR_OVERFLOW
then return Nothing
else Just . VG.convert <$> peekVector (fromIntegral n) ptr
deviceAddress :: Device -> Word8
deviceAddress dev = unsafePerformIO $ withDevicePtr dev c'libusb_get_device_address
deviceSpeed :: Device -> Maybe Speed
deviceSpeed dev = unsafePerformIO $ withDevicePtr dev $ \devPtr ->
unmarshallSpeed <$> c'libusb_get_device_speed devPtr
unmarshallSpeed :: CInt -> Maybe Speed
unmarshallSpeed speed | speed == c'LIBUSB_SPEED_UNKNOWN = Nothing
| otherwise = Just $ genToEnum (speed - 1)
data Speed = LowSpeed
| FullSpeed
| HighSpeed
| SuperSpeed
deriving (Enum, COMMON_INSTANCES)
data DeviceHandle = DeviceHandle
{ getDevice :: !Device
, getDevHndlPtr :: !(Ptr C'libusb_device_handle)
} deriving Typeable
instance Eq DeviceHandle where (==) = (==) `on` getDevHndlPtr
instance Show DeviceHandle where
show devHndl = "{USB device handle to: " ++ show (getDevice devHndl) ++ "}"
withDevHndlPtr :: DeviceHandle -> (Ptr C'libusb_device_handle -> IO a) -> IO a
withDevHndlPtr (DeviceHandle (Device ctx devFrgnPtr) devHndlPtr) f = do
x <- f devHndlPtr
touchForeignPtr devFrgnPtr
touchForeignPtr $ getCtxFrgnPtr ctx
return x
openDevice :: Device -> IO DeviceHandle
openDevice dev = withDevicePtr dev $ \devPtr ->
alloca $ \devHndlPtrPtr -> do
handleUSBException $ c'libusb_open devPtr devHndlPtrPtr
DeviceHandle dev <$> peek devHndlPtrPtr
closeDevice :: DeviceHandle -> IO ()
closeDevice devHndl = withDevHndlPtr devHndl c'libusb_close
withDeviceHandle :: Device -> (DeviceHandle -> IO a) -> IO a
withDeviceHandle dev = bracket (openDevice dev) closeDevice
type ConfigValue = Word8
getConfig :: DeviceHandle -> IO (Maybe ConfigValue)
getConfig devHndl =
alloca $ \configPtr -> do
withDevHndlPtr devHndl $ \devHndlPtr ->
handleUSBException $ c'libusb_get_configuration devHndlPtr configPtr
unmarshal <$> peek configPtr
where
unmarshal 0 = Nothing
unmarshal n = Just $ fromIntegral n
setConfig :: DeviceHandle -> Maybe ConfigValue -> IO ()
setConfig devHndl config =
withDevHndlPtr devHndl $ \devHndlPtr ->
handleUSBException $ c'libusb_set_configuration devHndlPtr $
marshal config
where
marshal = maybe (-1) fromIntegral
type InterfaceNumber = Word8
claimInterface :: DeviceHandle -> InterfaceNumber -> IO ()
claimInterface devHndl ifNum =
withDevHndlPtr devHndl $ \devHndlPtr ->
handleUSBException $ c'libusb_claim_interface devHndlPtr
(fromIntegral ifNum)
releaseInterface :: DeviceHandle -> InterfaceNumber -> IO ()
releaseInterface devHndl ifNum =
withDevHndlPtr devHndl $ \devHndlPtr ->
handleUSBException $ c'libusb_release_interface devHndlPtr
(fromIntegral ifNum)
withClaimedInterface :: DeviceHandle -> InterfaceNumber -> IO a -> IO a
withClaimedInterface devHndl ifNum = bracket_ (claimInterface devHndl ifNum)
(releaseInterface devHndl ifNum)
type InterfaceAltSetting = Word8
setInterfaceAltSetting :: DeviceHandle
-> InterfaceNumber
-> InterfaceAltSetting
-> IO ()
setInterfaceAltSetting devHndl ifNum alternateSetting =
withDevHndlPtr devHndl $ \devHndlPtr ->
handleUSBException $
c'libusb_set_interface_alt_setting devHndlPtr
(fromIntegral ifNum)
(fromIntegral alternateSetting)
clearHalt :: DeviceHandle -> EndpointAddress -> IO ()
clearHalt devHndl endpointAddr =
withDevHndlPtr devHndl $ \devHndlPtr ->
handleUSBException $
c'libusb_clear_halt devHndlPtr (marshalEndpointAddress endpointAddr)
resetDevice :: DeviceHandle -> IO ()
resetDevice devHndl = withDevHndlPtr devHndl $
handleUSBException . c'libusb_reset_device
setAutoDetachKernelDriver :: DeviceHandle -> Bool -> IO ()
setAutoDetachKernelDriver devHndl enable =
withDevHndlPtr devHndl $ \devHndlPtr ->
handleUSBException $ c'libusb_set_auto_detach_kernel_driver
devHndlPtr (fromBool enable)
kernelDriverActive :: DeviceHandle -> InterfaceNumber -> IO Bool
kernelDriverActive devHndl ifNum =
withDevHndlPtr devHndl $ \devHndlPtr -> do
r <- c'libusb_kernel_driver_active devHndlPtr (fromIntegral ifNum)
case r of
0 -> return False
1 -> return True
_ -> throwIO $ convertUSBException r
detachKernelDriver :: DeviceHandle -> InterfaceNumber -> IO ()
detachKernelDriver devHndl ifNum =
withDevHndlPtr devHndl $ \devHndlPtr ->
handleUSBException $ c'libusb_detach_kernel_driver devHndlPtr
(fromIntegral ifNum)
attachKernelDriver :: DeviceHandle -> InterfaceNumber -> IO ()
attachKernelDriver devHndl ifNum =
withDevHndlPtr devHndl $ \devHndlPtr ->
handleUSBException $ c'libusb_attach_kernel_driver devHndlPtr
(fromIntegral ifNum)
withDetachedKernelDriver :: DeviceHandle -> InterfaceNumber -> IO a -> IO a
withDetachedKernelDriver devHndl ifNum action =
ifM (kernelDriverActive devHndl ifNum)
(bracket_ (detachKernelDriver devHndl ifNum)
(attachKernelDriver devHndl ifNum)
action)
action
data DeviceDesc = DeviceDesc
{
deviceUSBSpecReleaseNumber :: !ReleaseNumber
, deviceClass :: !Word8
, deviceSubClass :: !Word8
, deviceProtocol :: !Word8
, deviceMaxPacketSize0 :: !Word8
, deviceVendorId :: !VendorId
, deviceProductId :: !ProductId
, deviceReleaseNumber :: !ReleaseNumber
, deviceManufacturerStrIx :: !(Maybe StrIx)
, deviceProductStrIx :: !(Maybe StrIx)
, deviceSerialNumberStrIx :: !(Maybe StrIx)
, deviceNumConfigs :: !Word8
} deriving (COMMON_INSTANCES)
type ReleaseNumber = (Int, Int, Int, Int)
type VendorId = Word16
type ProductId = Word16
data ConfigDesc = ConfigDesc
{
configValue :: !ConfigValue
, configStrIx :: !(Maybe StrIx)
, configAttribs :: !ConfigAttribs
, configMaxPower :: !Word8
, configInterfaces :: !(Vector Interface)
, configExtra :: !B.ByteString
} deriving (COMMON_INSTANCES)
type ConfigAttribs = DeviceStatus
data DeviceStatus = DeviceStatus
{ remoteWakeup :: !Bool
, selfPowered :: !Bool
} deriving (COMMON_INSTANCES)
type Interface = Vector InterfaceDesc
data InterfaceDesc = InterfaceDesc
{
interfaceNumber :: !InterfaceNumber
, interfaceAltSetting :: !InterfaceAltSetting
, interfaceClass :: !Word8
, interfaceSubClass :: !Word8
, interfaceProtocol :: !Word8
, interfaceStrIx :: !(Maybe StrIx)
, interfaceEndpoints :: !(Vector EndpointDesc)
, interfaceExtra :: !B.ByteString
} deriving (COMMON_INSTANCES)
data EndpointDesc = EndpointDesc
{
endpointAddress :: !EndpointAddress
, endpointAttribs :: !EndpointAttribs
, endpointMaxPacketSize :: !MaxPacketSize
, endpointInterval :: !Word8
, endpointRefresh :: !Word8
, endpointSynchAddress :: !Word8
, endpointExtra :: !B.ByteString
} deriving (COMMON_INSTANCES)
data EndpointAddress = EndpointAddress
{ endpointNumber :: !Int
, transferDirection :: !TransferDirection
} deriving (COMMON_INSTANCES)
data TransferDirection = Out
| In
deriving (Enum, COMMON_INSTANCES)
type EndpointAttribs = TransferType
data TransferType =
Control
| Isochronous !Synchronization !Usage
| Bulk
| Interrupt
deriving (COMMON_INSTANCES)
data Synchronization =
NoSynchronization
| Asynchronous
| Adaptive
| Synchronous
deriving (Enum, COMMON_INSTANCES)
data Usage = Data
| Feedback
| Implicit
deriving (Enum, COMMON_INSTANCES)
data MaxPacketSize = MaxPacketSize
{ maxPacketSize :: !Size
, transactionOpportunities :: !TransactionOpportunities
} deriving (COMMON_INSTANCES)
data TransactionOpportunities = Zero
| One
| Two
deriving (Enum, Ord, COMMON_INSTANCES)
maxIsoPacketSize :: EndpointDesc -> Size
maxIsoPacketSize epDesc | isochronousOrInterrupt = mps * (1 + fromEnum to)
| otherwise = mps
where
MaxPacketSize mps to = endpointMaxPacketSize epDesc
isochronousOrInterrupt = case endpointAttribs epDesc of
Isochronous _ _ -> True
Interrupt -> True
_ -> False
getDeviceDesc :: Device -> IO DeviceDesc
getDeviceDesc dev =
withDevicePtr dev $ \devPtr ->
convertDeviceDesc <$>
allocaPeek (handleUSBException . c'libusb_get_device_descriptor devPtr)
convertDeviceDesc :: C'libusb_device_descriptor -> DeviceDesc
convertDeviceDesc d = DeviceDesc
{ deviceUSBSpecReleaseNumber = unmarshalReleaseNumber $
c'libusb_device_descriptor'bcdUSB d
, deviceClass = c'libusb_device_descriptor'bDeviceClass d
, deviceSubClass = c'libusb_device_descriptor'bDeviceSubClass d
, deviceProtocol = c'libusb_device_descriptor'bDeviceProtocol d
, deviceMaxPacketSize0 = c'libusb_device_descriptor'bMaxPacketSize0 d
, deviceVendorId = c'libusb_device_descriptor'idVendor d
, deviceProductId = c'libusb_device_descriptor'idProduct d
, deviceReleaseNumber = unmarshalReleaseNumber $
c'libusb_device_descriptor'bcdDevice d
, deviceManufacturerStrIx = unmarshalStrIx $
c'libusb_device_descriptor'iManufacturer d
, deviceProductStrIx = unmarshalStrIx $
c'libusb_device_descriptor'iProduct d
, deviceSerialNumberStrIx = unmarshalStrIx $
c'libusb_device_descriptor'iSerialNumber d
, deviceNumConfigs = c'libusb_device_descriptor'bNumConfigurations d
}
unmarshalReleaseNumber :: Word16 -> ReleaseNumber
unmarshalReleaseNumber abcd = (a, b, c, d)
where
a = fromIntegral $ abcd `shiftR` 12
b = fromIntegral $ (abcd `shiftL` 4) `shiftR` 12
c = fromIntegral $ (abcd `shiftL` 8) `shiftR` 12
d = fromIntegral $ (abcd `shiftL` 12) `shiftR` 12
unmarshalStrIx :: Word8 -> Maybe StrIx
unmarshalStrIx 0 = Nothing
unmarshalStrIx strIx = Just strIx
getConfigDesc :: Device -> Word8 -> IO ConfigDesc
getConfigDesc dev ix = withDevicePtr dev $ \devPtr ->
bracket (allocaPeek $ handleUSBException
. c'libusb_get_config_descriptor devPtr ix)
c'libusb_free_config_descriptor
((convertConfigDesc =<<) . peek)
convertConfigDesc :: C'libusb_config_descriptor -> IO ConfigDesc
convertConfigDesc c = do
interfaces <- mapPeekArray convertInterface
(fromIntegral $ c'libusb_config_descriptor'bNumInterfaces c)
(c'libusb_config_descriptor'interface c)
extra <- getExtra (c'libusb_config_descriptor'extra c)
(c'libusb_config_descriptor'extra_length c)
return ConfigDesc
{ configValue = c'libusb_config_descriptor'bConfigurationValue c
, configStrIx = unmarshalStrIx $
c'libusb_config_descriptor'iConfiguration c
, configAttribs = unmarshalConfigAttribs $
c'libusb_config_descriptor'bmAttributes c
, configMaxPower = c'libusb_config_descriptor'MaxPower c
, configInterfaces = interfaces
, configExtra = extra
}
unmarshalConfigAttribs :: Word8 -> ConfigAttribs
unmarshalConfigAttribs a = DeviceStatus { remoteWakeup = testBit a 5
, selfPowered = testBit a 6
}
getExtra :: Ptr CUChar -> CInt -> IO B.ByteString
getExtra extra extraLength = B.packCStringLen ( castPtr extra
, fromIntegral extraLength
)
convertInterface :: C'libusb_interface -> IO Interface
convertInterface i =
mapPeekArray convertInterfaceDesc
(fromIntegral $ c'libusb_interface'num_altsetting i)
(c'libusb_interface'altsetting i)
convertInterfaceDesc :: C'libusb_interface_descriptor -> IO InterfaceDesc
convertInterfaceDesc i = do
endpoints <- mapPeekArray convertEndpointDesc
(fromIntegral $ c'libusb_interface_descriptor'bNumEndpoints i)
(c'libusb_interface_descriptor'endpoint i)
extra <- getExtra (c'libusb_interface_descriptor'extra i)
(c'libusb_interface_descriptor'extra_length i)
return InterfaceDesc
{ interfaceNumber = c'libusb_interface_descriptor'bInterfaceNumber i
, interfaceAltSetting = c'libusb_interface_descriptor'bAlternateSetting i
, interfaceClass = c'libusb_interface_descriptor'bInterfaceClass i
, interfaceSubClass = c'libusb_interface_descriptor'bInterfaceSubClass i
, interfaceStrIx = unmarshalStrIx $
c'libusb_interface_descriptor'iInterface i
, interfaceProtocol = c'libusb_interface_descriptor'bInterfaceProtocol i
, interfaceEndpoints = endpoints
, interfaceExtra = extra
}
convertEndpointDesc :: C'libusb_endpoint_descriptor -> IO EndpointDesc
convertEndpointDesc e = do
extra <- getExtra (c'libusb_endpoint_descriptor'extra e)
(c'libusb_endpoint_descriptor'extra_length e)
return EndpointDesc
{ endpointAddress = unmarshalEndpointAddress $
c'libusb_endpoint_descriptor'bEndpointAddress e
, endpointAttribs = unmarshalEndpointAttribs $
c'libusb_endpoint_descriptor'bmAttributes e
, endpointMaxPacketSize = unmarshalMaxPacketSize $
c'libusb_endpoint_descriptor'wMaxPacketSize e
, endpointInterval = c'libusb_endpoint_descriptor'bInterval e
, endpointRefresh = c'libusb_endpoint_descriptor'bRefresh e
, endpointSynchAddress = c'libusb_endpoint_descriptor'bSynchAddress e
, endpointExtra = extra
}
unmarshalEndpointAddress :: Word8 -> EndpointAddress
unmarshalEndpointAddress a =
EndpointAddress { endpointNumber = fromIntegral $ bits 0 3 a
, transferDirection = if testBit a 7 then In else Out
}
marshalEndpointAddress :: (Bits a, Num a) => EndpointAddress -> a
marshalEndpointAddress (EndpointAddress num transDir) =
assert (between num 0 15) $ let n = fromIntegral num
in case transDir of
Out -> n
In -> setBit n 7
unmarshalEndpointAttribs :: Word8 -> EndpointAttribs
unmarshalEndpointAttribs a =
case bits 0 1 a of
0 -> Control
1 -> Isochronous (genToEnum $ bits 2 3 a)
(genToEnum $ bits 4 5 a)
2 -> Bulk
3 -> Interrupt
_ -> moduleError "unmarshalEndpointAttribs: this can't happen!"
unmarshalMaxPacketSize :: Word16 -> MaxPacketSize
unmarshalMaxPacketSize m =
MaxPacketSize
{ maxPacketSize = fromIntegral $ bits 0 10 m
, transactionOpportunities = genToEnum $ bits 11 12 m
}
strDescHeaderSize :: Size
strDescHeaderSize = 2
charSize :: Size
charSize = 2
getLanguages :: DeviceHandle -> IO (Vector LangId)
getLanguages devHndl = allocaArray maxSize $ \dataPtr -> do
let strIx = 0
langId = 0
reportedSize <- retrieveStrDesc devHndl strIx langId maxSize dataPtr
let strSize = (reportedSize - strDescHeaderSize) `div` charSize
strPtr = castPtr $ dataPtr `plusPtr` strDescHeaderSize
(VG.map unmarshalLangId . VG.convert) <$> peekVector strSize strPtr
where
maxSize = 255
retrieveStrDesc :: DeviceHandle
-> StrIx
-> Word16
-> Size
-> Ptr CUChar
-> IO Size
retrieveStrDesc devHndl strIx langId maxSize dataPtr = do
actualSize <- withDevHndlPtr devHndl $ \devHndlPtr ->
checkUSBException $ c'libusb_get_string_descriptor
devHndlPtr
strIx
langId
dataPtr
(fromIntegral maxSize)
when (actualSize < strDescHeaderSize) $
throwIO $ IOException "Incomplete header"
reportedSize <- peek dataPtr
when (reportedSize > fromIntegral actualSize) $
throwIO $ IOException "Not enough space to hold data"
descType <- peekElemOff dataPtr 1
when (descType /= c'LIBUSB_DT_STRING) $
throwIO $ IOException "Invalid header"
return $ fromIntegral reportedSize
type LangId = (PrimaryLangId, SubLangId)
type PrimaryLangId = Word16
type SubLangId = Word16
unmarshalLangId :: Word16 -> LangId
unmarshalLangId = bits 0 9 &&& bits 10 15
marshalLangId :: LangId -> Word16
marshalLangId (p, s) = p .|. s `shiftL`10
type StrIx = Word8
getStrDesc :: DeviceHandle
-> StrIx
-> LangId
-> Int
-> IO Text
getStrDesc devHndl strIx langId nrOfChars = assert (strIx /= 0) $
fmap decode $ BI.createAndTrim size $ write . castPtr
where
write = retrieveStrDesc devHndl strIx (marshalLangId langId) size
size = strDescHeaderSize + nrOfChars * charSize
decode = TE.decodeUtf16LE . B.drop strDescHeaderSize
getStrDescFirstLang :: DeviceHandle
-> StrIx
-> Int
-> IO Text
getStrDescFirstLang devHndl strIx nrOfChars = do
langIds <- getLanguages devHndl
case V.uncons langIds of
Nothing -> throwIO $ IOException "Zero languages"
Just (langId, _) -> getStrDesc devHndl strIx langId nrOfChars
type ReadAction = Size -> Timeout -> IO (B.ByteString, Status)
type ReadExactAction = Size -> Timeout -> IO B.ByteString
type WriteAction = B.ByteString -> Timeout -> IO (Size, Status)
type WriteExactAction = B.ByteString -> Timeout -> IO ()
type Size = Int
type Timeout = Int
noTimeout :: Timeout
noTimeout = 0
data Status = Completed
| TimedOut
deriving (COMMON_INSTANCES)
data ControlSetup = ControlSetup
{ controlSetupRequestType :: !RequestType
, controlSetupRecipient :: !Recipient
, controlSetupRequest :: !Request
, controlSetupValue :: !Value
, controlSetupIndex :: !Index
} deriving (COMMON_INSTANCES)
marshallControlSetup :: ControlSetup ->
TransferDirection ->
Size -> C'libusb_control_setup
marshallControlSetup (ControlSetup reqType reqRecipient request value index)
reqDir size =
C'libusb_control_setup requestType
request value index
(fromIntegral size)
where
requestType = marshalRequestType reqType reqRecipient reqDir
data RequestType =
Standard
| Class
| Vendor
deriving (Enum, COMMON_INSTANCES)
data Recipient = ToDevice
| ToInterface
| ToEndpoint
| ToOther
deriving (Enum, COMMON_INSTANCES)
type Request = Word8
type Value = Word16
type Index = Word16
marshalRequestType :: RequestType -> Recipient -> TransferDirection -> Word8
marshalRequestType t r d = genFromEnum d `shiftL` 7 .|.
genFromEnum t `shiftL` 5 .|.
genFromEnum r
control :: DeviceHandle -> ControlSetup -> Timeout -> IO ()
control devHndl ctrlSetup timeout = do
(_, status) <- doControl
when (status == TimedOut) $ throwIO TimeoutException
where
doControl
#ifdef HAS_EVENT_MANAGER
| Just wait <- getWait devHndl =
allocaBytes controlSetupSize $ \bufferPtr -> do
poke bufferPtr $ marshallControlSetup ctrlSetup Out 0
transferAsync wait
c'LIBUSB_TRANSFER_TYPE_CONTROL
devHndl
controlEndpoint
timeout
(bufferPtr, controlSetupSize)
#endif
| otherwise = controlTransferSync devHndl ctrlSetup Out timeout (nullPtr, 0)
readControl :: DeviceHandle -> ControlSetup -> ReadAction
readControl devHndl ctrlSetup size timeout
#ifdef HAS_EVENT_MANAGER
| Just wait <- getWait devHndl = do
let totalSize = controlSetupSize + size
allocaBytes totalSize $ \bufferPtr -> do
poke bufferPtr $ marshallControlSetup ctrlSetup In size
(transferred, status) <- transferAsync wait
c'LIBUSB_TRANSFER_TYPE_CONTROL
devHndl controlEndpoint
timeout
(bufferPtr, totalSize)
bs <- BI.create transferred $ \dataPtr ->
copyArray dataPtr (bufferPtr `plusPtr` controlSetupSize) transferred
return (bs, status)
#endif
| otherwise = createAndTrimNoOffset size $ \dataPtr ->
controlTransferSync devHndl ctrlSetup In timeout (dataPtr, size)
readControlExact :: DeviceHandle -> ControlSetup -> ReadExactAction
readControlExact devHndl ctrlSetup size timeout = do
(bs, _) <- readControl devHndl ctrlSetup size timeout
if B.length bs /= size
then throwIO incompleteReadException
else return bs
writeControl :: DeviceHandle -> ControlSetup -> WriteAction
writeControl devHndl ctrlSetup input timeout
#ifdef HAS_EVENT_MANAGER
| Just wait <- getWait devHndl =
BU.unsafeUseAsCStringLen input $ \(dataPtr, size) -> do
let totalSize = controlSetupSize + size
allocaBytes totalSize $ \bufferPtr -> do
poke bufferPtr $ marshallControlSetup ctrlSetup Out size
copyArray (bufferPtr `plusPtr` controlSetupSize) dataPtr size
transferAsync wait
c'LIBUSB_TRANSFER_TYPE_CONTROL
devHndl controlEndpoint
timeout
(bufferPtr, totalSize)
#endif
| otherwise = BU.unsafeUseAsCStringLen input $
controlTransferSync devHndl ctrlSetup Out timeout
writeControlExact :: DeviceHandle -> ControlSetup -> WriteExactAction
writeControlExact devHndl ctrlSetup input timeout = do
(transferred, _) <- writeControl devHndl ctrlSetup input timeout
when (transferred /= B.length input) $ throwIO incompleteWriteException
#ifdef HAS_EVENT_MANAGER
controlSetupSize :: Size
controlSetupSize = sizeOf (undefined :: C'libusb_control_setup)
controlEndpoint :: CUChar
controlEndpoint = 0
#endif
controlTransferSync :: DeviceHandle
-> ControlSetup
-> TransferDirection
-> Timeout
-> (Ptr byte, Size)
-> IO (Size, Status)
controlTransferSync devHndl
(ControlSetup reqType reqRecipient request value index)
reqDir
timeout
(dataPtr, size) = do
err <- withDevHndlPtr devHndl $ \devHndlPtr ->
c'libusb_control_transfer devHndlPtr
requestType request value index
(castPtr dataPtr) (fromIntegral size)
(fromIntegral timeout)
let timedOut = err == c'LIBUSB_ERROR_TIMEOUT
if err < 0 && not timedOut
then throwIO $ convertUSBException err
else return ( fromIntegral err
, if timedOut then TimedOut else Completed
)
where
requestType = marshalRequestType reqType reqRecipient reqDir
readBulk :: DeviceHandle
-> EndpointAddress
-> ReadAction
readBulk devHndl
#ifdef HAS_EVENT_MANAGER
| Just wait <- getWait devHndl =
readTransferAsync wait c'LIBUSB_TRANSFER_TYPE_BULK devHndl
#endif
| otherwise = readTransferSync c'libusb_bulk_transfer devHndl
writeBulk :: DeviceHandle
-> EndpointAddress
-> WriteAction
writeBulk devHndl
#ifdef HAS_EVENT_MANAGER
| Just wait <- getWait devHndl =
writeTransferAsync wait c'LIBUSB_TRANSFER_TYPE_BULK devHndl
#endif
| otherwise = writeTransferSync c'libusb_bulk_transfer devHndl
readInterrupt :: DeviceHandle
-> EndpointAddress
-> ReadAction
readInterrupt devHndl
#ifdef HAS_EVENT_MANAGER
| Just wait <- getWait devHndl =
readTransferAsync wait c'LIBUSB_TRANSFER_TYPE_INTERRUPT devHndl
#endif
| otherwise = readTransferSync c'libusb_interrupt_transfer devHndl
writeInterrupt :: DeviceHandle
-> EndpointAddress
-> WriteAction
writeInterrupt devHndl
#ifdef HAS_EVENT_MANAGER
| Just wait <- getWait devHndl =
writeTransferAsync wait c'LIBUSB_TRANSFER_TYPE_INTERRUPT devHndl
#endif
| otherwise = writeTransferSync c'libusb_interrupt_transfer devHndl
type C'TransferFunc = Ptr C'libusb_device_handle
-> CUChar
-> Ptr CUChar
-> CInt
-> Ptr CInt
-> CUInt
-> IO CInt
readTransferSync :: C'TransferFunc -> (DeviceHandle -> EndpointAddress -> ReadAction)
readTransferSync c'transfer = \devHndl endpointAddr -> \size timeout ->
createAndTrimNoOffset size $ \dataPtr ->
transferSync c'transfer
devHndl endpointAddr
timeout
(castPtr dataPtr, size)
writeTransferSync :: C'TransferFunc -> (DeviceHandle -> EndpointAddress -> WriteAction)
writeTransferSync c'transfer = \devHndl endpointAddr -> \input timeout ->
BU.unsafeUseAsCStringLen input $
transferSync c'transfer
devHndl endpointAddr
timeout
transferSync :: C'TransferFunc -> DeviceHandle
-> EndpointAddress
-> Timeout
-> CStringLen
-> IO (Size, Status)
transferSync c'transfer devHndl
endpointAddr
timeout
(dataPtr, size) =
alloca $ \transferredPtr -> do
err <- withDevHndlPtr devHndl $ \devHndlPtr ->
c'transfer devHndlPtr
(marshalEndpointAddress endpointAddr)
(castPtr dataPtr)
(fromIntegral size)
transferredPtr
(fromIntegral timeout)
let timedOut = err == c'LIBUSB_ERROR_TIMEOUT
if err /= c'LIBUSB_SUCCESS && not timedOut
then throwIO $ convertUSBException err
else do transferred <- peek transferredPtr
return ( fromIntegral transferred
, if timedOut then TimedOut else Completed
)
#ifdef HAS_EVENT_MANAGER
readTransferAsync :: Wait
-> C'TransferType
-> DeviceHandle -> EndpointAddress -> ReadAction
readTransferAsync wait transType = \devHndl endpointAddr -> \size timeout ->
createAndTrimNoOffset size $ \bufferPtr ->
transferAsync wait
transType
devHndl (marshalEndpointAddress endpointAddr)
timeout
(bufferPtr, size)
writeTransferAsync :: Wait
-> C'TransferType
-> DeviceHandle -> EndpointAddress -> WriteAction
writeTransferAsync wait transType = \devHndl endpointAddr -> \input timeout ->
BU.unsafeUseAsCStringLen input $
transferAsync wait
transType
devHndl (marshalEndpointAddress endpointAddr)
timeout
type C'TransferType = CUChar
transferAsync :: Wait
-> C'TransferType
-> DeviceHandle -> CUChar
-> Timeout
-> (Ptr byte, Size)
-> IO (Size, Status)
transferAsync wait transType devHndl endpoint timeout bytes =
withTerminatedTransfer wait
transType
isos
devHndl endpoint
timeout
bytes
(continue Completed)
(continue TimedOut)
where
isos :: Storable.Vector C'libusb_iso_packet_descriptor
isos = VG.empty
continue :: Status -> (Ptr C'libusb_transfer -> IO (Size, Status))
continue status = \transPtr -> do
n <- peek $ p'libusb_transfer'actual_length transPtr
return (fromIntegral n, status)
withTerminatedTransfer :: Wait
-> C'TransferType
-> Storable.Vector C'libusb_iso_packet_descriptor
-> DeviceHandle -> CUChar
-> Timeout
-> (Ptr byte, Size)
-> (Ptr C'libusb_transfer -> IO a)
-> (Ptr C'libusb_transfer -> IO a)
-> IO a
withTerminatedTransfer wait
transType
isos
devHndl endpoint
timeout
(bufferPtr, size)
onCompletion
onTimeout =
withDevHndlPtr devHndl $ \devHndlPtr ->
allocaTransfer nrOfIsos $ \transPtr -> do
lock <- newLock
withCallback (\_transPtr -> release lock) $ \cbPtr -> do
poke (p'libusb_transfer'dev_handle transPtr) devHndlPtr
poke (p'libusb_transfer'endpoint transPtr) endpoint
poke (p'libusb_transfer'type transPtr) transType
poke (p'libusb_transfer'timeout transPtr) (fromIntegral timeout)
poke (p'libusb_transfer'length transPtr) (fromIntegral size)
poke (p'libusb_transfer'callback transPtr) cbPtr
poke (p'libusb_transfer'buffer transPtr) (castPtr bufferPtr)
poke (p'libusb_transfer'num_iso_packets transPtr) (fromIntegral nrOfIsos)
pokeVector (p'libusb_transfer'iso_packet_desc transPtr) isos
mask_ $ do
handleUSBException $ c'libusb_submit_transfer transPtr
wait timeout lock transPtr
status <- peek $ p'libusb_transfer'status transPtr
case status of
ts | ts == c'LIBUSB_TRANSFER_COMPLETED -> onCompletion transPtr
| ts == c'LIBUSB_TRANSFER_TIMED_OUT -> onTimeout transPtr
| ts == c'LIBUSB_TRANSFER_ERROR -> throwIO ioException
| ts == c'LIBUSB_TRANSFER_NO_DEVICE -> throwIO NoDeviceException
| ts == c'LIBUSB_TRANSFER_OVERFLOW -> throwIO OverflowException
| ts == c'LIBUSB_TRANSFER_STALL -> throwIO PipeException
| ts == c'LIBUSB_TRANSFER_CANCELLED ->
moduleError "transfer status can't be Cancelled!"
| otherwise -> moduleError $ "Unknown transfer status: " ++
show ts ++ "!"
where
nrOfIsos = VG.length isos
allocaTransfer :: Int -> (Ptr C'libusb_transfer -> IO a) -> IO a
allocaTransfer nrOfIsos = bracket (mallocTransfer nrOfIsos)
c'libusb_free_transfer
mallocTransfer :: Int -> IO (Ptr C'libusb_transfer)
mallocTransfer nrOfIsos = do
transPtr <- c'libusb_alloc_transfer (fromIntegral nrOfIsos)
when (transPtr == nullPtr) (throwIO NoMemException)
return transPtr
withCallback :: (Ptr C'libusb_transfer -> IO ())
-> (C'libusb_transfer_cb_fn -> IO a)
-> IO a
withCallback cb = bracket (mk'libusb_transfer_cb_fn cb) freeHaskellFunPtr
newtype Lock = Lock (MVar ()) deriving Eq
newLock :: IO Lock
newLock = Lock <$> newEmptyMVar
acquire :: Lock -> IO ()
acquire (Lock mv) = takeMVar mv
release :: Lock -> IO ()
release (Lock mv) = putMVar mv ()
readIsochronous :: DeviceHandle
-> EndpointAddress
-> Unboxed.Vector Size
-> Timeout
-> IO (Vector B.ByteString)
readIsochronous devHndl endpointAddr sizes timeout = do
wait <- requireWait devHndl "readIsochronous"
allocaBytes totalSize $ \bufferPtr ->
withTerminatedTransfer
wait
c'LIBUSB_TRANSFER_TYPE_ISOCHRONOUS
isos
devHndl
(marshalEndpointAddress endpointAddr)
timeout
(bufferPtr, totalSize)
(getPackets nrOfIsos bufferPtr)
(\_ -> throwIO TimeoutException)
where
totalSize = VG.sum sizes
nrOfIsos = VG.length sizes
isos = VG.map initIsoPacketDesc $ VG.convert sizes
getPackets :: Int -> Ptr Word8 -> Ptr C'libusb_transfer -> IO (Vector B.ByteString)
getPackets nrOfIsos bufferPtr transPtr = do
mv <- VGM.unsafeNew nrOfIsos
let go !ix ptr
| ix < nrOfIsos = do
let isoPtr = advancePtr isoArrayPtr ix
l <- peek (p'libusb_iso_packet_descriptor'length isoPtr)
a <- peek (p'libusb_iso_packet_descriptor'actual_length isoPtr)
let transferred = fromIntegral a
bs <- BI.create transferred $ \p -> copyArray p ptr transferred
VGM.unsafeWrite mv ix bs
go (ix+1) (ptr `plusPtr` fromIntegral l)
| otherwise = VG.unsafeFreeze mv
go 0 bufferPtr
where
isoArrayPtr = p'libusb_transfer'iso_packet_desc transPtr
writeIsochronous :: DeviceHandle
-> EndpointAddress
-> Vector B.ByteString
-> Timeout
-> IO (Unboxed.Vector Size)
writeIsochronous devHndl endpointAddr isoPackets timeout = do
wait <- requireWait devHndl "writeIsochronous"
allocaBytes totalSize $ \bufferPtr -> do
copyIsos (castPtr bufferPtr) isoPackets
withTerminatedTransfer
wait
c'LIBUSB_TRANSFER_TYPE_ISOCHRONOUS
isos
devHndl
(marshalEndpointAddress endpointAddr)
timeout
(bufferPtr, totalSize)
(getSizes nrOfIsos)
(\_ -> throwIO TimeoutException)
where
sizes = VG.map B.length isoPackets
nrOfIsos = VG.length sizes
totalSize = VG.sum sizes
isos = VG.convert $ VG.map initIsoPacketDesc sizes
getSizes :: Int -> Ptr C'libusb_transfer -> IO (Unboxed.Vector Size)
getSizes nrOfIsos transPtr = do
mv <- VGM.unsafeNew nrOfIsos
let go !ix
| ix < nrOfIsos = do
let isoPtr = advancePtr isoArrayPtr ix
a <- peek (p'libusb_iso_packet_descriptor'actual_length isoPtr)
let transferred = fromIntegral a
VGM.unsafeWrite mv ix transferred
go (ix+1)
| otherwise = VG.unsafeFreeze mv
go 0
where
isoArrayPtr = p'libusb_transfer'iso_packet_desc transPtr
copyIsos :: Ptr CChar -> Vector B.ByteString -> IO ()
copyIsos = VG.foldM_ $ \bufferPtr bs ->
BU.unsafeUseAsCStringLen bs $ \(ptr, len) -> do
copyArray bufferPtr ptr len
return $ bufferPtr `plusPtr` len
initIsoPacketDesc :: Size -> C'libusb_iso_packet_descriptor
initIsoPacketDesc size =
C'libusb_iso_packet_descriptor
{ c'libusb_iso_packet_descriptor'length = fromIntegral size
, c'libusb_iso_packet_descriptor'actual_length = 0
, c'libusb_iso_packet_descriptor'status = 0
}
type ThreadSafeTransfer = MVar Transfer
data Transfer = Transfer
{ transForeignPtr :: ForeignPtr C'libusb_transfer
, transWait :: Timeout -> Ptr C'libusb_transfer -> IO ()
, transDevHndl :: DeviceHandle
, transBufferFinalizerIORef :: IORef (IO ())
}
withTransPtr :: Transfer -> (Ptr C'libusb_transfer -> IO a) -> IO a
withTransPtr = withForeignPtr . transForeignPtr
newThreadSafeTransfer :: (Ptr byte, Size)
-> IO ()
-> C'TransferType
-> Storable.Vector C'libusb_iso_packet_descriptor
-> DeviceHandle
-> CUChar
-> Timeout
-> IO ThreadSafeTransfer
newThreadSafeTransfer (bufferPtr, size)
finalizeBuffer transType isos devHndl endpoint timeout = do
wait <- requireWait devHndl "newThreadSafeTransfer"
withDevHndlPtr devHndl $ \devHndlPtr -> mask_ $ do
transPtr <- mallocTransfer nrOfIsos
waitLock <- newLock
cbPtr <- mk'libusb_transfer_cb_fn (\_transPtr -> release waitLock)
poke (p'libusb_transfer'dev_handle transPtr) devHndlPtr
poke (p'libusb_transfer'endpoint transPtr) endpoint
poke (p'libusb_transfer'type transPtr) transType
poke (p'libusb_transfer'timeout transPtr) (fromIntegral timeout)
poke (p'libusb_transfer'length transPtr) (fromIntegral size)
poke (p'libusb_transfer'callback transPtr) cbPtr
poke (p'libusb_transfer'buffer transPtr) (castPtr bufferPtr)
poke (p'libusb_transfer'num_iso_packets transPtr) (fromIntegral nrOfIsos)
pokeVector (p'libusb_transfer'iso_packet_desc transPtr) isos
bufferFinalizerIORef <- newIORef finalizeBuffer
transFP <- FC.newForeignPtr transPtr $ do
c'libusb_free_transfer transPtr
freeHaskellFunPtr cbPtr
bufferFinalizer <- readIORef bufferFinalizerIORef
bufferFinalizer
newMVar Transfer
{ transForeignPtr = transFP
, transWait = \t -> wait t waitLock
, transDevHndl = devHndl
, transBufferFinalizerIORef = bufferFinalizerIORef
}
where
nrOfIsos = VG.length isos
performThreadSafeTransfer :: ThreadSafeTransfer
-> (Ptr C'libusb_transfer -> IO a)
-> (Ptr C'libusb_transfer -> IO a)
-> IO a
performThreadSafeTransfer threadSafeTransfer onCompletion onTimeout =
withMVar threadSafeTransfer $ \transfer ->
withTransPtr transfer $ \transPtr ->
withDevHndlPtr (transDevHndl transfer) $ \_devHndlPtr -> do
mask_ $ do
handleUSBException $ c'libusb_submit_transfer transPtr
timeout <- peek $ p'libusb_transfer'timeout transPtr
transWait transfer (fromIntegral timeout) transPtr
status <- peek $ p'libusb_transfer'status transPtr
case status of
ts | ts == c'LIBUSB_TRANSFER_COMPLETED -> onCompletion transPtr
| ts == c'LIBUSB_TRANSFER_TIMED_OUT -> onTimeout transPtr
| ts == c'LIBUSB_TRANSFER_ERROR -> throwIO ioException
| ts == c'LIBUSB_TRANSFER_NO_DEVICE -> throwIO NoDeviceException
| ts == c'LIBUSB_TRANSFER_OVERFLOW -> throwIO OverflowException
| ts == c'LIBUSB_TRANSFER_STALL -> throwIO PipeException
| ts == c'LIBUSB_TRANSFER_CANCELLED ->
moduleError "transfer status can't be Cancelled!"
| otherwise -> moduleError $ "Unknown transfer status: " ++
show ts ++ "!"
setTransferProperty :: (Storable a)
=> (Ptr C'libusb_transfer -> Ptr a)
-> ThreadSafeTransfer
-> a
-> IO ()
setTransferProperty prop threadSafeTransfer val =
withMVar threadSafeTransfer $ \transfer ->
withTransPtr transfer $ \transPtr ->
poke (prop transPtr) val
setTransferType :: ThreadSafeTransfer -> C'TransferType -> IO ()
setTransferType = setTransferProperty p'libusb_transfer'type
setTransferDeviceHandle :: ThreadSafeTransfer -> DeviceHandle -> IO ()
setTransferDeviceHandle threadSafeTransfer devHndl =
withDevHndlPtr devHndl $ \devHndlPtr -> mask_ $ do
transfer <- takeMVar threadSafeTransfer
withTransPtr transfer $ \transPtr -> do
poke (p'libusb_transfer'dev_handle transPtr) devHndlPtr
putMVar threadSafeTransfer transfer{transDevHndl = devHndl}
setTransferEndpointAddress :: ThreadSafeTransfer -> EndpointAddress -> IO ()
setTransferEndpointAddress threadSafeTransfer endpointAddr =
setTransferProperty p'libusb_transfer'endpoint
threadSafeTransfer
(marshalEndpointAddress endpointAddr)
setTransferTimeout :: ThreadSafeTransfer -> Timeout -> IO ()
setTransferTimeout threadSafeTransfer timeout =
setTransferProperty p'libusb_transfer'timeout
threadSafeTransfer
(fromIntegral timeout)
getTransferProperty :: (Storable a)
=> (Ptr C'libusb_transfer -> Ptr a)
-> ThreadSafeTransfer
-> IO a
getTransferProperty prop threadSafeTransfer =
withMVar threadSafeTransfer $ \transfer ->
withTransPtr transfer $ \transPtr ->
peek $ prop transPtr
getTransferType :: ThreadSafeTransfer -> IO C'TransferType
getTransferType = getTransferProperty p'libusb_transfer'type
getTransferDeviceHandle :: ThreadSafeTransfer -> IO DeviceHandle
getTransferDeviceHandle threadSafeTransfer =
withMVar threadSafeTransfer $ return . transDevHndl
getTransferEndpointAddress :: ThreadSafeTransfer -> IO EndpointAddress
getTransferEndpointAddress = fmap (unmarshalEndpointAddress . fromIntegral)
. getTransferProperty p'libusb_transfer'endpoint
getTransferTimeout :: ThreadSafeTransfer -> IO Timeout
getTransferTimeout = fmap fromIntegral
. getTransferProperty p'libusb_transfer'timeout
newtype ControlTransfer = ControlTransfer
{unControlTransfer :: ThreadSafeTransfer}
newControlTransfer :: DeviceHandle -> ControlSetup -> Timeout -> IO ControlTransfer
newControlTransfer devHndl ctrlSetup timeout = mask_ $ do
bufferPtr <- mallocBytes size
when (bufferPtr == nullPtr) $ throwIO NoMemException
poke bufferPtr $ marshallControlSetup ctrlSetup Out 0
ControlTransfer <$> newThreadSafeTransfer
(bufferPtr, size) (free bufferPtr)
c'LIBUSB_TRANSFER_TYPE_CONTROL
isos devHndl controlEndpoint timeout
where
size = controlSetupSize
isos :: Storable.Vector C'libusb_iso_packet_descriptor
isos = VG.empty
performControlTransfer :: ControlTransfer -> IO ()
performControlTransfer ctrlTransfer =
performThreadSafeTransfer (unControlTransfer ctrlTransfer)
(\_transPtr -> return ())
(\_transPtr -> throwIO TimeoutException)
setControlTransferDeviceHandle :: ControlTransfer -> DeviceHandle -> IO ()
setControlTransferDeviceHandle = setTransferDeviceHandle . unControlTransfer
setControlTransferTimeout :: ControlTransfer -> Timeout -> IO ()
setControlTransferTimeout = setTransferTimeout . unControlTransfer
setControlSetup :: ControlTransfer -> ControlSetup -> IO ()
setControlSetup ctrlTransfer ctrlSetup =
withMVarMasked (unControlTransfer ctrlTransfer) $ \transfer ->
withTransPtr transfer $ \transPtr -> do
bufferPtr <- peek (p'libusb_transfer'buffer transPtr)
poke (castPtr bufferPtr) $ marshallControlSetup ctrlSetup Out 0
getControlTransferDeviceHandle :: ControlTransfer -> IO DeviceHandle
getControlTransferDeviceHandle = getTransferDeviceHandle . unControlTransfer
getControlTransferTimeout :: ControlTransfer -> IO Timeout
getControlTransferTimeout = getTransferTimeout . unControlTransfer
newtype ControlReadTransfer = ControlReadTransfer
{unControlReadTransfer :: ThreadSafeTransfer}
newControlReadTransfer
:: DeviceHandle -> ControlSetup -> Size -> Timeout -> IO ControlReadTransfer
newControlReadTransfer devHndl ctrlSetup readSize timeout = mask_ $ do
bufferPtr <- mallocBytes size
when (bufferPtr == nullPtr) $ throwIO NoMemException
poke bufferPtr $ marshallControlSetup ctrlSetup In readSize
ControlReadTransfer <$> newThreadSafeTransfer
(bufferPtr, size) (free bufferPtr)
c'LIBUSB_TRANSFER_TYPE_CONTROL
isos devHndl controlEndpoint timeout
where
size = controlSetupSize + readSize
isos :: Storable.Vector C'libusb_iso_packet_descriptor
isos = VG.empty
performControlReadTransfer :: ControlReadTransfer -> IO (B.ByteString, Status)
performControlReadTransfer ctrlReadTransfer =
performThreadSafeTransfer (unControlReadTransfer ctrlReadTransfer)
(continue Completed) (continue TimedOut)
where
continue :: Status -> (Ptr C'libusb_transfer -> IO (B.ByteString, Status))
continue status = \transPtr -> do
len <- fromIntegral <$> peek (p'libusb_transfer'actual_length transPtr)
bufferPtr <- castPtr <$> peek (p'libusb_transfer'buffer transPtr)
bs <- BI.create len $ \ptr ->
BI.memcpy ptr (bufferPtr `plusPtr` controlSetupSize) (fromIntegral len)
return (bs, status)
setControlReadTransferDeviceHandle :: ControlReadTransfer -> DeviceHandle -> IO ()
setControlReadTransferDeviceHandle = setTransferDeviceHandle . unControlReadTransfer
setControlReadTransferTimeout :: ControlReadTransfer -> Timeout -> IO ()
setControlReadTransferTimeout = setTransferTimeout . unControlReadTransfer
setControlReadSetup :: ControlReadTransfer -> ControlSetup -> Size -> IO ()
setControlReadSetup ctrlReadTransfer ctrlSetup readSize =
withMVarMasked (unControlReadTransfer ctrlReadTransfer) $ \transfer ->
withTransPtr transfer $ \transPtr -> do
oldBufferPtr <- peek (p'libusb_transfer'buffer transPtr)
oldReadSize <- peek (p'libusb_control_setup'wLength (castPtr oldBufferPtr))
bufferPtr <-
if fromIntegral oldReadSize == readSize
then return oldBufferPtr
else do
bufferPtr <- reallocBytes oldBufferPtr size
when (bufferPtr == nullPtr) $ throwIO NoMemException
writeIORef (transBufferFinalizerIORef transfer) $ free bufferPtr
poke (p'libusb_transfer'buffer transPtr) bufferPtr
poke (p'libusb_transfer'length transPtr) (fromIntegral size)
return bufferPtr
poke (castPtr bufferPtr) $ marshallControlSetup ctrlSetup In readSize
where
size = controlSetupSize + readSize
getControlReadTransferDeviceHandle :: ControlReadTransfer -> IO DeviceHandle
getControlReadTransferDeviceHandle = getTransferDeviceHandle . unControlReadTransfer
getControlReadTransferTimeout :: ControlReadTransfer -> IO Timeout
getControlReadTransferTimeout = getTransferTimeout . unControlReadTransfer
newtype ControlWriteTransfer = ControlWriteTransfer
{unControlWriteTransfer :: ThreadSafeTransfer}
newControlWriteTransfer
:: DeviceHandle
-> ControlSetup
-> B.ByteString
-> Timeout
-> IO ControlWriteTransfer
newControlWriteTransfer devHndl ctrlSetup input timeout =
BU.unsafeUseAsCStringLen input $ \(dataPtr, writeSize) -> mask_ $ do
let size = controlSetupSize + writeSize
bufferPtr <- mallocBytes size
when (bufferPtr == nullPtr) $ throwIO NoMemException
poke bufferPtr $ marshallControlSetup ctrlSetup Out writeSize
copyArray (bufferPtr `plusPtr` controlSetupSize) dataPtr writeSize
ControlWriteTransfer <$> newThreadSafeTransfer
(bufferPtr, size) (free bufferPtr)
c'LIBUSB_TRANSFER_TYPE_CONTROL
isos devHndl controlEndpoint timeout
where
isos :: Storable.Vector C'libusb_iso_packet_descriptor
isos = VG.empty
performControlWriteTransfer :: ControlWriteTransfer -> IO (Size, Status)
performControlWriteTransfer ctrlWriteTransfer =
performThreadSafeTransfer (unControlWriteTransfer ctrlWriteTransfer)
(continue Completed) (continue TimedOut)
where
continue :: Status -> (Ptr C'libusb_transfer -> IO (Size, Status))
continue status = \transPtr -> do
len <- fromIntegral <$> peek (p'libusb_transfer'actual_length transPtr)
return (len, status)
setControlWriteTransferDeviceHandle :: ControlWriteTransfer -> DeviceHandle -> IO ()
setControlWriteTransferDeviceHandle = setTransferDeviceHandle . unControlWriteTransfer
setControlWriteTransferTimeout :: ControlWriteTransfer -> Timeout -> IO ()
setControlWriteTransferTimeout = setTransferTimeout . unControlWriteTransfer
setControlWriteSetup :: ControlWriteTransfer -> ControlSetup -> B.ByteString -> IO ()
setControlWriteSetup ctrlWriteTransfer ctrlSetup input =
withMVarMasked (unControlWriteTransfer ctrlWriteTransfer) $ \transfer ->
withTransPtr transfer $ \transPtr -> do
BU.unsafeUseAsCStringLen input $ \(dataPtr, writeSize) -> do
let size = controlSetupSize + writeSize
oldBufferPtr <- peek (p'libusb_transfer'buffer transPtr)
oldWriteSize <- peek (p'libusb_control_setup'wLength (castPtr oldBufferPtr))
bufferPtr <-
if fromIntegral oldWriteSize == writeSize
then return oldBufferPtr
else do
bufferPtr <- reallocBytes oldBufferPtr size
when (bufferPtr == nullPtr) $ throwIO NoMemException
writeIORef (transBufferFinalizerIORef transfer) $ free bufferPtr
poke (p'libusb_transfer'buffer transPtr) bufferPtr
poke (p'libusb_transfer'length transPtr) (fromIntegral size)
return bufferPtr
poke (castPtr bufferPtr) $ marshallControlSetup ctrlSetup Out writeSize
copyArray (bufferPtr `plusPtr` controlSetupSize) dataPtr writeSize
getControlWriteTransferDeviceHandle :: ControlWriteTransfer -> IO DeviceHandle
getControlWriteTransferDeviceHandle = getTransferDeviceHandle . unControlWriteTransfer
getControlWriteTransferTimeout :: ControlWriteTransfer -> IO Timeout
getControlWriteTransferTimeout = getTransferTimeout . unControlWriteTransfer
data RepeatableTransferType = BulkTransfer
| InterruptTransfer
marshallRepeatableTransferType :: RepeatableTransferType -> C'TransferType
marshallRepeatableTransferType BulkTransfer = c'LIBUSB_TRANSFER_TYPE_BULK
marshallRepeatableTransferType InterruptTransfer = c'LIBUSB_TRANSFER_TYPE_INTERRUPT
unmarshalRepeatableTransferType :: C'TransferType -> RepeatableTransferType
unmarshalRepeatableTransferType transType
| transType == c'LIBUSB_TRANSFER_TYPE_BULK = BulkTransfer
| transType == c'LIBUSB_TRANSFER_TYPE_INTERRUPT = InterruptTransfer
| otherwise =
moduleError $ "unmarshalRepeatableTransferType: Invalid transfer type: " ++
show transType
newtype ReadTransfer = ReadTransfer {unReadTransfer :: ThreadSafeTransfer}
newReadTransfer :: RepeatableTransferType
-> DeviceHandle
-> EndpointAddress
-> Size
-> Timeout
-> IO ReadTransfer
newReadTransfer transType devHndl endpointAddr size timeout = mask_ $ do
bufferPtr <- mallocBytes size
when (bufferPtr == nullPtr && size /= 0) $
throwIO NoMemException
ReadTransfer <$> newThreadSafeTransfer
(bufferPtr, size) (free bufferPtr)
(marshallRepeatableTransferType transType)
isos devHndl (marshalEndpointAddress endpointAddr) timeout
where
isos :: Storable.Vector C'libusb_iso_packet_descriptor
isos = VG.empty
performReadTransfer :: ReadTransfer -> IO (B.ByteString, Status)
performReadTransfer readTransfer =
performThreadSafeTransfer (unReadTransfer readTransfer)
(continue Completed) (continue TimedOut)
where
continue :: Status -> (Ptr C'libusb_transfer -> IO (B.ByteString, Status))
continue status = \transPtr -> do
len <- fromIntegral <$> peek (p'libusb_transfer'actual_length transPtr)
bufferPtr <- castPtr <$> peek (p'libusb_transfer'buffer transPtr)
bs <- BI.create len $ \ptr -> BI.memcpy ptr bufferPtr (fromIntegral len)
return (bs, status)
setReadTransferType :: ReadTransfer -> RepeatableTransferType -> IO ()
setReadTransferType readTransfer transType =
setTransferType (unReadTransfer readTransfer)
(marshallRepeatableTransferType transType)
setReadTransferDeviceHandle :: ReadTransfer -> DeviceHandle -> IO ()
setReadTransferDeviceHandle = setTransferDeviceHandle . unReadTransfer
setReadTransferEndpointAddress :: ReadTransfer -> EndpointAddress -> IO ()
setReadTransferEndpointAddress = setTransferEndpointAddress . unReadTransfer
setReadTransferTimeout :: ReadTransfer -> Timeout -> IO ()
setReadTransferTimeout = setTransferTimeout . unReadTransfer
setReadTransferSize :: ReadTransfer -> Size -> IO ()
setReadTransferSize readTransfer size =
withMVarMasked (unReadTransfer readTransfer) $ \transfer ->
withTransPtr transfer $ \transPtr -> do
let ref = transBufferFinalizerIORef transfer
if size == 0
then do
finalizeBuffer <- readIORef ref
finalizeBuffer
writeIORef ref (return ())
else do
bufferPtr <- peek (p'libusb_transfer'buffer transPtr)
bufferPtr' <- reallocBytes bufferPtr size
when (bufferPtr' == nullPtr) $ throwIO NoMemException
writeIORef ref $ free bufferPtr'
poke (p'libusb_transfer'buffer transPtr) bufferPtr'
poke (p'libusb_transfer'length transPtr) (fromIntegral size)
getReadTransferType :: ReadTransfer -> IO RepeatableTransferType
getReadTransferType = fmap unmarshalRepeatableTransferType
. getTransferType . unReadTransfer
getReadTransferDeviceHandle :: ReadTransfer -> IO DeviceHandle
getReadTransferDeviceHandle = getTransferDeviceHandle . unReadTransfer
getReadTransferEndpointAddress :: ReadTransfer -> IO EndpointAddress
getReadTransferEndpointAddress = getTransferEndpointAddress . unReadTransfer
getReadTransferSize :: ReadTransfer -> IO Size
getReadTransferSize = fmap fromIntegral
. getTransferProperty p'libusb_transfer'length
. unReadTransfer
getReadTransferTimeout :: ReadTransfer -> IO Timeout
getReadTransferTimeout = getTransferTimeout . unReadTransfer
newtype WriteTransfer = WriteTransfer {unWriteTransfer :: ThreadSafeTransfer}
newWriteTransfer :: RepeatableTransferType
-> DeviceHandle
-> EndpointAddress
-> B.ByteString
-> Timeout
-> IO WriteTransfer
newWriteTransfer transType devHndl endpointAddr input timeout =
WriteTransfer <$> newThreadSafeTransfer
(bufferPtr, size) finalizeBuffer
(marshallRepeatableTransferType transType)
isos devHndl (marshalEndpointAddress endpointAddr) timeout
where
(fp, offset, size) = BI.toForeignPtr input
bufferPtr = unsafeForeignPtrToPtr fp `plusPtr` offset
finalizeBuffer = touchForeignPtr fp
isos :: Storable.Vector C'libusb_iso_packet_descriptor
isos = VG.empty
performWriteTransfer :: WriteTransfer -> IO (Size, Status)
performWriteTransfer writeTransfer =
performThreadSafeTransfer (unWriteTransfer writeTransfer)
(continue Completed) (continue TimedOut)
where
continue :: Status -> (Ptr C'libusb_transfer -> IO (Size, Status))
continue status = \transPtr -> do
len <- fromIntegral <$> peek (p'libusb_transfer'actual_length transPtr)
return (len, status)
setWriteTransferType :: WriteTransfer -> RepeatableTransferType -> IO ()
setWriteTransferType writeTransfer transType =
setTransferType (unWriteTransfer writeTransfer)
(marshallRepeatableTransferType transType)
setWriteTransferDeviceHandle :: WriteTransfer -> DeviceHandle -> IO ()
setWriteTransferDeviceHandle = setTransferDeviceHandle . unWriteTransfer
setWriteTransferEndpointAddress :: WriteTransfer -> EndpointAddress -> IO ()
setWriteTransferEndpointAddress = setTransferEndpointAddress . unWriteTransfer
setWriteTransferTimeout :: WriteTransfer -> Timeout -> IO ()
setWriteTransferTimeout = setTransferTimeout . unWriteTransfer
setWriteTransferInput :: WriteTransfer -> B.ByteString -> IO ()
setWriteTransferInput writeTransfer input =
withMVarMasked (unWriteTransfer writeTransfer) $ \transfer ->
withTransPtr transfer $ \transPtr -> do
writeIORef (transBufferFinalizerIORef transfer) finalizeBuffer
poke (p'libusb_transfer'buffer transPtr) bufferPtr
poke (p'libusb_transfer'length transPtr) (fromIntegral size)
where
(fp, offset, size) = BI.toForeignPtr input
bufferPtr = unsafeForeignPtrToPtr fp `plusPtr` offset
finalizeBuffer = touchForeignPtr fp
getWriteTransferType :: WriteTransfer -> IO RepeatableTransferType
getWriteTransferType = fmap unmarshalRepeatableTransferType
. getTransferType . unWriteTransfer
getWriteTransferDeviceHandle :: WriteTransfer -> IO DeviceHandle
getWriteTransferDeviceHandle = getTransferDeviceHandle . unWriteTransfer
getWriteTransferEndpointAddress :: WriteTransfer -> IO EndpointAddress
getWriteTransferEndpointAddress = getTransferEndpointAddress . unWriteTransfer
getWriteTransferInput :: WriteTransfer -> IO B.ByteString
getWriteTransferInput writeTransfer =
withMVar (unWriteTransfer writeTransfer) $ \transfer ->
withTransPtr transfer $ \transPtr -> do
len <- fromIntegral <$> peek (p'libusb_transfer'length transPtr)
bufferPtr <- castPtr <$> peek (p'libusb_transfer'buffer transPtr)
BI.create len $ \ptr -> BI.memcpy ptr bufferPtr (fromIntegral len)
getWriteTransferTimeout :: WriteTransfer -> IO Timeout
getWriteTransferTimeout = getTransferTimeout . unWriteTransfer
newtype IsochronousReadTransfer = IsochronousReadTransfer
{unIsochronousReadTransfer :: ThreadSafeTransfer}
newIsochronousReadTransfer :: DeviceHandle
-> EndpointAddress
-> Unboxed.Vector Size
-> Timeout
-> IO IsochronousReadTransfer
newIsochronousReadTransfer devHndl endpointAddr sizes timeout = mask_ $ do
bufferPtr <- mallocBytes size
when (bufferPtr == nullPtr && size /= 0) $
throwIO NoMemException
IsochronousReadTransfer <$> newThreadSafeTransfer
(bufferPtr, size) (free bufferPtr)
c'LIBUSB_TRANSFER_TYPE_ISOCHRONOUS
isos devHndl (marshalEndpointAddress endpointAddr) timeout
where
size = VG.sum sizes
isos = VG.map initIsoPacketDesc $ VG.convert sizes
performIsochronousReadTransfer :: IsochronousReadTransfer -> IO (Vector B.ByteString)
performIsochronousReadTransfer isoReadTransfer =
performThreadSafeTransfer (unIsochronousReadTransfer isoReadTransfer)
onCompletion onTimeout
where
onCompletion, onTimeout :: Ptr C'libusb_transfer -> IO (Vector B.ByteString)
onCompletion transPtr = do
nrOfIsos <- fromIntegral <$> peek (p'libusb_transfer'num_iso_packets transPtr)
bufferPtr <- castPtr <$> peek (p'libusb_transfer'buffer transPtr)
getPackets nrOfIsos bufferPtr transPtr
onTimeout _transPtr = throwIO TimeoutException
setIsochronousReadTransferDeviceHandle
:: IsochronousReadTransfer -> DeviceHandle -> IO ()
setIsochronousReadTransferDeviceHandle
= setTransferDeviceHandle . unIsochronousReadTransfer
setIsochronousReadTransferEndpointAddress
:: IsochronousReadTransfer -> EndpointAddress -> IO ()
setIsochronousReadTransferEndpointAddress
= setTransferEndpointAddress . unIsochronousReadTransfer
setIsochronousReadTransferSizes
:: IsochronousReadTransfer -> Unboxed.Vector Size -> IO ()
setIsochronousReadTransferSizes isoReadTransfer sizes =
withMVarMasked (unIsochronousReadTransfer isoReadTransfer) $ \transfer ->
withTransPtr transfer $ \transPtr -> do
bufferPtr <- peek (p'libusb_transfer'buffer transPtr)
bufferPtr' <- reallocBytes bufferPtr size
if bufferPtr' == nullPtr
then unless (size == 0) $ throwIO NoMemException
else writeIORef (transBufferFinalizerIORef transfer) $ free bufferPtr'
poke (p'libusb_transfer'buffer transPtr) bufferPtr'
poke (p'libusb_transfer'length transPtr) (fromIntegral size)
pokeVector (p'libusb_transfer'iso_packet_desc transPtr) isos
where
size = VG.sum sizes
isos = VG.map initIsoPacketDesc $ VG.convert sizes
getIsochronousReadTransferDeviceHandle
:: IsochronousReadTransfer -> IO DeviceHandle
getIsochronousReadTransferDeviceHandle
= getTransferDeviceHandle . unIsochronousReadTransfer
getIsochronousReadTransferEndpointAddress
:: IsochronousReadTransfer -> IO EndpointAddress
getIsochronousReadTransferEndpointAddress
= getTransferEndpointAddress . unIsochronousReadTransfer
getIsochronousReadTransferSizes
:: IsochronousReadTransfer -> IO (Unboxed.Vector Size)
getIsochronousReadTransferSizes isoReadTransfer =
withMVar (unIsochronousReadTransfer isoReadTransfer) $ \transfer ->
withTransPtr transfer $ \transPtr -> do
nrOfIsos <- fromIntegral <$>
peek (p'libusb_transfer'num_iso_packets transPtr)
(VG.convert . VG.map (fromIntegral . c'libusb_iso_packet_descriptor'length)) <$>
peekVector nrOfIsos (p'libusb_transfer'iso_packet_desc transPtr)
newtype IsochronousWriteTransfer = IsochronousWriteTransfer
{unIsochronousWriteTransfer :: ThreadSafeTransfer}
newIsochronousWriteTransfer :: DeviceHandle
-> EndpointAddress
-> Vector B.ByteString
-> Timeout
-> IO IsochronousWriteTransfer
newIsochronousWriteTransfer devHndl endpointAddr isoPackets timeout = mask_ $ do
bufferPtr <- mallocBytes size
when (bufferPtr == nullPtr && size /= 0) $
throwIO NoMemException
copyIsos (castPtr bufferPtr) isoPackets
IsochronousWriteTransfer <$> newThreadSafeTransfer
(bufferPtr, size) (free bufferPtr)
c'LIBUSB_TRANSFER_TYPE_ISOCHRONOUS
isos devHndl (marshalEndpointAddress endpointAddr) timeout
where
size = VG.sum sizes
sizes = VG.map B.length isoPackets
isos = VG.convert $ VG.map initIsoPacketDesc sizes
performIsochronousWriteTransfer :: IsochronousWriteTransfer -> IO (Unboxed.Vector Size)
performIsochronousWriteTransfer isoWriteTransfer =
performThreadSafeTransfer (unIsochronousWriteTransfer isoWriteTransfer)
onCompletion onTimeout
where
onCompletion, onTimeout :: Ptr C'libusb_transfer -> IO (Unboxed.Vector Size)
onCompletion transPtr = do
nrOfIsos <- fromIntegral <$> peek (p'libusb_transfer'num_iso_packets transPtr)
getSizes nrOfIsos transPtr
onTimeout _transPtr = throwIO TimeoutException
setIsochronousWriteTransferDeviceHandle
:: IsochronousWriteTransfer -> DeviceHandle -> IO ()
setIsochronousWriteTransferDeviceHandle
= setTransferDeviceHandle . unIsochronousWriteTransfer
setIsochronousWriteTransferEndpointAddress
:: IsochronousWriteTransfer -> EndpointAddress -> IO ()
setIsochronousWriteTransferEndpointAddress
= setTransferEndpointAddress . unIsochronousWriteTransfer
setIsochronousWriteTransferPackets
:: IsochronousWriteTransfer -> Vector B.ByteString -> IO ()
setIsochronousWriteTransferPackets isoWriteTransfer isoPackets =
withMVarMasked (unIsochronousWriteTransfer isoWriteTransfer) $ \transfer ->
withTransPtr transfer $ \transPtr -> do
bufferPtr <- peek (p'libusb_transfer'buffer transPtr)
bufferPtr' <- reallocBytes bufferPtr size
if bufferPtr' == nullPtr
then unless (size == 0) $ throwIO NoMemException
else writeIORef (transBufferFinalizerIORef transfer) $ free bufferPtr'
poke (p'libusb_transfer'buffer transPtr) bufferPtr'
poke (p'libusb_transfer'length transPtr) (fromIntegral size)
pokeVector (p'libusb_transfer'iso_packet_desc transPtr) isos
copyIsos (castPtr bufferPtr') isoPackets
where
size = VG.sum sizes
sizes = VG.map B.length isoPackets
isos = VG.convert $ VG.map initIsoPacketDesc sizes
getIsochronousWriteTransferDeviceHandle
:: IsochronousWriteTransfer -> IO DeviceHandle
getIsochronousWriteTransferDeviceHandle
= getTransferDeviceHandle . unIsochronousWriteTransfer
getIsochronousWriteTransferEndpointAddress
:: IsochronousWriteTransfer -> IO EndpointAddress
getIsochronousWriteTransferEndpointAddress
= getTransferEndpointAddress . unIsochronousWriteTransfer
getIsochronousWriteTransferPackets
:: IsochronousWriteTransfer -> IO (Vector B.ByteString)
getIsochronousWriteTransferPackets isoWriteTransfer =
withMVar (unIsochronousWriteTransfer isoWriteTransfer) $ \transfer ->
withTransPtr transfer $ \transPtr -> do
bufferPtr <- castPtr <$> peek (p'libusb_transfer'buffer transPtr)
nrOfIsos <- fromIntegral <$>
peek (p'libusb_transfer'num_iso_packets transPtr)
mv <- VGM.unsafeNew nrOfIsos
let isoArrayPtr = p'libusb_transfer'iso_packet_desc transPtr
go !ix ptr
| ix < nrOfIsos = do
let isoPtr = advancePtr isoArrayPtr ix
l <- fromIntegral <$> peek (p'libusb_iso_packet_descriptor'length isoPtr)
bs <- BI.create l $ \p -> copyArray p ptr l
VGM.unsafeWrite mv ix bs
go (ix+1) (ptr `plusPtr` l)
| otherwise = VG.unsafeFreeze mv
go 0 bufferPtr
#endif
createAndTrimNoOffset :: Size -> (Ptr Word8 -> IO (Size, a)) -> IO (B.ByteString, a)
createAndTrimNoOffset size f = BI.createAndTrim' size $ \ptr -> do
(l, x) <- f ptr
return (offset, l, x)
where
offset = 0
handleUSBException :: IO CInt -> IO ()
handleUSBException action = do err <- action
when (err /= c'LIBUSB_SUCCESS)
(throwIO $ convertUSBException err)
checkUSBException :: (Integral a, Show a) => IO a -> IO Int
checkUSBException action = do r <- action
if r < 0
then throwIO $ convertUSBException r
else return $ fromIntegral r
convertUSBException :: (Num a, Eq a, Show a) => a -> USBException
convertUSBException err = fromMaybe unknownLibUsbError $
lookup err libusb_error_to_USBException
where
unknownLibUsbError =
moduleError $ "Unknown libusb error code: " ++ show err ++ "!"
libusb_error_to_USBException :: Num a => [(a, USBException)]
libusb_error_to_USBException =
[ (c'LIBUSB_ERROR_IO, ioException)
, (c'LIBUSB_ERROR_INVALID_PARAM, InvalidParamException)
, (c'LIBUSB_ERROR_ACCESS, AccessException)
, (c'LIBUSB_ERROR_NO_DEVICE, NoDeviceException)
, (c'LIBUSB_ERROR_NOT_FOUND, NotFoundException)
, (c'LIBUSB_ERROR_BUSY, BusyException)
, (c'LIBUSB_ERROR_TIMEOUT, TimeoutException)
, (c'LIBUSB_ERROR_OVERFLOW, OverflowException)
, (c'LIBUSB_ERROR_PIPE, PipeException)
, (c'LIBUSB_ERROR_INTERRUPTED, InterruptedException)
, (c'LIBUSB_ERROR_NO_MEM, NoMemException)
, (c'LIBUSB_ERROR_NOT_SUPPORTED, NotSupportedException)
, (c'LIBUSB_ERROR_OTHER, OtherException)
]
data USBException =
IOException String
| InvalidParamException
| AccessException
| NoDeviceException
| NotFoundException
| BusyException
| TimeoutException
| OverflowException
| PipeException
| InterruptedException
| NoMemException
| NotSupportedException
| OtherException
deriving (COMMON_INSTANCES)
instance Exception USBException
ioException :: USBException
ioException = IOException ""
incompleteReadException :: USBException
incompleteReadException = incompleteException "read"
incompleteWriteException :: USBException
incompleteWriteException = incompleteException "written"
incompleteException :: String -> USBException
incompleteException rw = IOException $
"The number of bytes " ++ rw ++ " doesn't equal the requested number!"
moduleError :: String -> error
moduleError msg = error $ thisModule ++ ": " ++ msg
thisModule :: String
thisModule = "System.USB.Base"