mirror of https://github.com/ethereum/go-ethereum
Merge pull request #3592 from karalabe/hw-wallets
accounts: initial support for Ledger hardware walletspull/3668/head
commit
f8f428cc18
@ -1,350 +0,0 @@ |
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package accounts implements encrypted storage of secp256k1 private keys.
|
||||
//
|
||||
// Keys are stored as encrypted JSON files according to the Web3 Secret Storage specification.
|
||||
// See https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition for more information.
|
||||
package accounts |
||||
|
||||
import ( |
||||
"crypto/ecdsa" |
||||
crand "crypto/rand" |
||||
"encoding/json" |
||||
"errors" |
||||
"fmt" |
||||
"os" |
||||
"path/filepath" |
||||
"runtime" |
||||
"sync" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
) |
||||
|
||||
var ( |
||||
ErrLocked = errors.New("account is locked") |
||||
ErrNoMatch = errors.New("no key for given address or file") |
||||
ErrDecrypt = errors.New("could not decrypt key with given passphrase") |
||||
) |
||||
|
||||
// Account represents a stored key.
|
||||
// When used as an argument, it selects a unique key file to act on.
|
||||
type Account struct { |
||||
Address common.Address // Ethereum account address derived from the key
|
||||
|
||||
// File contains the key file name.
|
||||
// When Acccount is used as an argument to select a key, File can be left blank to
|
||||
// select just by address or set to the basename or absolute path of a file in the key
|
||||
// directory. Accounts returned by Manager will always contain an absolute path.
|
||||
File string |
||||
} |
||||
|
||||
func (acc *Account) MarshalJSON() ([]byte, error) { |
||||
return []byte(`"` + acc.Address.Hex() + `"`), nil |
||||
} |
||||
|
||||
func (acc *Account) UnmarshalJSON(raw []byte) error { |
||||
return json.Unmarshal(raw, &acc.Address) |
||||
} |
||||
|
||||
// Manager manages a key storage directory on disk.
|
||||
type Manager struct { |
||||
cache *addrCache |
||||
keyStore keyStore |
||||
mu sync.RWMutex |
||||
unlocked map[common.Address]*unlocked |
||||
} |
||||
|
||||
type unlocked struct { |
||||
*Key |
||||
abort chan struct{} |
||||
} |
||||
|
||||
// NewManager creates a manager for the given directory.
|
||||
func NewManager(keydir string, scryptN, scryptP int) *Manager { |
||||
keydir, _ = filepath.Abs(keydir) |
||||
am := &Manager{keyStore: &keyStorePassphrase{keydir, scryptN, scryptP}} |
||||
am.init(keydir) |
||||
return am |
||||
} |
||||
|
||||
// NewPlaintextManager creates a manager for the given directory.
|
||||
// Deprecated: Use NewManager.
|
||||
func NewPlaintextManager(keydir string) *Manager { |
||||
keydir, _ = filepath.Abs(keydir) |
||||
am := &Manager{keyStore: &keyStorePlain{keydir}} |
||||
am.init(keydir) |
||||
return am |
||||
} |
||||
|
||||
func (am *Manager) init(keydir string) { |
||||
am.unlocked = make(map[common.Address]*unlocked) |
||||
am.cache = newAddrCache(keydir) |
||||
// TODO: In order for this finalizer to work, there must be no references
|
||||
// to am. addrCache doesn't keep a reference but unlocked keys do,
|
||||
// so the finalizer will not trigger until all timed unlocks have expired.
|
||||
runtime.SetFinalizer(am, func(m *Manager) { |
||||
m.cache.close() |
||||
}) |
||||
} |
||||
|
||||
// HasAddress reports whether a key with the given address is present.
|
||||
func (am *Manager) HasAddress(addr common.Address) bool { |
||||
return am.cache.hasAddress(addr) |
||||
} |
||||
|
||||
// Accounts returns all key files present in the directory.
|
||||
func (am *Manager) Accounts() []Account { |
||||
return am.cache.accounts() |
||||
} |
||||
|
||||
// Delete deletes the key matched by account if the passphrase is correct.
|
||||
// If the account contains no filename, the address must match a unique key.
|
||||
func (am *Manager) Delete(a Account, passphrase string) error { |
||||
// Decrypting the key isn't really necessary, but we do
|
||||
// it anyway to check the password and zero out the key
|
||||
// immediately afterwards.
|
||||
a, key, err := am.getDecryptedKey(a, passphrase) |
||||
if key != nil { |
||||
zeroKey(key.PrivateKey) |
||||
} |
||||
if err != nil { |
||||
return err |
||||
} |
||||
// The order is crucial here. The key is dropped from the
|
||||
// cache after the file is gone so that a reload happening in
|
||||
// between won't insert it into the cache again.
|
||||
err = os.Remove(a.File) |
||||
if err == nil { |
||||
am.cache.delete(a) |
||||
} |
||||
return err |
||||
} |
||||
|
||||
// Sign calculates a ECDSA signature for the given hash. The produced signature
|
||||
// is in the [R || S || V] format where V is 0 or 1.
|
||||
func (am *Manager) Sign(addr common.Address, hash []byte) ([]byte, error) { |
||||
am.mu.RLock() |
||||
defer am.mu.RUnlock() |
||||
|
||||
unlockedKey, found := am.unlocked[addr] |
||||
if !found { |
||||
return nil, ErrLocked |
||||
} |
||||
return crypto.Sign(hash, unlockedKey.PrivateKey) |
||||
} |
||||
|
||||
// SignWithPassphrase signs hash if the private key matching the given address
|
||||
// can be decrypted with the given passphrase. The produced signature is in the
|
||||
// [R || S || V] format where V is 0 or 1.
|
||||
func (am *Manager) SignWithPassphrase(a Account, passphrase string, hash []byte) (signature []byte, err error) { |
||||
_, key, err := am.getDecryptedKey(a, passphrase) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer zeroKey(key.PrivateKey) |
||||
return crypto.Sign(hash, key.PrivateKey) |
||||
} |
||||
|
||||
// Unlock unlocks the given account indefinitely.
|
||||
func (am *Manager) Unlock(a Account, passphrase string) error { |
||||
return am.TimedUnlock(a, passphrase, 0) |
||||
} |
||||
|
||||
// Lock removes the private key with the given address from memory.
|
||||
func (am *Manager) Lock(addr common.Address) error { |
||||
am.mu.Lock() |
||||
if unl, found := am.unlocked[addr]; found { |
||||
am.mu.Unlock() |
||||
am.expire(addr, unl, time.Duration(0)*time.Nanosecond) |
||||
} else { |
||||
am.mu.Unlock() |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// TimedUnlock unlocks the given account with the passphrase. The account
|
||||
// stays unlocked for the duration of timeout. A timeout of 0 unlocks the account
|
||||
// until the program exits. The account must match a unique key file.
|
||||
//
|
||||
// If the account address is already unlocked for a duration, TimedUnlock extends or
|
||||
// shortens the active unlock timeout. If the address was previously unlocked
|
||||
// indefinitely the timeout is not altered.
|
||||
func (am *Manager) TimedUnlock(a Account, passphrase string, timeout time.Duration) error { |
||||
a, key, err := am.getDecryptedKey(a, passphrase) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
am.mu.Lock() |
||||
defer am.mu.Unlock() |
||||
u, found := am.unlocked[a.Address] |
||||
if found { |
||||
if u.abort == nil { |
||||
// The address was unlocked indefinitely, so unlocking
|
||||
// it with a timeout would be confusing.
|
||||
zeroKey(key.PrivateKey) |
||||
return nil |
||||
} else { |
||||
// Terminate the expire goroutine and replace it below.
|
||||
close(u.abort) |
||||
} |
||||
} |
||||
if timeout > 0 { |
||||
u = &unlocked{Key: key, abort: make(chan struct{})} |
||||
go am.expire(a.Address, u, timeout) |
||||
} else { |
||||
u = &unlocked{Key: key} |
||||
} |
||||
am.unlocked[a.Address] = u |
||||
return nil |
||||
} |
||||
|
||||
// Find resolves the given account into a unique entry in the keystore.
|
||||
func (am *Manager) Find(a Account) (Account, error) { |
||||
am.cache.maybeReload() |
||||
am.cache.mu.Lock() |
||||
a, err := am.cache.find(a) |
||||
am.cache.mu.Unlock() |
||||
return a, err |
||||
} |
||||
|
||||
func (am *Manager) getDecryptedKey(a Account, auth string) (Account, *Key, error) { |
||||
a, err := am.Find(a) |
||||
if err != nil { |
||||
return a, nil, err |
||||
} |
||||
key, err := am.keyStore.GetKey(a.Address, a.File, auth) |
||||
return a, key, err |
||||
} |
||||
|
||||
func (am *Manager) expire(addr common.Address, u *unlocked, timeout time.Duration) { |
||||
t := time.NewTimer(timeout) |
||||
defer t.Stop() |
||||
select { |
||||
case <-u.abort: |
||||
// just quit
|
||||
case <-t.C: |
||||
am.mu.Lock() |
||||
// only drop if it's still the same key instance that dropLater
|
||||
// was launched with. we can check that using pointer equality
|
||||
// because the map stores a new pointer every time the key is
|
||||
// unlocked.
|
||||
if am.unlocked[addr] == u { |
||||
zeroKey(u.PrivateKey) |
||||
delete(am.unlocked, addr) |
||||
} |
||||
am.mu.Unlock() |
||||
} |
||||
} |
||||
|
||||
// NewAccount generates a new key and stores it into the key directory,
|
||||
// encrypting it with the passphrase.
|
||||
func (am *Manager) NewAccount(passphrase string) (Account, error) { |
||||
_, account, err := storeNewKey(am.keyStore, crand.Reader, passphrase) |
||||
if err != nil { |
||||
return Account{}, err |
||||
} |
||||
// Add the account to the cache immediately rather
|
||||
// than waiting for file system notifications to pick it up.
|
||||
am.cache.add(account) |
||||
return account, nil |
||||
} |
||||
|
||||
// AccountByIndex returns the ith account.
|
||||
func (am *Manager) AccountByIndex(i int) (Account, error) { |
||||
accounts := am.Accounts() |
||||
if i < 0 || i >= len(accounts) { |
||||
return Account{}, fmt.Errorf("account index %d out of range [0, %d]", i, len(accounts)-1) |
||||
} |
||||
return accounts[i], nil |
||||
} |
||||
|
||||
// Export exports as a JSON key, encrypted with newPassphrase.
|
||||
func (am *Manager) Export(a Account, passphrase, newPassphrase string) (keyJSON []byte, err error) { |
||||
_, key, err := am.getDecryptedKey(a, passphrase) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
var N, P int |
||||
if store, ok := am.keyStore.(*keyStorePassphrase); ok { |
||||
N, P = store.scryptN, store.scryptP |
||||
} else { |
||||
N, P = StandardScryptN, StandardScryptP |
||||
} |
||||
return EncryptKey(key, newPassphrase, N, P) |
||||
} |
||||
|
||||
// Import stores the given encrypted JSON key into the key directory.
|
||||
func (am *Manager) Import(keyJSON []byte, passphrase, newPassphrase string) (Account, error) { |
||||
key, err := DecryptKey(keyJSON, passphrase) |
||||
if key != nil && key.PrivateKey != nil { |
||||
defer zeroKey(key.PrivateKey) |
||||
} |
||||
if err != nil { |
||||
return Account{}, err |
||||
} |
||||
return am.importKey(key, newPassphrase) |
||||
} |
||||
|
||||
// ImportECDSA stores the given key into the key directory, encrypting it with the passphrase.
|
||||
func (am *Manager) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (Account, error) { |
||||
key := newKeyFromECDSA(priv) |
||||
if am.cache.hasAddress(key.Address) { |
||||
return Account{}, fmt.Errorf("account already exists") |
||||
} |
||||
|
||||
return am.importKey(key, passphrase) |
||||
} |
||||
|
||||
func (am *Manager) importKey(key *Key, passphrase string) (Account, error) { |
||||
a := Account{Address: key.Address, File: am.keyStore.JoinPath(keyFileName(key.Address))} |
||||
if err := am.keyStore.StoreKey(a.File, key, passphrase); err != nil { |
||||
return Account{}, err |
||||
} |
||||
am.cache.add(a) |
||||
return a, nil |
||||
} |
||||
|
||||
// Update changes the passphrase of an existing account.
|
||||
func (am *Manager) Update(a Account, passphrase, newPassphrase string) error { |
||||
a, key, err := am.getDecryptedKey(a, passphrase) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return am.keyStore.StoreKey(a.File, key, newPassphrase) |
||||
} |
||||
|
||||
// ImportPreSaleKey decrypts the given Ethereum presale wallet and stores
|
||||
// a key file in the key directory. The key file is encrypted with the same passphrase.
|
||||
func (am *Manager) ImportPreSaleKey(keyJSON []byte, passphrase string) (Account, error) { |
||||
a, _, err := importPreSaleKey(am.keyStore, keyJSON, passphrase) |
||||
if err != nil { |
||||
return a, err |
||||
} |
||||
am.cache.add(a) |
||||
return a, nil |
||||
} |
||||
|
||||
// zeroKey zeroes a private key in memory.
|
||||
func zeroKey(k *ecdsa.PrivateKey) { |
||||
b := k.D.Bits() |
||||
for i := range b { |
||||
b[i] = 0 |
||||
} |
||||
} |
@ -0,0 +1,155 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package accounts implements high level Ethereum account management.
|
||||
package accounts |
||||
|
||||
import ( |
||||
"math/big" |
||||
|
||||
ethereum "github.com/ethereum/go-ethereum" |
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/event" |
||||
) |
||||
|
||||
// Account represents an Ethereum account located at a specific location defined
|
||||
// by the optional URL field.
|
||||
type Account struct { |
||||
Address common.Address `json:"address"` // Ethereum account address derived from the key
|
||||
URL URL `json:"url"` // Optional resource locator within a backend
|
||||
} |
||||
|
||||
// Wallet represents a software or hardware wallet that might contain one or more
|
||||
// accounts (derived from the same seed).
|
||||
type Wallet interface { |
||||
// URL retrieves the canonical path under which this wallet is reachable. It is
|
||||
// user by upper layers to define a sorting order over all wallets from multiple
|
||||
// backends.
|
||||
URL() URL |
||||
|
||||
// Status returns a textual status to aid the user in the current state of the
|
||||
// wallet.
|
||||
Status() string |
||||
|
||||
// Open initializes access to a wallet instance. It is not meant to unlock or
|
||||
// decrypt account keys, rather simply to establish a connection to hardware
|
||||
// wallets and/or to access derivation seeds.
|
||||
//
|
||||
// The passphrase parameter may or may not be used by the implementation of a
|
||||
// particular wallet instance. The reason there is no passwordless open method
|
||||
// is to strive towards a uniform wallet handling, oblivious to the different
|
||||
// backend providers.
|
||||
//
|
||||
// Please note, if you open a wallet, you must close it to release any allocated
|
||||
// resources (especially important when working with hardware wallets).
|
||||
Open(passphrase string) error |
||||
|
||||
// Close releases any resources held by an open wallet instance.
|
||||
Close() error |
||||
|
||||
// Accounts retrieves the list of signing accounts the wallet is currently aware
|
||||
// of. For hierarchical deterministic wallets, the list will not be exhaustive,
|
||||
// rather only contain the accounts explicitly pinned during account derivation.
|
||||
Accounts() []Account |
||||
|
||||
// Contains returns whether an account is part of this particular wallet or not.
|
||||
Contains(account Account) bool |
||||
|
||||
// Derive attempts to explicitly derive a hierarchical deterministic account at
|
||||
// the specified derivation path. If requested, the derived account will be added
|
||||
// to the wallet's tracked account list.
|
||||
Derive(path DerivationPath, pin bool) (Account, error) |
||||
|
||||
// SelfDerive sets a base account derivation path from which the wallet attempts
|
||||
// to discover non zero accounts and automatically add them to list of tracked
|
||||
// accounts.
|
||||
//
|
||||
// Note, self derivaton will increment the last component of the specified path
|
||||
// opposed to decending into a child path to allow discovering accounts starting
|
||||
// from non zero components.
|
||||
//
|
||||
// You can disable automatic account discovery by calling SelfDerive with a nil
|
||||
// chain state reader.
|
||||
SelfDerive(base DerivationPath, chain ethereum.ChainStateReader) |
||||
|
||||
// SignHash requests the wallet to sign the given hash.
|
||||
//
|
||||
// It looks up the account specified either solely via its address contained within,
|
||||
// or optionally with the aid of any location metadata from the embedded URL field.
|
||||
//
|
||||
// If the wallet requires additional authentication to sign the request (e.g.
|
||||
// a password to decrypt the account, or a PIN code o verify the transaction),
|
||||
// an AuthNeededError instance will be returned, containing infos for the user
|
||||
// about which fields or actions are needed. The user may retry by providing
|
||||
// the needed details via SignHashWithPassphrase, or by other means (e.g. unlock
|
||||
// the account in a keystore).
|
||||
SignHash(account Account, hash []byte) ([]byte, error) |
||||
|
||||
// SignTx requests the wallet to sign the given transaction.
|
||||
//
|
||||
// It looks up the account specified either solely via its address contained within,
|
||||
// or optionally with the aid of any location metadata from the embedded URL field.
|
||||
//
|
||||
// If the wallet requires additional authentication to sign the request (e.g.
|
||||
// a password to decrypt the account, or a PIN code o verify the transaction),
|
||||
// an AuthNeededError instance will be returned, containing infos for the user
|
||||
// about which fields or actions are needed. The user may retry by providing
|
||||
// the needed details via SignTxWithPassphrase, or by other means (e.g. unlock
|
||||
// the account in a keystore).
|
||||
SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) |
||||
|
||||
// SignHashWithPassphrase requests the wallet to sign the given hash with the
|
||||
// given passphrase as extra authentication information.
|
||||
//
|
||||
// It looks up the account specified either solely via its address contained within,
|
||||
// or optionally with the aid of any location metadata from the embedded URL field.
|
||||
SignHashWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error) |
||||
|
||||
// SignTxWithPassphrase requests the wallet to sign the given transaction, with the
|
||||
// given passphrase as extra authentication information.
|
||||
//
|
||||
// It looks up the account specified either solely via its address contained within,
|
||||
// or optionally with the aid of any location metadata from the embedded URL field.
|
||||
SignTxWithPassphrase(account Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) |
||||
} |
||||
|
||||
// Backend is a "wallet provider" that may contain a batch of accounts they can
|
||||
// sign transactions with and upon request, do so.
|
||||
type Backend interface { |
||||
// Wallets retrieves the list of wallets the backend is currently aware of.
|
||||
//
|
||||
// The returned wallets are not opened by default. For software HD wallets this
|
||||
// means that no base seeds are decrypted, and for hardware wallets that no actual
|
||||
// connection is established.
|
||||
//
|
||||
// The resulting wallet list will be sorted alphabetically based on its internal
|
||||
// URL assigned by the backend. Since wallets (especially hardware) may come and
|
||||
// go, the same wallet might appear at a different positions in the list during
|
||||
// subsequent retrievals.
|
||||
Wallets() []Wallet |
||||
|
||||
// Subscribe creates an async subscription to receive notifications when the
|
||||
// backend detects the arrival or departure of a wallet.
|
||||
Subscribe(sink chan<- WalletEvent) event.Subscription |
||||
} |
||||
|
||||
// WalletEvent is an event fired by an account backend when a wallet arrival or
|
||||
// departure is detected.
|
||||
type WalletEvent struct { |
||||
Wallet Wallet // Wallet instance arrived or departed
|
||||
Arrive bool // Whether the wallet was added or removed
|
||||
} |
@ -1,224 +0,0 @@ |
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package accounts |
||||
|
||||
import ( |
||||
"io/ioutil" |
||||
"os" |
||||
"runtime" |
||||
"strings" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
) |
||||
|
||||
var testSigData = make([]byte, 32) |
||||
|
||||
func TestManager(t *testing.T) { |
||||
dir, am := tmpManager(t, true) |
||||
defer os.RemoveAll(dir) |
||||
|
||||
a, err := am.NewAccount("foo") |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if !strings.HasPrefix(a.File, dir) { |
||||
t.Errorf("account file %s doesn't have dir prefix", a.File) |
||||
} |
||||
stat, err := os.Stat(a.File) |
||||
if err != nil { |
||||
t.Fatalf("account file %s doesn't exist (%v)", a.File, err) |
||||
} |
||||
if runtime.GOOS != "windows" && stat.Mode() != 0600 { |
||||
t.Fatalf("account file has wrong mode: got %o, want %o", stat.Mode(), 0600) |
||||
} |
||||
if !am.HasAddress(a.Address) { |
||||
t.Errorf("HasAccount(%x) should've returned true", a.Address) |
||||
} |
||||
if err := am.Update(a, "foo", "bar"); err != nil { |
||||
t.Errorf("Update error: %v", err) |
||||
} |
||||
if err := am.Delete(a, "bar"); err != nil { |
||||
t.Errorf("Delete error: %v", err) |
||||
} |
||||
if common.FileExist(a.File) { |
||||
t.Errorf("account file %s should be gone after Delete", a.File) |
||||
} |
||||
if am.HasAddress(a.Address) { |
||||
t.Errorf("HasAccount(%x) should've returned true after Delete", a.Address) |
||||
} |
||||
} |
||||
|
||||
func TestSign(t *testing.T) { |
||||
dir, am := tmpManager(t, true) |
||||
defer os.RemoveAll(dir) |
||||
|
||||
pass := "" // not used but required by API
|
||||
a1, err := am.NewAccount(pass) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if err := am.Unlock(a1, ""); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if _, err := am.Sign(a1.Address, testSigData); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
func TestSignWithPassphrase(t *testing.T) { |
||||
dir, am := tmpManager(t, true) |
||||
defer os.RemoveAll(dir) |
||||
|
||||
pass := "passwd" |
||||
acc, err := am.NewAccount(pass) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
if _, unlocked := am.unlocked[acc.Address]; unlocked { |
||||
t.Fatal("expected account to be locked") |
||||
} |
||||
|
||||
_, err = am.SignWithPassphrase(acc, pass, testSigData) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
if _, unlocked := am.unlocked[acc.Address]; unlocked { |
||||
t.Fatal("expected account to be locked") |
||||
} |
||||
|
||||
if _, err = am.SignWithPassphrase(acc, "invalid passwd", testSigData); err == nil { |
||||
t.Fatal("expected SignHash to fail with invalid password") |
||||
} |
||||
} |
||||
|
||||
func TestTimedUnlock(t *testing.T) { |
||||
dir, am := tmpManager(t, true) |
||||
defer os.RemoveAll(dir) |
||||
|
||||
pass := "foo" |
||||
a1, err := am.NewAccount(pass) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
// Signing without passphrase fails because account is locked
|
||||
_, err = am.Sign(a1.Address, testSigData) |
||||
if err != ErrLocked { |
||||
t.Fatal("Signing should've failed with ErrLocked before unlocking, got ", err) |
||||
} |
||||
|
||||
// Signing with passphrase works
|
||||
if err = am.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
// Signing without passphrase works because account is temp unlocked
|
||||
_, err = am.Sign(a1.Address, testSigData) |
||||
if err != nil { |
||||
t.Fatal("Signing shouldn't return an error after unlocking, got ", err) |
||||
} |
||||
|
||||
// Signing fails again after automatic locking
|
||||
time.Sleep(250 * time.Millisecond) |
||||
_, err = am.Sign(a1.Address, testSigData) |
||||
if err != ErrLocked { |
||||
t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) |
||||
} |
||||
} |
||||
|
||||
func TestOverrideUnlock(t *testing.T) { |
||||
dir, am := tmpManager(t, false) |
||||
defer os.RemoveAll(dir) |
||||
|
||||
pass := "foo" |
||||
a1, err := am.NewAccount(pass) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
// Unlock indefinitely.
|
||||
if err = am.TimedUnlock(a1, pass, 5*time.Minute); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
// Signing without passphrase works because account is temp unlocked
|
||||
_, err = am.Sign(a1.Address, testSigData) |
||||
if err != nil { |
||||
t.Fatal("Signing shouldn't return an error after unlocking, got ", err) |
||||
} |
||||
|
||||
// reset unlock to a shorter period, invalidates the previous unlock
|
||||
if err = am.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
// Signing without passphrase still works because account is temp unlocked
|
||||
_, err = am.Sign(a1.Address, testSigData) |
||||
if err != nil { |
||||
t.Fatal("Signing shouldn't return an error after unlocking, got ", err) |
||||
} |
||||
|
||||
// Signing fails again after automatic locking
|
||||
time.Sleep(250 * time.Millisecond) |
||||
_, err = am.Sign(a1.Address, testSigData) |
||||
if err != ErrLocked { |
||||
t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) |
||||
} |
||||
} |
||||
|
||||
// This test should fail under -race if signing races the expiration goroutine.
|
||||
func TestSignRace(t *testing.T) { |
||||
dir, am := tmpManager(t, false) |
||||
defer os.RemoveAll(dir) |
||||
|
||||
// Create a test account.
|
||||
a1, err := am.NewAccount("") |
||||
if err != nil { |
||||
t.Fatal("could not create the test account", err) |
||||
} |
||||
|
||||
if err := am.TimedUnlock(a1, "", 15*time.Millisecond); err != nil { |
||||
t.Fatal("could not unlock the test account", err) |
||||
} |
||||
end := time.Now().Add(500 * time.Millisecond) |
||||
for time.Now().Before(end) { |
||||
if _, err := am.Sign(a1.Address, testSigData); err == ErrLocked { |
||||
return |
||||
} else if err != nil { |
||||
t.Errorf("Sign error: %v", err) |
||||
return |
||||
} |
||||
time.Sleep(1 * time.Millisecond) |
||||
} |
||||
t.Errorf("Account did not lock within the timeout") |
||||
} |
||||
|
||||
func tmpManager(t *testing.T, encrypted bool) (string, *Manager) { |
||||
d, err := ioutil.TempDir("", "eth-keystore-test") |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
new := NewPlaintextManager |
||||
if encrypted { |
||||
new = func(kd string) *Manager { return NewManager(kd, veryLightScryptN, veryLightScryptP) } |
||||
} |
||||
return d, new(d) |
||||
} |
@ -0,0 +1,68 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package accounts |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
) |
||||
|
||||
// ErrUnknownAccount is returned for any requested operation for which no backend
|
||||
// provides the specified account.
|
||||
var ErrUnknownAccount = errors.New("unknown account") |
||||
|
||||
// ErrUnknownWallet is returned for any requested operation for which no backend
|
||||
// provides the specified wallet.
|
||||
var ErrUnknownWallet = errors.New("unknown wallet") |
||||
|
||||
// ErrNotSupported is returned when an operation is requested from an account
|
||||
// backend that it does not support.
|
||||
var ErrNotSupported = errors.New("not supported") |
||||
|
||||
// ErrInvalidPassphrase is returned when a decryption operation receives a bad
|
||||
// passphrase.
|
||||
var ErrInvalidPassphrase = errors.New("invalid passphrase") |
||||
|
||||
// ErrWalletAlreadyOpen is returned if a wallet is attempted to be opened the
|
||||
// secodn time.
|
||||
var ErrWalletAlreadyOpen = errors.New("wallet already open") |
||||
|
||||
// ErrWalletClosed is returned if a wallet is attempted to be opened the
|
||||
// secodn time.
|
||||
var ErrWalletClosed = errors.New("wallet closed") |
||||
|
||||
// AuthNeededError is returned by backends for signing requests where the user
|
||||
// is required to provide further authentication before signing can succeed.
|
||||
//
|
||||
// This usually means either that a password needs to be supplied, or perhaps a
|
||||
// one time PIN code displayed by some hardware device.
|
||||
type AuthNeededError struct { |
||||
Needed string // Extra authentication the user needs to provide
|
||||
} |
||||
|
||||
// NewAuthNeededError creates a new authentication error with the extra details
|
||||
// about the needed fields set.
|
||||
func NewAuthNeededError(needed string) error { |
||||
return &AuthNeededError{ |
||||
Needed: needed, |
||||
} |
||||
} |
||||
|
||||
// Error implements the standard error interfacel.
|
||||
func (err *AuthNeededError) Error() string { |
||||
return fmt.Sprintf("authentication needed: %s", err.Needed) |
||||
} |
@ -0,0 +1,130 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package accounts |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"math" |
||||
"math/big" |
||||
"strings" |
||||
) |
||||
|
||||
// DefaultRootDerivationPath is the root path to which custom derivation endpoints
|
||||
// are appended. As such, the first account will be at m/44'/60'/0'/0, the second
|
||||
// at m/44'/60'/0'/1, etc.
|
||||
var DefaultRootDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0} |
||||
|
||||
// DefaultBaseDerivationPath is the base path from which custom derivation endpoints
|
||||
// are incremented. As such, the first account will be at m/44'/60'/0'/0, the second
|
||||
// at m/44'/60'/0'/1, etc.
|
||||
var DefaultBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0} |
||||
|
||||
// DerivationPath represents the computer friendly version of a hierarchical
|
||||
// deterministic wallet account derivaion path.
|
||||
//
|
||||
// The BIP-32 spec https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
|
||||
// defines derivation paths to be of the form:
|
||||
//
|
||||
// m / purpose' / coin_type' / account' / change / address_index
|
||||
//
|
||||
// The BIP-44 spec https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
|
||||
// defines that the `purpose` be 44' (or 0x8000002C) for crypto currencies, and
|
||||
// SLIP-44 https://github.com/satoshilabs/slips/blob/master/slip-0044.md assigns
|
||||
// the `coin_type` 60' (or 0x8000003C) to Ethereum.
|
||||
//
|
||||
// The root path for Ethereum is m/44'/60'/0'/0 according to the specification
|
||||
// from https://github.com/ethereum/EIPs/issues/84, albeit it's not set in stone
|
||||
// yet whether accounts should increment the last component or the children of
|
||||
// that. We will go with the simpler approach of incrementing the last component.
|
||||
type DerivationPath []uint32 |
||||
|
||||
// ParseDerivationPath converts a user specified derivation path string to the
|
||||
// internal binary representation.
|
||||
//
|
||||
// Full derivation paths need to start with the `m/` prefix, relative derivation
|
||||
// paths (which will get appended to the default root path) must not have prefixes
|
||||
// in front of the first element. Whitespace is ignored.
|
||||
func ParseDerivationPath(path string) (DerivationPath, error) { |
||||
var result DerivationPath |
||||
|
||||
// Handle absolute or relative paths
|
||||
components := strings.Split(path, "/") |
||||
switch { |
||||
case len(components) == 0: |
||||
return nil, errors.New("empty derivation path") |
||||
|
||||
case strings.TrimSpace(components[0]) == "": |
||||
return nil, errors.New("ambiguous path: use 'm/' prefix for absolute paths, or no leading '/' for relative ones") |
||||
|
||||
case strings.TrimSpace(components[0]) == "m": |
||||
components = components[1:] |
||||
|
||||
default: |
||||
result = append(result, DefaultRootDerivationPath...) |
||||
} |
||||
// All remaining components are relative, append one by one
|
||||
if len(components) == 0 { |
||||
return nil, errors.New("empty derivation path") // Empty relative paths
|
||||
} |
||||
for _, component := range components { |
||||
// Ignore any user added whitespace
|
||||
component = strings.TrimSpace(component) |
||||
var value uint32 |
||||
|
||||
// Handle hardened paths
|
||||
if strings.HasSuffix(component, "'") { |
||||
value = 0x80000000 |
||||
component = strings.TrimSpace(strings.TrimSuffix(component, "'")) |
||||
} |
||||
// Handle the non hardened component
|
||||
bigval, ok := new(big.Int).SetString(component, 0) |
||||
if !ok { |
||||
return nil, fmt.Errorf("invalid component: %s", component) |
||||
} |
||||
max := math.MaxUint32 - value |
||||
if bigval.Sign() < 0 || bigval.Cmp(big.NewInt(int64(max))) > 0 { |
||||
if value == 0 { |
||||
return nil, fmt.Errorf("component %v out of allowed range [0, %d]", bigval, max) |
||||
} |
||||
return nil, fmt.Errorf("component %v out of allowed hardened range [0, %d]", bigval, max) |
||||
} |
||||
value += uint32(bigval.Uint64()) |
||||
|
||||
// Append and repeat
|
||||
result = append(result, value) |
||||
} |
||||
return result, nil |
||||
} |
||||
|
||||
// String implements the stringer interface, converting a binary derivation path
|
||||
// to its canonical representation.
|
||||
func (path DerivationPath) String() string { |
||||
result := "m" |
||||
for _, component := range path { |
||||
var hardened bool |
||||
if component >= 0x80000000 { |
||||
component -= 0x80000000 |
||||
hardened = true |
||||
} |
||||
result = fmt.Sprintf("%s/%d", result, component) |
||||
if hardened { |
||||
result += "'" |
||||
} |
||||
} |
||||
return result |
||||
} |
@ -0,0 +1,79 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package accounts |
||||
|
||||
import ( |
||||
"reflect" |
||||
"testing" |
||||
) |
||||
|
||||
// Tests that HD derivation paths can be correctly parsed into our internal binary
|
||||
// representation.
|
||||
func TestHDPathParsing(t *testing.T) { |
||||
tests := []struct { |
||||
input string |
||||
output DerivationPath |
||||
}{ |
||||
// Plain absolute derivation paths
|
||||
{"m/44'/60'/0'/0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, |
||||
{"m/44'/60'/0'/128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}}, |
||||
{"m/44'/60'/0'/0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, |
||||
{"m/44'/60'/0'/128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}}, |
||||
{"m/2147483692/2147483708/2147483648/0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, |
||||
{"m/2147483692/2147483708/2147483648/2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, |
||||
|
||||
// Plain relative derivation paths
|
||||
{"0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, |
||||
{"128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}}, |
||||
{"0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, |
||||
{"128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}}, |
||||
{"2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, |
||||
|
||||
// Hexadecimal absolute derivation paths
|
||||
{"m/0x2C'/0x3c'/0x00'/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, |
||||
{"m/0x2C'/0x3c'/0x00'/0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}}, |
||||
{"m/0x2C'/0x3c'/0x00'/0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, |
||||
{"m/0x2C'/0x3c'/0x00'/0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}}, |
||||
{"m/0x8000002C/0x8000003c/0x80000000/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, |
||||
{"m/0x8000002C/0x8000003c/0x80000000/0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, |
||||
|
||||
// Hexadecimal relative derivation paths
|
||||
{"0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, |
||||
{"0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}}, |
||||
{"0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, |
||||
{"0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}}, |
||||
{"0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, |
||||
|
||||
// Weird inputs just to ensure they work
|
||||
{" m / 44 '\n/\n 60 \n\n\t' /\n0 ' /\t\t 0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, |
||||
|
||||
// Invaid derivation paths
|
||||
{"", nil}, // Empty relative derivation path
|
||||
{"m", nil}, // Empty absolute derivation path
|
||||
{"m/", nil}, // Missing last derivation component
|
||||
{"/44'/60'/0'/0", nil}, // Absolute path without m prefix, might be user error
|
||||
{"m/2147483648'", nil}, // Overflows 32 bit integer
|
||||
{"m/-1'", nil}, // Cannot contain negative number
|
||||
} |
||||
for i, tt := range tests { |
||||
if path, err := ParseDerivationPath(tt.input); !reflect.DeepEqual(path, tt.output) { |
||||
t.Errorf("test %d: parse mismatch: have %v (%v), want %v", i, path, err, tt.output) |
||||
} else if path == nil && err == nil { |
||||
t.Errorf("test %d: nil path and error: %v", i, err) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,494 @@ |
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package keystore implements encrypted storage of secp256k1 private keys.
|
||||
//
|
||||
// Keys are stored as encrypted JSON files according to the Web3 Secret Storage specification.
|
||||
// See https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition for more information.
|
||||
package keystore |
||||
|
||||
import ( |
||||
"crypto/ecdsa" |
||||
crand "crypto/rand" |
||||
"errors" |
||||
"fmt" |
||||
"math/big" |
||||
"os" |
||||
"path/filepath" |
||||
"reflect" |
||||
"runtime" |
||||
"sync" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/accounts" |
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
"github.com/ethereum/go-ethereum/event" |
||||
) |
||||
|
||||
var ( |
||||
ErrLocked = accounts.NewAuthNeededError("password or unlock") |
||||
ErrNoMatch = errors.New("no key for given address or file") |
||||
ErrDecrypt = errors.New("could not decrypt key with given passphrase") |
||||
) |
||||
|
||||
// KeyStoreType is the reflect type of a keystore backend.
|
||||
var KeyStoreType = reflect.TypeOf(&KeyStore{}) |
||||
|
||||
// KeyStoreScheme is the protocol scheme prefixing account and wallet URLs.
|
||||
var KeyStoreScheme = "keystore" |
||||
|
||||
// Maximum time between wallet refreshes (if filesystem notifications don't work).
|
||||
const walletRefreshCycle = 3 * time.Second |
||||
|
||||
// KeyStore manages a key storage directory on disk.
|
||||
type KeyStore struct { |
||||
storage keyStore // Storage backend, might be cleartext or encrypted
|
||||
cache *accountCache // In-memory account cache over the filesystem storage
|
||||
changes chan struct{} // Channel receiving change notifications from the cache
|
||||
unlocked map[common.Address]*unlocked // Currently unlocked account (decrypted private keys)
|
||||
|
||||
wallets []accounts.Wallet // Wallet wrappers around the individual key files
|
||||
updateFeed event.Feed // Event feed to notify wallet additions/removals
|
||||
updateScope event.SubscriptionScope // Subscription scope tracking current live listeners
|
||||
updating bool // Whether the event notification loop is running
|
||||
|
||||
mu sync.RWMutex |
||||
} |
||||
|
||||
type unlocked struct { |
||||
*Key |
||||
abort chan struct{} |
||||
} |
||||
|
||||
// NewKeyStore creates a keystore for the given directory.
|
||||
func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore { |
||||
keydir, _ = filepath.Abs(keydir) |
||||
ks := &KeyStore{storage: &keyStorePassphrase{keydir, scryptN, scryptP}} |
||||
ks.init(keydir) |
||||
return ks |
||||
} |
||||
|
||||
// NewPlaintextKeyStore creates a keystore for the given directory.
|
||||
// Deprecated: Use NewKeyStore.
|
||||
func NewPlaintextKeyStore(keydir string) *KeyStore { |
||||
keydir, _ = filepath.Abs(keydir) |
||||
ks := &KeyStore{storage: &keyStorePlain{keydir}} |
||||
ks.init(keydir) |
||||
return ks |
||||
} |
||||
|
||||
func (ks *KeyStore) init(keydir string) { |
||||
// Lock the mutex since the account cache might call back with events
|
||||
ks.mu.Lock() |
||||
defer ks.mu.Unlock() |
||||
|
||||
// Initialize the set of unlocked keys and the account cache
|
||||
ks.unlocked = make(map[common.Address]*unlocked) |
||||
ks.cache, ks.changes = newAccountCache(keydir) |
||||
|
||||
// TODO: In order for this finalizer to work, there must be no references
|
||||
// to ks. addressCache doesn't keep a reference but unlocked keys do,
|
||||
// so the finalizer will not trigger until all timed unlocks have expired.
|
||||
runtime.SetFinalizer(ks, func(m *KeyStore) { |
||||
m.cache.close() |
||||
}) |
||||
// Create the initial list of wallets from the cache
|
||||
accs := ks.cache.accounts() |
||||
ks.wallets = make([]accounts.Wallet, len(accs)) |
||||
for i := 0; i < len(accs); i++ { |
||||
ks.wallets[i] = &keystoreWallet{account: accs[i], keystore: ks} |
||||
} |
||||
} |
||||
|
||||
// Wallets implements accounts.Backend, returning all single-key wallets from the
|
||||
// keystore directory.
|
||||
func (ks *KeyStore) Wallets() []accounts.Wallet { |
||||
// Make sure the list of wallets is in sync with the account cache
|
||||
ks.refreshWallets() |
||||
|
||||
ks.mu.RLock() |
||||
defer ks.mu.RUnlock() |
||||
|
||||
cpy := make([]accounts.Wallet, len(ks.wallets)) |
||||
copy(cpy, ks.wallets) |
||||
return cpy |
||||
} |
||||
|
||||
// refreshWallets retrieves the current account list and based on that does any
|
||||
// necessary wallet refreshes.
|
||||
func (ks *KeyStore) refreshWallets() { |
||||
// Retrieve the current list of accounts
|
||||
ks.mu.Lock() |
||||
accs := ks.cache.accounts() |
||||
|
||||
// Transform the current list of wallets into the new one
|
||||
wallets := make([]accounts.Wallet, 0, len(accs)) |
||||
events := []accounts.WalletEvent{} |
||||
|
||||
for _, account := range accs { |
||||
// Drop wallets while they were in front of the next account
|
||||
for len(ks.wallets) > 0 && ks.wallets[0].URL().Cmp(account.URL) < 0 { |
||||
events = append(events, accounts.WalletEvent{Wallet: ks.wallets[0], Arrive: false}) |
||||
ks.wallets = ks.wallets[1:] |
||||
} |
||||
// If there are no more wallets or the account is before the next, wrap new wallet
|
||||
if len(ks.wallets) == 0 || ks.wallets[0].URL().Cmp(account.URL) > 0 { |
||||
wallet := &keystoreWallet{account: account, keystore: ks} |
||||
|
||||
events = append(events, accounts.WalletEvent{Wallet: wallet, Arrive: true}) |
||||
wallets = append(wallets, wallet) |
||||
continue |
||||
} |
||||
// If the account is the same as the first wallet, keep it
|
||||
if ks.wallets[0].Accounts()[0] == account { |
||||
wallets = append(wallets, ks.wallets[0]) |
||||
ks.wallets = ks.wallets[1:] |
||||
continue |
||||
} |
||||
} |
||||
// Drop any leftover wallets and set the new batch
|
||||
for _, wallet := range ks.wallets { |
||||
events = append(events, accounts.WalletEvent{Wallet: wallet, Arrive: false}) |
||||
} |
||||
ks.wallets = wallets |
||||
ks.mu.Unlock() |
||||
|
||||
// Fire all wallet events and return
|
||||
for _, event := range events { |
||||
ks.updateFeed.Send(event) |
||||
} |
||||
} |
||||
|
||||
// Subscribe implements accounts.Backend, creating an async subscription to
|
||||
// receive notifications on the addition or removal of keystore wallets.
|
||||
func (ks *KeyStore) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { |
||||
// We need the mutex to reliably start/stop the update loop
|
||||
ks.mu.Lock() |
||||
defer ks.mu.Unlock() |
||||
|
||||
// Subscribe the caller and track the subscriber count
|
||||
sub := ks.updateScope.Track(ks.updateFeed.Subscribe(sink)) |
||||
|
||||
// Subscribers require an active notification loop, start it
|
||||
if !ks.updating { |
||||
ks.updating = true |
||||
go ks.updater() |
||||
} |
||||
return sub |
||||
} |
||||
|
||||
// updater is responsible for maintaining an up-to-date list of wallets stored in
|
||||
// the keystore, and for firing wallet addition/removal events. It listens for
|
||||
// account change events from the underlying account cache, and also periodically
|
||||
// forces a manual refresh (only triggers for systems where the filesystem notifier
|
||||
// is not running).
|
||||
func (ks *KeyStore) updater() { |
||||
for { |
||||
// Wait for an account update or a refresh timeout
|
||||
select { |
||||
case <-ks.changes: |
||||
case <-time.After(walletRefreshCycle): |
||||
} |
||||
// Run the wallet refresher
|
||||
ks.refreshWallets() |
||||
|
||||
// If all our subscribers left, stop the updater
|
||||
ks.mu.Lock() |
||||
if ks.updateScope.Count() == 0 { |
||||
ks.updating = false |
||||
ks.mu.Unlock() |
||||
return |
||||
} |
||||
ks.mu.Unlock() |
||||
} |
||||
} |
||||
|
||||
// HasAddress reports whether a key with the given address is present.
|
||||
func (ks *KeyStore) HasAddress(addr common.Address) bool { |
||||
return ks.cache.hasAddress(addr) |
||||
} |
||||
|
||||
// Accounts returns all key files present in the directory.
|
||||
func (ks *KeyStore) Accounts() []accounts.Account { |
||||
return ks.cache.accounts() |
||||
} |
||||
|
||||
// Delete deletes the key matched by account if the passphrase is correct.
|
||||
// If the account contains no filename, the address must match a unique key.
|
||||
func (ks *KeyStore) Delete(a accounts.Account, passphrase string) error { |
||||
// Decrypting the key isn't really necessary, but we do
|
||||
// it anyway to check the password and zero out the key
|
||||
// immediately afterwards.
|
||||
a, key, err := ks.getDecryptedKey(a, passphrase) |
||||
if key != nil { |
||||
zeroKey(key.PrivateKey) |
||||
} |
||||
if err != nil { |
||||
return err |
||||
} |
||||
// The order is crucial here. The key is dropped from the
|
||||
// cache after the file is gone so that a reload happening in
|
||||
// between won't insert it into the cache again.
|
||||
err = os.Remove(a.URL.Path) |
||||
if err == nil { |
||||
ks.cache.delete(a) |
||||
ks.refreshWallets() |
||||
} |
||||
return err |
||||
} |
||||
|
||||
// SignHash calculates a ECDSA signature for the given hash. The produced
|
||||
// signature is in the [R || S || V] format where V is 0 or 1.
|
||||
func (ks *KeyStore) SignHash(a accounts.Account, hash []byte) ([]byte, error) { |
||||
// Look up the key to sign with and abort if it cannot be found
|
||||
ks.mu.RLock() |
||||
defer ks.mu.RUnlock() |
||||
|
||||
unlockedKey, found := ks.unlocked[a.Address] |
||||
if !found { |
||||
return nil, ErrLocked |
||||
} |
||||
// Sign the hash using plain ECDSA operations
|
||||
return crypto.Sign(hash, unlockedKey.PrivateKey) |
||||
} |
||||
|
||||
// SignTx signs the given transaction with the requested account.
|
||||
func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { |
||||
// Look up the key to sign with and abort if it cannot be found
|
||||
ks.mu.RLock() |
||||
defer ks.mu.RUnlock() |
||||
|
||||
unlockedKey, found := ks.unlocked[a.Address] |
||||
if !found { |
||||
return nil, ErrLocked |
||||
} |
||||
// Depending on the presence of the chain ID, sign with EIP155 or homestead
|
||||
if chainID != nil { |
||||
return types.SignTx(tx, types.NewEIP155Signer(chainID), unlockedKey.PrivateKey) |
||||
} |
||||
return types.SignTx(tx, types.HomesteadSigner{}, unlockedKey.PrivateKey) |
||||
} |
||||
|
||||
// SignHashWithPassphrase signs hash if the private key matching the given address
|
||||
// can be decrypted with the given passphrase. The produced signature is in the
|
||||
// [R || S || V] format where V is 0 or 1.
|
||||
func (ks *KeyStore) SignHashWithPassphrase(a accounts.Account, passphrase string, hash []byte) (signature []byte, err error) { |
||||
_, key, err := ks.getDecryptedKey(a, passphrase) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer zeroKey(key.PrivateKey) |
||||
return crypto.Sign(hash, key.PrivateKey) |
||||
} |
||||
|
||||
// SignTxWithPassphrase signs the transaction if the private key matching the
|
||||
// given address can be decrypted with the given passphrase.
|
||||
func (ks *KeyStore) SignTxWithPassphrase(a accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { |
||||
_, key, err := ks.getDecryptedKey(a, passphrase) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer zeroKey(key.PrivateKey) |
||||
|
||||
// Depending on the presence of the chain ID, sign with EIP155 or homestead
|
||||
if chainID != nil { |
||||
return types.SignTx(tx, types.NewEIP155Signer(chainID), key.PrivateKey) |
||||
} |
||||
return types.SignTx(tx, types.HomesteadSigner{}, key.PrivateKey) |
||||
} |
||||
|
||||
// Unlock unlocks the given account indefinitely.
|
||||
func (ks *KeyStore) Unlock(a accounts.Account, passphrase string) error { |
||||
return ks.TimedUnlock(a, passphrase, 0) |
||||
} |
||||
|
||||
// Lock removes the private key with the given address from memory.
|
||||
func (ks *KeyStore) Lock(addr common.Address) error { |
||||
ks.mu.Lock() |
||||
if unl, found := ks.unlocked[addr]; found { |
||||
ks.mu.Unlock() |
||||
ks.expire(addr, unl, time.Duration(0)*time.Nanosecond) |
||||
} else { |
||||
ks.mu.Unlock() |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// TimedUnlock unlocks the given account with the passphrase. The account
|
||||
// stays unlocked for the duration of timeout. A timeout of 0 unlocks the account
|
||||
// until the program exits. The account must match a unique key file.
|
||||
//
|
||||
// If the account address is already unlocked for a duration, TimedUnlock extends or
|
||||
// shortens the active unlock timeout. If the address was previously unlocked
|
||||
// indefinitely the timeout is not altered.
|
||||
func (ks *KeyStore) TimedUnlock(a accounts.Account, passphrase string, timeout time.Duration) error { |
||||
a, key, err := ks.getDecryptedKey(a, passphrase) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
ks.mu.Lock() |
||||
defer ks.mu.Unlock() |
||||
u, found := ks.unlocked[a.Address] |
||||
if found { |
||||
if u.abort == nil { |
||||
// The address was unlocked indefinitely, so unlocking
|
||||
// it with a timeout would be confusing.
|
||||
zeroKey(key.PrivateKey) |
||||
return nil |
||||
} |
||||
// Terminate the expire goroutine and replace it below.
|
||||
close(u.abort) |
||||
} |
||||
if timeout > 0 { |
||||
u = &unlocked{Key: key, abort: make(chan struct{})} |
||||
go ks.expire(a.Address, u, timeout) |
||||
} else { |
||||
u = &unlocked{Key: key} |
||||
} |
||||
ks.unlocked[a.Address] = u |
||||
return nil |
||||
} |
||||
|
||||
// Find resolves the given account into a unique entry in the keystore.
|
||||
func (ks *KeyStore) Find(a accounts.Account) (accounts.Account, error) { |
||||
ks.cache.maybeReload() |
||||
ks.cache.mu.Lock() |
||||
a, err := ks.cache.find(a) |
||||
ks.cache.mu.Unlock() |
||||
return a, err |
||||
} |
||||
|
||||
func (ks *KeyStore) getDecryptedKey(a accounts.Account, auth string) (accounts.Account, *Key, error) { |
||||
a, err := ks.Find(a) |
||||
if err != nil { |
||||
return a, nil, err |
||||
} |
||||
key, err := ks.storage.GetKey(a.Address, a.URL.Path, auth) |
||||
return a, key, err |
||||
} |
||||
|
||||
func (ks *KeyStore) expire(addr common.Address, u *unlocked, timeout time.Duration) { |
||||
t := time.NewTimer(timeout) |
||||
defer t.Stop() |
||||
select { |
||||
case <-u.abort: |
||||
// just quit
|
||||
case <-t.C: |
||||
ks.mu.Lock() |
||||
// only drop if it's still the same key instance that dropLater
|
||||
// was launched with. we can check that using pointer equality
|
||||
// because the map stores a new pointer every time the key is
|
||||
// unlocked.
|
||||
if ks.unlocked[addr] == u { |
||||
zeroKey(u.PrivateKey) |
||||
delete(ks.unlocked, addr) |
||||
} |
||||
ks.mu.Unlock() |
||||
} |
||||
} |
||||
|
||||
// NewAccount generates a new key and stores it into the key directory,
|
||||
// encrypting it with the passphrase.
|
||||
func (ks *KeyStore) NewAccount(passphrase string) (accounts.Account, error) { |
||||
_, account, err := storeNewKey(ks.storage, crand.Reader, passphrase) |
||||
if err != nil { |
||||
return accounts.Account{}, err |
||||
} |
||||
// Add the account to the cache immediately rather
|
||||
// than waiting for file system notifications to pick it up.
|
||||
ks.cache.add(account) |
||||
ks.refreshWallets() |
||||
return account, nil |
||||
} |
||||
|
||||
// Export exports as a JSON key, encrypted with newPassphrase.
|
||||
func (ks *KeyStore) Export(a accounts.Account, passphrase, newPassphrase string) (keyJSON []byte, err error) { |
||||
_, key, err := ks.getDecryptedKey(a, passphrase) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
var N, P int |
||||
if store, ok := ks.storage.(*keyStorePassphrase); ok { |
||||
N, P = store.scryptN, store.scryptP |
||||
} else { |
||||
N, P = StandardScryptN, StandardScryptP |
||||
} |
||||
return EncryptKey(key, newPassphrase, N, P) |
||||
} |
||||
|
||||
// Import stores the given encrypted JSON key into the key directory.
|
||||
func (ks *KeyStore) Import(keyJSON []byte, passphrase, newPassphrase string) (accounts.Account, error) { |
||||
key, err := DecryptKey(keyJSON, passphrase) |
||||
if key != nil && key.PrivateKey != nil { |
||||
defer zeroKey(key.PrivateKey) |
||||
} |
||||
if err != nil { |
||||
return accounts.Account{}, err |
||||
} |
||||
return ks.importKey(key, newPassphrase) |
||||
} |
||||
|
||||
// ImportECDSA stores the given key into the key directory, encrypting it with the passphrase.
|
||||
func (ks *KeyStore) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (accounts.Account, error) { |
||||
key := newKeyFromECDSA(priv) |
||||
if ks.cache.hasAddress(key.Address) { |
||||
return accounts.Account{}, fmt.Errorf("account already exists") |
||||
} |
||||
|
||||
return ks.importKey(key, passphrase) |
||||
} |
||||
|
||||
func (ks *KeyStore) importKey(key *Key, passphrase string) (accounts.Account, error) { |
||||
a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.storage.JoinPath(keyFileName(key.Address))}} |
||||
if err := ks.storage.StoreKey(a.URL.Path, key, passphrase); err != nil { |
||||
return accounts.Account{}, err |
||||
} |
||||
ks.cache.add(a) |
||||
ks.refreshWallets() |
||||
return a, nil |
||||
} |
||||
|
||||
// Update changes the passphrase of an existing account.
|
||||
func (ks *KeyStore) Update(a accounts.Account, passphrase, newPassphrase string) error { |
||||
a, key, err := ks.getDecryptedKey(a, passphrase) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return ks.storage.StoreKey(a.URL.Path, key, newPassphrase) |
||||
} |
||||
|
||||
// ImportPreSaleKey decrypts the given Ethereum presale wallet and stores
|
||||
// a key file in the key directory. The key file is encrypted with the same passphrase.
|
||||
func (ks *KeyStore) ImportPreSaleKey(keyJSON []byte, passphrase string) (accounts.Account, error) { |
||||
a, _, err := importPreSaleKey(ks.storage, keyJSON, passphrase) |
||||
if err != nil { |
||||
return a, err |
||||
} |
||||
ks.cache.add(a) |
||||
ks.refreshWallets() |
||||
return a, nil |
||||
} |
||||
|
||||
// zeroKey zeroes a private key in memory.
|
||||
func zeroKey(k *ecdsa.PrivateKey) { |
||||
b := k.D.Bits() |
||||
for i := range b { |
||||
b[i] = 0 |
||||
} |
||||
} |
@ -0,0 +1,365 @@ |
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package keystore |
||||
|
||||
import ( |
||||
"io/ioutil" |
||||
"math/rand" |
||||
"os" |
||||
"runtime" |
||||
"sort" |
||||
"strings" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/accounts" |
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/event" |
||||
) |
||||
|
||||
var testSigData = make([]byte, 32) |
||||
|
||||
func TestKeyStore(t *testing.T) { |
||||
dir, ks := tmpKeyStore(t, true) |
||||
defer os.RemoveAll(dir) |
||||
|
||||
a, err := ks.NewAccount("foo") |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if !strings.HasPrefix(a.URL.Path, dir) { |
||||
t.Errorf("account file %s doesn't have dir prefix", a.URL) |
||||
} |
||||
stat, err := os.Stat(a.URL.Path) |
||||
if err != nil { |
||||
t.Fatalf("account file %s doesn't exist (%v)", a.URL, err) |
||||
} |
||||
if runtime.GOOS != "windows" && stat.Mode() != 0600 { |
||||
t.Fatalf("account file has wrong mode: got %o, want %o", stat.Mode(), 0600) |
||||
} |
||||
if !ks.HasAddress(a.Address) { |
||||
t.Errorf("HasAccount(%x) should've returned true", a.Address) |
||||
} |
||||
if err := ks.Update(a, "foo", "bar"); err != nil { |
||||
t.Errorf("Update error: %v", err) |
||||
} |
||||
if err := ks.Delete(a, "bar"); err != nil { |
||||
t.Errorf("Delete error: %v", err) |
||||
} |
||||
if common.FileExist(a.URL.Path) { |
||||
t.Errorf("account file %s should be gone after Delete", a.URL) |
||||
} |
||||
if ks.HasAddress(a.Address) { |
||||
t.Errorf("HasAccount(%x) should've returned true after Delete", a.Address) |
||||
} |
||||
} |
||||
|
||||
func TestSign(t *testing.T) { |
||||
dir, ks := tmpKeyStore(t, true) |
||||
defer os.RemoveAll(dir) |
||||
|
||||
pass := "" // not used but required by API
|
||||
a1, err := ks.NewAccount(pass) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if err := ks.Unlock(a1, ""); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
} |
||||
|
||||
func TestSignWithPassphrase(t *testing.T) { |
||||
dir, ks := tmpKeyStore(t, true) |
||||
defer os.RemoveAll(dir) |
||||
|
||||
pass := "passwd" |
||||
acc, err := ks.NewAccount(pass) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
if _, unlocked := ks.unlocked[acc.Address]; unlocked { |
||||
t.Fatal("expected account to be locked") |
||||
} |
||||
|
||||
_, err = ks.SignHashWithPassphrase(acc, pass, testSigData) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
if _, unlocked := ks.unlocked[acc.Address]; unlocked { |
||||
t.Fatal("expected account to be locked") |
||||
} |
||||
|
||||
if _, err = ks.SignHashWithPassphrase(acc, "invalid passwd", testSigData); err == nil { |
||||
t.Fatal("expected SignHashWithPassphrase to fail with invalid password") |
||||
} |
||||
} |
||||
|
||||
func TestTimedUnlock(t *testing.T) { |
||||
dir, ks := tmpKeyStore(t, true) |
||||
defer os.RemoveAll(dir) |
||||
|
||||
pass := "foo" |
||||
a1, err := ks.NewAccount(pass) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
// Signing without passphrase fails because account is locked
|
||||
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) |
||||
if err != ErrLocked { |
||||
t.Fatal("Signing should've failed with ErrLocked before unlocking, got ", err) |
||||
} |
||||
|
||||
// Signing with passphrase works
|
||||
if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
// Signing without passphrase works because account is temp unlocked
|
||||
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) |
||||
if err != nil { |
||||
t.Fatal("Signing shouldn't return an error after unlocking, got ", err) |
||||
} |
||||
|
||||
// Signing fails again after automatic locking
|
||||
time.Sleep(250 * time.Millisecond) |
||||
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) |
||||
if err != ErrLocked { |
||||
t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) |
||||
} |
||||
} |
||||
|
||||
func TestOverrideUnlock(t *testing.T) { |
||||
dir, ks := tmpKeyStore(t, false) |
||||
defer os.RemoveAll(dir) |
||||
|
||||
pass := "foo" |
||||
a1, err := ks.NewAccount(pass) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
// Unlock indefinitely.
|
||||
if err = ks.TimedUnlock(a1, pass, 5*time.Minute); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
// Signing without passphrase works because account is temp unlocked
|
||||
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) |
||||
if err != nil { |
||||
t.Fatal("Signing shouldn't return an error after unlocking, got ", err) |
||||
} |
||||
|
||||
// reset unlock to a shorter period, invalidates the previous unlock
|
||||
if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
// Signing without passphrase still works because account is temp unlocked
|
||||
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) |
||||
if err != nil { |
||||
t.Fatal("Signing shouldn't return an error after unlocking, got ", err) |
||||
} |
||||
|
||||
// Signing fails again after automatic locking
|
||||
time.Sleep(250 * time.Millisecond) |
||||
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData) |
||||
if err != ErrLocked { |
||||
t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err) |
||||
} |
||||
} |
||||
|
||||
// This test should fail under -race if signing races the expiration goroutine.
|
||||
func TestSignRace(t *testing.T) { |
||||
dir, ks := tmpKeyStore(t, false) |
||||
defer os.RemoveAll(dir) |
||||
|
||||
// Create a test account.
|
||||
a1, err := ks.NewAccount("") |
||||
if err != nil { |
||||
t.Fatal("could not create the test account", err) |
||||
} |
||||
|
||||
if err := ks.TimedUnlock(a1, "", 15*time.Millisecond); err != nil { |
||||
t.Fatal("could not unlock the test account", err) |
||||
} |
||||
end := time.Now().Add(500 * time.Millisecond) |
||||
for time.Now().Before(end) { |
||||
if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err == ErrLocked { |
||||
return |
||||
} else if err != nil { |
||||
t.Errorf("Sign error: %v", err) |
||||
return |
||||
} |
||||
time.Sleep(1 * time.Millisecond) |
||||
} |
||||
t.Errorf("Account did not lock within the timeout") |
||||
} |
||||
|
||||
// Tests that the wallet notifier loop starts and stops correctly based on the
|
||||
// addition and removal of wallet event subscriptions.
|
||||
func TestWalletNotifierLifecycle(t *testing.T) { |
||||
// Create a temporary kesytore to test with
|
||||
dir, ks := tmpKeyStore(t, false) |
||||
defer os.RemoveAll(dir) |
||||
|
||||
// Ensure that the notification updater is not running yet
|
||||
time.Sleep(250 * time.Millisecond) |
||||
ks.mu.RLock() |
||||
updating := ks.updating |
||||
ks.mu.RUnlock() |
||||
|
||||
if updating { |
||||
t.Errorf("wallet notifier running without subscribers") |
||||
} |
||||
// Subscribe to the wallet feed and ensure the updater boots up
|
||||
updates := make(chan accounts.WalletEvent) |
||||
|
||||
subs := make([]event.Subscription, 2) |
||||
for i := 0; i < len(subs); i++ { |
||||
// Create a new subscription
|
||||
subs[i] = ks.Subscribe(updates) |
||||
|
||||
// Ensure the notifier comes online
|
||||
time.Sleep(250 * time.Millisecond) |
||||
ks.mu.RLock() |
||||
updating = ks.updating |
||||
ks.mu.RUnlock() |
||||
|
||||
if !updating { |
||||
t.Errorf("sub %d: wallet notifier not running after subscription", i) |
||||
} |
||||
} |
||||
// Unsubscribe and ensure the updater terminates eventually
|
||||
for i := 0; i < len(subs); i++ { |
||||
// Close an existing subscription
|
||||
subs[i].Unsubscribe() |
||||
|
||||
// Ensure the notifier shuts down at and only at the last close
|
||||
for k := 0; k < int(walletRefreshCycle/(250*time.Millisecond))+2; k++ { |
||||
ks.mu.RLock() |
||||
updating = ks.updating |
||||
ks.mu.RUnlock() |
||||
|
||||
if i < len(subs)-1 && !updating { |
||||
t.Fatalf("sub %d: event notifier stopped prematurely", i) |
||||
} |
||||
if i == len(subs)-1 && !updating { |
||||
return |
||||
} |
||||
time.Sleep(250 * time.Millisecond) |
||||
} |
||||
} |
||||
t.Errorf("wallet notifier didn't terminate after unsubscribe") |
||||
} |
||||
|
||||
// Tests that wallet notifications and correctly fired when accounts are added
|
||||
// or deleted from the keystore.
|
||||
func TestWalletNotifications(t *testing.T) { |
||||
// Create a temporary kesytore to test with
|
||||
dir, ks := tmpKeyStore(t, false) |
||||
defer os.RemoveAll(dir) |
||||
|
||||
// Subscribe to the wallet feed
|
||||
updates := make(chan accounts.WalletEvent, 1) |
||||
sub := ks.Subscribe(updates) |
||||
defer sub.Unsubscribe() |
||||
|
||||
// Randomly add and remove account and make sure events and wallets are in sync
|
||||
live := make(map[common.Address]accounts.Account) |
||||
for i := 0; i < 1024; i++ { |
||||
// Execute a creation or deletion and ensure event arrival
|
||||
if create := len(live) == 0 || rand.Int()%4 > 0; create { |
||||
// Add a new account and ensure wallet notifications arrives
|
||||
account, err := ks.NewAccount("") |
||||
if err != nil { |
||||
t.Fatalf("failed to create test account: %v", err) |
||||
} |
||||
select { |
||||
case event := <-updates: |
||||
if !event.Arrive { |
||||
t.Errorf("departure event on account creation") |
||||
} |
||||
if event.Wallet.Accounts()[0] != account { |
||||
t.Errorf("account mismatch on created wallet: have %v, want %v", event.Wallet.Accounts()[0], account) |
||||
} |
||||
default: |
||||
t.Errorf("wallet arrival event not fired on account creation") |
||||
} |
||||
live[account.Address] = account |
||||
} else { |
||||
// Select a random account to delete (crude, but works)
|
||||
var account accounts.Account |
||||
for _, a := range live { |
||||
account = a |
||||
break |
||||
} |
||||
// Remove an account and ensure wallet notifiaction arrives
|
||||
if err := ks.Delete(account, ""); err != nil { |
||||
t.Fatalf("failed to delete test account: %v", err) |
||||
} |
||||
select { |
||||
case event := <-updates: |
||||
if event.Arrive { |
||||
t.Errorf("arrival event on account deletion") |
||||
} |
||||
if event.Wallet.Accounts()[0] != account { |
||||
t.Errorf("account mismatch on deleted wallet: have %v, want %v", event.Wallet.Accounts()[0], account) |
||||
} |
||||
default: |
||||
t.Errorf("wallet departure event not fired on account creation") |
||||
} |
||||
delete(live, account.Address) |
||||
} |
||||
// Retrieve the list of wallets and ensure it matches with our required live set
|
||||
liveList := make([]accounts.Account, 0, len(live)) |
||||
for _, account := range live { |
||||
liveList = append(liveList, account) |
||||
} |
||||
sort.Sort(accountsByURL(liveList)) |
||||
|
||||
wallets := ks.Wallets() |
||||
if len(liveList) != len(wallets) { |
||||
t.Errorf("wallet list doesn't match required accounts: have %v, want %v", wallets, liveList) |
||||
} else { |
||||
for j, wallet := range wallets { |
||||
if accs := wallet.Accounts(); len(accs) != 1 { |
||||
t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs)) |
||||
} else if accs[0] != liveList[j] { |
||||
t.Errorf("wallet %d: account mismatch: have %v, want %v", j, accs[0], liveList[j]) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func tmpKeyStore(t *testing.T, encrypted bool) (string, *KeyStore) { |
||||
d, err := ioutil.TempDir("", "eth-keystore-test") |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
new := NewPlaintextKeyStore |
||||
if encrypted { |
||||
new = func(kd string) *KeyStore { return NewKeyStore(kd, veryLightScryptN, veryLightScryptP) } |
||||
} |
||||
return d, new(d) |
||||
} |
@ -0,0 +1,139 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package keystore |
||||
|
||||
import ( |
||||
"math/big" |
||||
|
||||
ethereum "github.com/ethereum/go-ethereum" |
||||
"github.com/ethereum/go-ethereum/accounts" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
) |
||||
|
||||
// keystoreWallet implements the accounts.Wallet interface for the original
|
||||
// keystore.
|
||||
type keystoreWallet struct { |
||||
account accounts.Account // Single account contained in this wallet
|
||||
keystore *KeyStore // Keystore where the account originates from
|
||||
} |
||||
|
||||
// URL implements accounts.Wallet, returning the URL of the account within.
|
||||
func (w *keystoreWallet) URL() accounts.URL { |
||||
return w.account.URL |
||||
} |
||||
|
||||
// Status implements accounts.Wallet, always returning "open", since there is no
|
||||
// concept of open/close for plain keystore accounts.
|
||||
func (w *keystoreWallet) Status() string { |
||||
w.keystore.mu.RLock() |
||||
defer w.keystore.mu.RUnlock() |
||||
|
||||
if _, ok := w.keystore.unlocked[w.account.Address]; ok { |
||||
return "Unlocked" |
||||
} |
||||
return "Locked" |
||||
} |
||||
|
||||
// Open implements accounts.Wallet, but is a noop for plain wallets since there
|
||||
// is no connection or decryption step necessary to access the list of accounts.
|
||||
func (w *keystoreWallet) Open(passphrase string) error { return nil } |
||||
|
||||
// Close implements accounts.Wallet, but is a noop for plain wallets since is no
|
||||
// meaningful open operation.
|
||||
func (w *keystoreWallet) Close() error { return nil } |
||||
|
||||
// Accounts implements accounts.Wallet, returning an account list consisting of
|
||||
// a single account that the plain kestore wallet contains.
|
||||
func (w *keystoreWallet) Accounts() []accounts.Account { |
||||
return []accounts.Account{w.account} |
||||
} |
||||
|
||||
// Contains implements accounts.Wallet, returning whether a particular account is
|
||||
// or is not wrapped by this wallet instance.
|
||||
func (w *keystoreWallet) Contains(account accounts.Account) bool { |
||||
return account.Address == w.account.Address && (account.URL == (accounts.URL{}) || account.URL == w.account.URL) |
||||
} |
||||
|
||||
// Derive implements accounts.Wallet, but is a noop for plain wallets since there
|
||||
// is no notion of hierarchical account derivation for plain keystore accounts.
|
||||
func (w *keystoreWallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { |
||||
return accounts.Account{}, accounts.ErrNotSupported |
||||
} |
||||
|
||||
// SelfDerive implements accounts.Wallet, but is a noop for plain wallets since
|
||||
// there is no notion of hierarchical account derivation for plain keystore accounts.
|
||||
func (w *keystoreWallet) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainStateReader) {} |
||||
|
||||
// SignHash implements accounts.Wallet, attempting to sign the given hash with
|
||||
// the given account. If the wallet does not wrap this particular account, an
|
||||
// error is returned to avoid account leakage (even though in theory we may be
|
||||
// able to sign via our shared keystore backend).
|
||||
func (w *keystoreWallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) { |
||||
// Make sure the requested account is contained within
|
||||
if account.Address != w.account.Address { |
||||
return nil, accounts.ErrUnknownAccount |
||||
} |
||||
if account.URL != (accounts.URL{}) && account.URL != w.account.URL { |
||||
return nil, accounts.ErrUnknownAccount |
||||
} |
||||
// Account seems valid, request the keystore to sign
|
||||
return w.keystore.SignHash(account, hash) |
||||
} |
||||
|
||||
// SignTx implements accounts.Wallet, attempting to sign the given transaction
|
||||
// with the given account. If the wallet does not wrap this particular account,
|
||||
// an error is returned to avoid account leakage (even though in theory we may
|
||||
// be able to sign via our shared keystore backend).
|
||||
func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { |
||||
// Make sure the requested account is contained within
|
||||
if account.Address != w.account.Address { |
||||
return nil, accounts.ErrUnknownAccount |
||||
} |
||||
if account.URL != (accounts.URL{}) && account.URL != w.account.URL { |
||||
return nil, accounts.ErrUnknownAccount |
||||
} |
||||
// Account seems valid, request the keystore to sign
|
||||
return w.keystore.SignTx(account, tx, chainID) |
||||
} |
||||
|
||||
// SignHashWithPassphrase implements accounts.Wallet, attempting to sign the
|
||||
// given hash with the given account using passphrase as extra authentication.
|
||||
func (w *keystoreWallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) { |
||||
// Make sure the requested account is contained within
|
||||
if account.Address != w.account.Address { |
||||
return nil, accounts.ErrUnknownAccount |
||||
} |
||||
if account.URL != (accounts.URL{}) && account.URL != w.account.URL { |
||||
return nil, accounts.ErrUnknownAccount |
||||
} |
||||
// Account seems valid, request the keystore to sign
|
||||
return w.keystore.SignHashWithPassphrase(account, passphrase, hash) |
||||
} |
||||
|
||||
// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
|
||||
// transaction with the given account using passphrase as extra authentication.
|
||||
func (w *keystoreWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { |
||||
// Make sure the requested account is contained within
|
||||
if account.Address != w.account.Address { |
||||
return nil, accounts.ErrUnknownAccount |
||||
} |
||||
if account.URL != (accounts.URL{}) && account.URL != w.account.URL { |
||||
return nil, accounts.ErrUnknownAccount |
||||
} |
||||
// Account seems valid, request the keystore to sign
|
||||
return w.keystore.SignTxWithPassphrase(account, passphrase, tx, chainID) |
||||
} |
@ -0,0 +1,198 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package accounts |
||||
|
||||
import ( |
||||
"reflect" |
||||
"sort" |
||||
"sync" |
||||
|
||||
"github.com/ethereum/go-ethereum/event" |
||||
) |
||||
|
||||
// Manager is an overarching account manager that can communicate with various
|
||||
// backends for signing transactions.
|
||||
type Manager struct { |
||||
backends map[reflect.Type][]Backend // Index of backends currently registered
|
||||
updaters []event.Subscription // Wallet update subscriptions for all backends
|
||||
updates chan WalletEvent // Subscription sink for backend wallet changes
|
||||
wallets []Wallet // Cache of all wallets from all registered backends
|
||||
|
||||
feed event.Feed // Wallet feed notifying of arrivals/departures
|
||||
|
||||
quit chan chan error |
||||
lock sync.RWMutex |
||||
} |
||||
|
||||
// NewManager creates a generic account manager to sign transaction via various
|
||||
// supported backends.
|
||||
func NewManager(backends ...Backend) *Manager { |
||||
// Subscribe to wallet notifications from all backends
|
||||
updates := make(chan WalletEvent, 4*len(backends)) |
||||
|
||||
subs := make([]event.Subscription, len(backends)) |
||||
for i, backend := range backends { |
||||
subs[i] = backend.Subscribe(updates) |
||||
} |
||||
// Retrieve the initial list of wallets from the backends and sort by URL
|
||||
var wallets []Wallet |
||||
for _, backend := range backends { |
||||
wallets = merge(wallets, backend.Wallets()...) |
||||
} |
||||
// Assemble the account manager and return
|
||||
am := &Manager{ |
||||
backends: make(map[reflect.Type][]Backend), |
||||
updaters: subs, |
||||
updates: updates, |
||||
wallets: wallets, |
||||
quit: make(chan chan error), |
||||
} |
||||
for _, backend := range backends { |
||||
kind := reflect.TypeOf(backend) |
||||
am.backends[kind] = append(am.backends[kind], backend) |
||||
} |
||||
go am.update() |
||||
|
||||
return am |
||||
} |
||||
|
||||
// Close terminates the account manager's internal notification processes.
|
||||
func (am *Manager) Close() error { |
||||
errc := make(chan error) |
||||
am.quit <- errc |
||||
return <-errc |
||||
} |
||||
|
||||
// update is the wallet event loop listening for notifications from the backends
|
||||
// and updating the cache of wallets.
|
||||
func (am *Manager) update() { |
||||
// Close all subscriptions when the manager terminates
|
||||
defer func() { |
||||
am.lock.Lock() |
||||
for _, sub := range am.updaters { |
||||
sub.Unsubscribe() |
||||
} |
||||
am.updaters = nil |
||||
am.lock.Unlock() |
||||
}() |
||||
|
||||
// Loop until termination
|
||||
for { |
||||
select { |
||||
case event := <-am.updates: |
||||
// Wallet event arrived, update local cache
|
||||
am.lock.Lock() |
||||
if event.Arrive { |
||||
am.wallets = merge(am.wallets, event.Wallet) |
||||
} else { |
||||
am.wallets = drop(am.wallets, event.Wallet) |
||||
} |
||||
am.lock.Unlock() |
||||
|
||||
// Notify any listeners of the event
|
||||
am.feed.Send(event) |
||||
|
||||
case errc := <-am.quit: |
||||
// Manager terminating, return
|
||||
errc <- nil |
||||
return |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Backends retrieves the backend(s) with the given type from the account manager.
|
||||
func (am *Manager) Backends(kind reflect.Type) []Backend { |
||||
return am.backends[kind] |
||||
} |
||||
|
||||
// Wallets returns all signer accounts registered under this account manager.
|
||||
func (am *Manager) Wallets() []Wallet { |
||||
am.lock.RLock() |
||||
defer am.lock.RUnlock() |
||||
|
||||
cpy := make([]Wallet, len(am.wallets)) |
||||
copy(cpy, am.wallets) |
||||
return cpy |
||||
} |
||||
|
||||
// Wallet retrieves the wallet associated with a particular URL.
|
||||
func (am *Manager) Wallet(url string) (Wallet, error) { |
||||
am.lock.RLock() |
||||
defer am.lock.RUnlock() |
||||
|
||||
parsed, err := parseURL(url) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
for _, wallet := range am.Wallets() { |
||||
if wallet.URL() == parsed { |
||||
return wallet, nil |
||||
} |
||||
} |
||||
return nil, ErrUnknownWallet |
||||
} |
||||
|
||||
// Find attempts to locate the wallet corresponding to a specific account. Since
|
||||
// accounts can be dynamically added to and removed from wallets, this method has
|
||||
// a linear runtime in the number of wallets.
|
||||
func (am *Manager) Find(account Account) (Wallet, error) { |
||||
am.lock.RLock() |
||||
defer am.lock.RUnlock() |
||||
|
||||
for _, wallet := range am.wallets { |
||||
if wallet.Contains(account) { |
||||
return wallet, nil |
||||
} |
||||
} |
||||
return nil, ErrUnknownAccount |
||||
} |
||||
|
||||
// Subscribe creates an async subscription to receive notifications when the
|
||||
// manager detects the arrival or departure of a wallet from any of its backends.
|
||||
func (am *Manager) Subscribe(sink chan<- WalletEvent) event.Subscription { |
||||
return am.feed.Subscribe(sink) |
||||
} |
||||
|
||||
// merge is a sorted analogue of append for wallets, where the ordering of the
|
||||
// origin list is preserved by inserting new wallets at the correct position.
|
||||
//
|
||||
// The original slice is assumed to be already sorted by URL.
|
||||
func merge(slice []Wallet, wallets ...Wallet) []Wallet { |
||||
for _, wallet := range wallets { |
||||
n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 }) |
||||
if n == len(slice) { |
||||
slice = append(slice, wallet) |
||||
continue |
||||
} |
||||
slice = append(slice[:n], append([]Wallet{wallet}, slice[n:]...)...) |
||||
} |
||||
return slice |
||||
} |
||||
|
||||
// drop is the couterpart of merge, which looks up wallets from within the sorted
|
||||
// cache and removes the ones specified.
|
||||
func drop(slice []Wallet, wallets ...Wallet) []Wallet { |
||||
for _, wallet := range wallets { |
||||
n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 }) |
||||
if n == len(slice) { |
||||
// Wallet not found, may happen during startup
|
||||
continue |
||||
} |
||||
slice = append(slice[:n], slice[n+1:]...) |
||||
} |
||||
return slice |
||||
} |
@ -0,0 +1,79 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package accounts |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"errors" |
||||
"fmt" |
||||
"strings" |
||||
) |
||||
|
||||
// URL represents the canonical identification URL of a wallet or account.
|
||||
//
|
||||
// It is a simplified version of url.URL, with the important limitations (which
|
||||
// are considered features here) that it contains value-copyable components only,
|
||||
// as well as that it doesn't do any URL encoding/decoding of special characters.
|
||||
//
|
||||
// The former is important to allow an account to be copied without leaving live
|
||||
// references to the original version, whereas the latter is important to ensure
|
||||
// one single canonical form opposed to many allowed ones by the RFC 3986 spec.
|
||||
//
|
||||
// As such, these URLs should not be used outside of the scope of an Ethereum
|
||||
// wallet or account.
|
||||
type URL struct { |
||||
Scheme string // Protocol scheme to identify a capable account backend
|
||||
Path string // Path for the backend to identify a unique entity
|
||||
} |
||||
|
||||
// parseURL converts a user supplied URL into the accounts specific structure.
|
||||
func parseURL(url string) (URL, error) { |
||||
parts := strings.Split(url, "://") |
||||
if len(parts) != 2 || parts[0] == "" { |
||||
return URL{}, errors.New("protocol scheme missing") |
||||
} |
||||
return URL{ |
||||
Scheme: parts[0], |
||||
Path: parts[1], |
||||
}, nil |
||||
} |
||||
|
||||
// String implements the stringer interface.
|
||||
func (u URL) String() string { |
||||
if u.Scheme != "" { |
||||
return fmt.Sprintf("%s://%s", u.Scheme, u.Path) |
||||
} |
||||
return u.Path |
||||
} |
||||
|
||||
// MarshalJSON implements the json.Marshaller interface.
|
||||
func (u URL) MarshalJSON() ([]byte, error) { |
||||
return json.Marshal(u.String()) |
||||
} |
||||
|
||||
// Cmp compares x and y and returns:
|
||||
//
|
||||
// -1 if x < y
|
||||
// 0 if x == y
|
||||
// +1 if x > y
|
||||
//
|
||||
func (u URL) Cmp(url URL) int { |
||||
if u.Scheme == url.Scheme { |
||||
return strings.Compare(u.Path, url.Path) |
||||
} |
||||
return strings.Compare(u.Scheme, url.Scheme) |
||||
} |
@ -0,0 +1,209 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// This file contains the implementation for interacting with the Ledger hardware
|
||||
// wallets. The wire protocol spec can be found in the Ledger Blue GitHub repo:
|
||||
// https://raw.githubusercontent.com/LedgerHQ/blue-app-eth/master/doc/ethapp.asc
|
||||
|
||||
// +build !ios
|
||||
|
||||
package usbwallet |
||||
|
||||
import ( |
||||
"fmt" |
||||
"sync" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/accounts" |
||||
"github.com/ethereum/go-ethereum/event" |
||||
"github.com/karalabe/gousb/usb" |
||||
) |
||||
|
||||
// LedgerScheme is the protocol scheme prefixing account and wallet URLs.
|
||||
var LedgerScheme = "ledger" |
||||
|
||||
// ledgerDeviceIDs are the known device IDs that Ledger wallets use.
|
||||
var ledgerDeviceIDs = []deviceID{ |
||||
{Vendor: 0x2c97, Product: 0x0000}, // Ledger Blue
|
||||
{Vendor: 0x2c97, Product: 0x0001}, // Ledger Nano S
|
||||
} |
||||
|
||||
// Maximum time between wallet refreshes (if USB hotplug notifications don't work).
|
||||
const ledgerRefreshCycle = time.Second |
||||
|
||||
// Minimum time between wallet refreshes to avoid USB trashing.
|
||||
const ledgerRefreshThrottling = 500 * time.Millisecond |
||||
|
||||
// LedgerHub is a accounts.Backend that can find and handle Ledger hardware wallets.
|
||||
type LedgerHub struct { |
||||
ctx *usb.Context // Context interfacing with a libusb instance
|
||||
|
||||
refreshed time.Time // Time instance when the list of wallets was last refreshed
|
||||
wallets []accounts.Wallet // List of Ledger devices currently tracking
|
||||
updateFeed event.Feed // Event feed to notify wallet additions/removals
|
||||
updateScope event.SubscriptionScope // Subscription scope tracking current live listeners
|
||||
updating bool // Whether the event notification loop is running
|
||||
|
||||
quit chan chan error |
||||
lock sync.RWMutex |
||||
} |
||||
|
||||
// NewLedgerHub creates a new hardware wallet manager for Ledger devices.
|
||||
func NewLedgerHub() (*LedgerHub, error) { |
||||
// Initialize the USB library to access Ledgers through
|
||||
ctx, err := usb.NewContext() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
// Create the USB hub, start and return it
|
||||
hub := &LedgerHub{ |
||||
ctx: ctx, |
||||
quit: make(chan chan error), |
||||
} |
||||
hub.refreshWallets() |
||||
|
||||
return hub, nil |
||||
} |
||||
|
||||
// Wallets implements accounts.Backend, returning all the currently tracked USB
|
||||
// devices that appear to be Ledger hardware wallets.
|
||||
func (hub *LedgerHub) Wallets() []accounts.Wallet { |
||||
// Make sure the list of wallets is up to date
|
||||
hub.refreshWallets() |
||||
|
||||
hub.lock.RLock() |
||||
defer hub.lock.RUnlock() |
||||
|
||||
cpy := make([]accounts.Wallet, len(hub.wallets)) |
||||
copy(cpy, hub.wallets) |
||||
return cpy |
||||
} |
||||
|
||||
// refreshWallets scans the USB devices attached to the machine and updates the
|
||||
// list of wallets based on the found devices.
|
||||
func (hub *LedgerHub) refreshWallets() { |
||||
// Don't scan the USB like crazy it the user fetches wallets in a loop
|
||||
hub.lock.RLock() |
||||
elapsed := time.Since(hub.refreshed) |
||||
hub.lock.RUnlock() |
||||
|
||||
if elapsed < ledgerRefreshThrottling { |
||||
return |
||||
} |
||||
// Retrieve the current list of Ledger devices
|
||||
var devIDs []deviceID |
||||
var busIDs []uint16 |
||||
|
||||
hub.ctx.ListDevices(func(desc *usb.Descriptor) bool { |
||||
// Gather Ledger devices, don't connect any just yet
|
||||
for _, id := range ledgerDeviceIDs { |
||||
if desc.Vendor == id.Vendor && desc.Product == id.Product { |
||||
devIDs = append(devIDs, deviceID{Vendor: desc.Vendor, Product: desc.Product}) |
||||
busIDs = append(busIDs, uint16(desc.Bus)<<8+uint16(desc.Address)) |
||||
return false |
||||
} |
||||
} |
||||
// Not ledger, ignore and don't connect either
|
||||
return false |
||||
}) |
||||
// Transform the current list of wallets into the new one
|
||||
hub.lock.Lock() |
||||
|
||||
wallets := make([]accounts.Wallet, 0, len(devIDs)) |
||||
events := []accounts.WalletEvent{} |
||||
|
||||
for i := 0; i < len(devIDs); i++ { |
||||
devID, busID := devIDs[i], busIDs[i] |
||||
|
||||
url := accounts.URL{Scheme: LedgerScheme, Path: fmt.Sprintf("%03d:%03d", busID>>8, busID&0xff)} |
||||
|
||||
// Drop wallets in front of the next device or those that failed for some reason
|
||||
for len(hub.wallets) > 0 && (hub.wallets[0].URL().Cmp(url) < 0 || hub.wallets[0].(*ledgerWallet).failed()) { |
||||
events = append(events, accounts.WalletEvent{Wallet: hub.wallets[0], Arrive: false}) |
||||
hub.wallets = hub.wallets[1:] |
||||
} |
||||
// If there are no more wallets or the device is before the next, wrap new wallet
|
||||
if len(hub.wallets) == 0 || hub.wallets[0].URL().Cmp(url) > 0 { |
||||
wallet := &ledgerWallet{context: hub.ctx, hardwareID: devID, locationID: busID, url: &url} |
||||
|
||||
events = append(events, accounts.WalletEvent{Wallet: wallet, Arrive: true}) |
||||
wallets = append(wallets, wallet) |
||||
continue |
||||
} |
||||
// If the device is the same as the first wallet, keep it
|
||||
if hub.wallets[0].URL().Cmp(url) == 0 { |
||||
wallets = append(wallets, hub.wallets[0]) |
||||
hub.wallets = hub.wallets[1:] |
||||
continue |
||||
} |
||||
} |
||||
// Drop any leftover wallets and set the new batch
|
||||
for _, wallet := range hub.wallets { |
||||
events = append(events, accounts.WalletEvent{Wallet: wallet, Arrive: false}) |
||||
} |
||||
hub.refreshed = time.Now() |
||||
hub.wallets = wallets |
||||
hub.lock.Unlock() |
||||
|
||||
// Fire all wallet events and return
|
||||
for _, event := range events { |
||||
hub.updateFeed.Send(event) |
||||
} |
||||
} |
||||
|
||||
// Subscribe implements accounts.Backend, creating an async subscription to
|
||||
// receive notifications on the addition or removal of Ledger wallets.
|
||||
func (hub *LedgerHub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { |
||||
// We need the mutex to reliably start/stop the update loop
|
||||
hub.lock.Lock() |
||||
defer hub.lock.Unlock() |
||||
|
||||
// Subscribe the caller and track the subscriber count
|
||||
sub := hub.updateScope.Track(hub.updateFeed.Subscribe(sink)) |
||||
|
||||
// Subscribers require an active notification loop, start it
|
||||
if !hub.updating { |
||||
hub.updating = true |
||||
go hub.updater() |
||||
} |
||||
return sub |
||||
} |
||||
|
||||
// updater is responsible for maintaining an up-to-date list of wallets stored in
|
||||
// the keystore, and for firing wallet addition/removal events. It listens for
|
||||
// account change events from the underlying account cache, and also periodically
|
||||
// forces a manual refresh (only triggers for systems where the filesystem notifier
|
||||
// is not running).
|
||||
func (hub *LedgerHub) updater() { |
||||
for { |
||||
// Wait for a USB hotplug event (not supported yet) or a refresh timeout
|
||||
select { |
||||
//case <-hub.changes: // reenable on hutplug implementation
|
||||
case <-time.After(ledgerRefreshCycle): |
||||
} |
||||
// Run the wallet refresher
|
||||
hub.refreshWallets() |
||||
|
||||
// If all our subscribers left, stop the updater
|
||||
hub.lock.Lock() |
||||
if hub.updateScope.Count() == 0 { |
||||
hub.updating = false |
||||
hub.lock.Unlock() |
||||
return |
||||
} |
||||
hub.lock.Unlock() |
||||
} |
||||
} |
@ -0,0 +1,945 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// This file contains the implementation for interacting with the Ledger hardware
|
||||
// wallets. The wire protocol spec can be found in the Ledger Blue GitHub repo:
|
||||
// https://raw.githubusercontent.com/LedgerHQ/blue-app-eth/master/doc/ethapp.asc
|
||||
|
||||
// +build !ios
|
||||
|
||||
package usbwallet |
||||
|
||||
import ( |
||||
"encoding/binary" |
||||
"encoding/hex" |
||||
"errors" |
||||
"fmt" |
||||
"io" |
||||
"math/big" |
||||
"sync" |
||||
"time" |
||||
|
||||
ethereum "github.com/ethereum/go-ethereum" |
||||
"github.com/ethereum/go-ethereum/accounts" |
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/logger" |
||||
"github.com/ethereum/go-ethereum/logger/glog" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
"github.com/karalabe/gousb/usb" |
||||
"golang.org/x/net/context" |
||||
) |
||||
|
||||
// Maximum time between wallet health checks to detect USB unplugs.
|
||||
const ledgerHeartbeatCycle = time.Second |
||||
|
||||
// Minimum time to wait between self derivation attempts, even it the user is
|
||||
// requesting accounts like crazy.
|
||||
const ledgerSelfDeriveThrottling = time.Second |
||||
|
||||
// ledgerOpcode is an enumeration encoding the supported Ledger opcodes.
|
||||
type ledgerOpcode byte |
||||
|
||||
// ledgerParam1 is an enumeration encoding the supported Ledger parameters for
|
||||
// specific opcodes. The same parameter values may be reused between opcodes.
|
||||
type ledgerParam1 byte |
||||
|
||||
// ledgerParam2 is an enumeration encoding the supported Ledger parameters for
|
||||
// specific opcodes. The same parameter values may be reused between opcodes.
|
||||
type ledgerParam2 byte |
||||
|
||||
const ( |
||||
ledgerOpRetrieveAddress ledgerOpcode = 0x02 // Returns the public key and Ethereum address for a given BIP 32 path
|
||||
ledgerOpSignTransaction ledgerOpcode = 0x04 // Signs an Ethereum transaction after having the user validate the parameters
|
||||
ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration
|
||||
|
||||
ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet
|
||||
ledgerP1ConfirmFetchAddress ledgerParam1 = 0x01 // Require a user confirmation before returning the address
|
||||
ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing
|
||||
ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent transaction data block for signing
|
||||
ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address
|
||||
ledgerP2ReturnAddressChainCode ledgerParam2 = 0x01 // Require a user confirmation before returning the address
|
||||
) |
||||
|
||||
// errReplyInvalidHeader is the error message returned by a Ledfer data exchange
|
||||
// if the device replies with a mismatching header. This usually means the device
|
||||
// is in browser mode.
|
||||
var errReplyInvalidHeader = errors.New("invalid reply header") |
||||
|
||||
// ledgerWallet represents a live USB Ledger hardware wallet.
|
||||
type ledgerWallet struct { |
||||
context *usb.Context // USB context to interface libusb through
|
||||
hardwareID deviceID // USB identifiers to identify this device type
|
||||
locationID uint16 // USB bus and address to identify this device instance
|
||||
url *accounts.URL // Textual URL uniquely identifying this wallet
|
||||
|
||||
device *usb.Device // USB device advertising itself as a Ledger wallet
|
||||
input usb.Endpoint // Input endpoint to send data to this device
|
||||
output usb.Endpoint // Output endpoint to receive data from this device
|
||||
failure error // Any failure that would make the device unusable
|
||||
|
||||
version [3]byte // Current version of the Ledger Ethereum app (zero if app is offline)
|
||||
browser bool // Flag whether the Ledger is in browser mode (reply channel mismatch)
|
||||
accounts []accounts.Account // List of derive accounts pinned on the Ledger
|
||||
paths map[common.Address]accounts.DerivationPath // Known derivation paths for signing operations
|
||||
|
||||
deriveNextPath accounts.DerivationPath // Next derivation path for account auto-discovery
|
||||
deriveNextAddr common.Address // Next derived account address for auto-discovery
|
||||
deriveChain ethereum.ChainStateReader // Blockchain state reader to discover used account with
|
||||
deriveReq chan chan struct{} // Channel to request a self-derivation on
|
||||
deriveQuit chan chan error // Channel to terminate the self-deriver with
|
||||
|
||||
healthQuit chan chan error |
||||
|
||||
// Locking a hardware wallet is a bit special. Since hardware devices are lower
|
||||
// performing, any communication with them might take a non negligible amount of
|
||||
// time. Worse still, waiting for user confirmation can take arbitrarily long,
|
||||
// but exclusive communication must be upheld during. Locking the entire wallet
|
||||
// in the mean time however would stall any parts of the system that don't want
|
||||
// to communicate, just read some state (e.g. list the accounts).
|
||||
//
|
||||
// As such, a hardware wallet needs two locks to function correctly. A state
|
||||
// lock can be used to protect the wallet's software-side internal state, which
|
||||
// must not be held exlusively during hardware communication. A communication
|
||||
// lock can be used to achieve exclusive access to the device itself, this one
|
||||
// however should allow "skipping" waiting for operations that might want to
|
||||
// use the device, but can live without too (e.g. account self-derivation).
|
||||
//
|
||||
// Since we have two locks, it's important to know how to properly use them:
|
||||
// - Communication requires the `device` to not change, so obtaining the
|
||||
// commsLock should be done after having a stateLock.
|
||||
// - Communication must not disable read access to the wallet state, so it
|
||||
// must only ever hold a *read* lock to stateLock.
|
||||
commsLock chan struct{} // Mutex (buf=1) for the USB comms without keeping the state locked
|
||||
stateLock sync.RWMutex // Protects read and write access to the wallet struct fields
|
||||
} |
||||
|
||||
// URL implements accounts.Wallet, returning the URL of the Ledger device.
|
||||
func (w *ledgerWallet) URL() accounts.URL { |
||||
return *w.url // Immutable, no need for a lock
|
||||
} |
||||
|
||||
// Status implements accounts.Wallet, always whether the Ledger is opened, closed
|
||||
// or whether the Ethereum app was not started on it.
|
||||
func (w *ledgerWallet) Status() string { |
||||
w.stateLock.RLock() // No device communication, state lock is enough
|
||||
defer w.stateLock.RUnlock() |
||||
|
||||
if w.failure != nil { |
||||
return fmt.Sprintf("Failed: %v", w.failure) |
||||
} |
||||
if w.device == nil { |
||||
return "Closed" |
||||
} |
||||
if w.browser { |
||||
return "Ethereum app in browser mode" |
||||
} |
||||
if w.offline() { |
||||
return "Ethereum app offline" |
||||
} |
||||
return fmt.Sprintf("Ethereum app v%d.%d.%d online", w.version[0], w.version[1], w.version[2]) |
||||
} |
||||
|
||||
// offline returns whether the wallet and the Ethereum app is offline or not.
|
||||
//
|
||||
// The method assumes that the state lock is held!
|
||||
func (w *ledgerWallet) offline() bool { |
||||
return w.version == [3]byte{0, 0, 0} |
||||
} |
||||
|
||||
// failed returns if the USB device wrapped by the wallet failed for some reason.
|
||||
// This is used by the device scanner to report failed wallets as departed.
|
||||
//
|
||||
// The method assumes that the state lock is *not* held!
|
||||
func (w *ledgerWallet) failed() bool { |
||||
w.stateLock.RLock() // No device communication, state lock is enough
|
||||
defer w.stateLock.RUnlock() |
||||
|
||||
return w.failure != nil |
||||
} |
||||
|
||||
// Open implements accounts.Wallet, attempting to open a USB connection to the
|
||||
// Ledger hardware wallet. The Ledger does not require a user passphrase, so that
|
||||
// parameter is silently discarded.
|
||||
func (w *ledgerWallet) Open(passphrase string) error { |
||||
w.stateLock.Lock() // State lock is enough since there's no connection yet at this point
|
||||
defer w.stateLock.Unlock() |
||||
|
||||
// If the wallet was already opened, don't try to open again
|
||||
if w.device != nil { |
||||
return accounts.ErrWalletAlreadyOpen |
||||
} |
||||
// Otherwise iterate over all USB devices and find this again (no way to directly do this)
|
||||
// Iterate over all attached devices and fetch those seemingly Ledger
|
||||
devices, err := w.context.ListDevices(func(desc *usb.Descriptor) bool { |
||||
// Only open this single specific device
|
||||
return desc.Vendor == w.hardwareID.Vendor && desc.Product == w.hardwareID.Product && |
||||
uint16(desc.Bus)<<8+uint16(desc.Address) == w.locationID |
||||
}) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if len(devices) == 0 { |
||||
return accounts.ErrUnknownWallet |
||||
} |
||||
// Device opened, attach to the input and output endpoints
|
||||
device := devices[0] |
||||
|
||||
var invalid string |
||||
switch { |
||||
case len(device.Descriptor.Configs) == 0: |
||||
invalid = "no endpoint config available" |
||||
case len(device.Descriptor.Configs[0].Interfaces) == 0: |
||||
invalid = "no endpoint interface available" |
||||
case len(device.Descriptor.Configs[0].Interfaces[0].Setups) == 0: |
||||
invalid = "no endpoint setup available" |
||||
case len(device.Descriptor.Configs[0].Interfaces[0].Setups[0].Endpoints) < 2: |
||||
invalid = "not enough IO endpoints available" |
||||
} |
||||
if invalid != "" { |
||||
device.Close() |
||||
return fmt.Errorf("ledger wallet [%s] invalid: %s", w.url, invalid) |
||||
} |
||||
// Open the input and output endpoints to the device
|
||||
input, err := device.OpenEndpoint( |
||||
device.Descriptor.Configs[0].Config, |
||||
device.Descriptor.Configs[0].Interfaces[0].Number, |
||||
device.Descriptor.Configs[0].Interfaces[0].Setups[0].Number, |
||||
device.Descriptor.Configs[0].Interfaces[0].Setups[0].Endpoints[1].Address, |
||||
) |
||||
if err != nil { |
||||
device.Close() |
||||
return fmt.Errorf("ledger wallet [%s] input open failed: %v", w.url, err) |
||||
} |
||||
output, err := device.OpenEndpoint( |
||||
device.Descriptor.Configs[0].Config, |
||||
device.Descriptor.Configs[0].Interfaces[0].Number, |
||||
device.Descriptor.Configs[0].Interfaces[0].Setups[0].Number, |
||||
device.Descriptor.Configs[0].Interfaces[0].Setups[0].Endpoints[0].Address, |
||||
) |
||||
if err != nil { |
||||
device.Close() |
||||
return fmt.Errorf("ledger wallet [%s] output open failed: %v", w.url, err) |
||||
} |
||||
// Wallet seems to be successfully opened, guess if the Ethereum app is running
|
||||
w.device, w.input, w.output = device, input, output |
||||
w.commsLock = make(chan struct{}, 1) |
||||
w.commsLock <- struct{}{} // Enable lock
|
||||
|
||||
w.paths = make(map[common.Address]accounts.DerivationPath) |
||||
|
||||
w.deriveReq = make(chan chan struct{}) |
||||
w.deriveQuit = make(chan chan error) |
||||
w.healthQuit = make(chan chan error) |
||||
|
||||
defer func() { |
||||
go w.heartbeat() |
||||
go w.selfDerive() |
||||
}() |
||||
|
||||
if _, err = w.ledgerDerive(accounts.DefaultBaseDerivationPath); err != nil { |
||||
// Ethereum app is not running or in browser mode, nothing more to do, return
|
||||
if err == errReplyInvalidHeader { |
||||
w.browser = true |
||||
} |
||||
return nil |
||||
} |
||||
// Try to resolve the Ethereum app's version, will fail prior to v1.0.2
|
||||
if w.version, err = w.ledgerVersion(); err != nil { |
||||
w.version = [3]byte{1, 0, 0} // Assume worst case, can't verify if v1.0.0 or v1.0.1
|
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// heartbeat is a health check loop for the Ledger wallets to periodically verify
|
||||
// whether they are still present or if they malfunctioned. It is needed because:
|
||||
// - libusb on Windows doesn't support hotplug, so we can't detect USB unplugs
|
||||
// - communication timeout on the Ledger requires a device power cycle to fix
|
||||
func (w *ledgerWallet) heartbeat() { |
||||
glog.V(logger.Debug).Infof("%s health-check started", w.url.String()) |
||||
defer glog.V(logger.Debug).Infof("%s health-check stopped", w.url.String()) |
||||
|
||||
// Execute heartbeat checks until termination or error
|
||||
var ( |
||||
errc chan error |
||||
err error |
||||
) |
||||
for errc == nil && err == nil { |
||||
// Wait until termination is requested or the heartbeat cycle arrives
|
||||
select { |
||||
case errc = <-w.healthQuit: |
||||
// Termination requested
|
||||
continue |
||||
case <-time.After(ledgerHeartbeatCycle): |
||||
// Heartbeat time
|
||||
} |
||||
// Execute a tiny data exchange to see responsiveness
|
||||
w.stateLock.RLock() |
||||
if w.device == nil { |
||||
// Terminated while waiting for the lock
|
||||
w.stateLock.RUnlock() |
||||
continue |
||||
} |
||||
<-w.commsLock // Don't lock state while resolving version
|
||||
_, err = w.ledgerVersion() |
||||
w.commsLock <- struct{}{} |
||||
w.stateLock.RUnlock() |
||||
|
||||
if err == usb.ERROR_IO || err == usb.ERROR_NO_DEVICE { |
||||
w.stateLock.Lock() // Lock state to tear the wallet down
|
||||
w.failure = err |
||||
w.close() |
||||
w.stateLock.Unlock() |
||||
} |
||||
// Ignore uninteresting errors
|
||||
err = nil |
||||
} |
||||
// In case of error, wait for termination
|
||||
if err != nil { |
||||
glog.V(logger.Debug).Infof("%s health-check failed: %v", w.url.String(), err) |
||||
errc = <-w.healthQuit |
||||
} |
||||
errc <- err |
||||
} |
||||
|
||||
// Close implements accounts.Wallet, closing the USB connection to the Ledger.
|
||||
func (w *ledgerWallet) Close() error { |
||||
// Ensure the wallet was opened
|
||||
w.stateLock.RLock() |
||||
hQuit, dQuit := w.healthQuit, w.deriveQuit |
||||
w.stateLock.RUnlock() |
||||
|
||||
// Terminate the health checks
|
||||
var herr error |
||||
if hQuit != nil { |
||||
errc := make(chan error) |
||||
hQuit <- errc |
||||
herr = <-errc // Save for later, we *must* close the USB
|
||||
} |
||||
// Terminate the self-derivations
|
||||
var derr error |
||||
if dQuit != nil { |
||||
errc := make(chan error) |
||||
dQuit <- errc |
||||
derr = <-errc // Save for later, we *must* close the USB
|
||||
} |
||||
// Terminate the device connection
|
||||
w.stateLock.Lock() |
||||
defer w.stateLock.Unlock() |
||||
|
||||
w.healthQuit = nil |
||||
w.deriveQuit = nil |
||||
w.deriveReq = nil |
||||
|
||||
if err := w.close(); err != nil { |
||||
return err |
||||
} |
||||
if herr != nil { |
||||
return herr |
||||
} |
||||
return derr |
||||
} |
||||
|
||||
// close is the internal wallet closer that terminates the USB connection and
|
||||
// resets all the fields to their defaults.
|
||||
//
|
||||
// Note, close assumes the state lock is held!
|
||||
func (w *ledgerWallet) close() error { |
||||
// Allow duplicate closes, especially for health-check failures
|
||||
if w.device == nil { |
||||
return nil |
||||
} |
||||
// Close the device, clear everything, then return
|
||||
err := w.device.Close() |
||||
|
||||
w.device, w.input, w.output = nil, nil, nil |
||||
w.browser, w.version = false, [3]byte{} |
||||
w.accounts, w.paths = nil, nil |
||||
|
||||
return err |
||||
} |
||||
|
||||
// Accounts implements accounts.Wallet, returning the list of accounts pinned to
|
||||
// the Ledger hardware wallet. If self-derivation was enabled, the account list
|
||||
// is periodically expanded based on current chain state.
|
||||
func (w *ledgerWallet) Accounts() []accounts.Account { |
||||
// Attempt self-derivation if it's running
|
||||
reqc := make(chan struct{}, 1) |
||||
select { |
||||
case w.deriveReq <- reqc: |
||||
// Self-derivation request accepted, wait for it
|
||||
<-reqc |
||||
default: |
||||
// Self-derivation offline, throttled or busy, skip
|
||||
} |
||||
// Return whatever account list we ended up with
|
||||
w.stateLock.RLock() |
||||
defer w.stateLock.RUnlock() |
||||
|
||||
cpy := make([]accounts.Account, len(w.accounts)) |
||||
copy(cpy, w.accounts) |
||||
return cpy |
||||
} |
||||
|
||||
// selfDerive is an account derivation loop that upon request attempts to find
|
||||
// new non-zero accounts.
|
||||
func (w *ledgerWallet) selfDerive() { |
||||
glog.V(logger.Debug).Infof("%s self-derivation started", w.url.String()) |
||||
defer glog.V(logger.Debug).Infof("%s self-derivation stopped", w.url.String()) |
||||
|
||||
// Execute self-derivations until termination or error
|
||||
var ( |
||||
reqc chan struct{} |
||||
errc chan error |
||||
err error |
||||
) |
||||
for errc == nil && err == nil { |
||||
// Wait until either derivation or termination is requested
|
||||
select { |
||||
case errc = <-w.deriveQuit: |
||||
// Termination requested
|
||||
continue |
||||
case reqc = <-w.deriveReq: |
||||
// Account discovery requested
|
||||
} |
||||
// Derivation needs a chain and device access, skip if either unavailable
|
||||
w.stateLock.RLock() |
||||
if w.device == nil || w.deriveChain == nil || w.offline() { |
||||
w.stateLock.RUnlock() |
||||
reqc <- struct{}{} |
||||
continue |
||||
} |
||||
select { |
||||
case <-w.commsLock: |
||||
default: |
||||
w.stateLock.RUnlock() |
||||
reqc <- struct{}{} |
||||
continue |
||||
} |
||||
// Device lock obtained, derive the next batch of accounts
|
||||
var ( |
||||
accs []accounts.Account |
||||
paths []accounts.DerivationPath |
||||
|
||||
nextAddr = w.deriveNextAddr |
||||
nextPath = w.deriveNextPath |
||||
|
||||
context = context.Background() |
||||
) |
||||
for empty := false; !empty; { |
||||
// Retrieve the next derived Ethereum account
|
||||
if nextAddr == (common.Address{}) { |
||||
if nextAddr, err = w.ledgerDerive(nextPath); err != nil { |
||||
glog.V(logger.Warn).Infof("%s self-derivation failed: %v", w.url.String(), err) |
||||
break |
||||
} |
||||
} |
||||
// Check the account's status against the current chain state
|
||||
var ( |
||||
balance *big.Int |
||||
nonce uint64 |
||||
) |
||||
balance, err = w.deriveChain.BalanceAt(context, nextAddr, nil) |
||||
if err != nil { |
||||
glog.V(logger.Warn).Infof("%s self-derivation balance retrieval failed: %v", w.url.String(), err) |
||||
break |
||||
} |
||||
nonce, err = w.deriveChain.NonceAt(context, nextAddr, nil) |
||||
if err != nil { |
||||
glog.V(logger.Warn).Infof("%s self-derivation nonce retrieval failed: %v", w.url.String(), err) |
||||
break |
||||
} |
||||
// If the next account is empty, stop self-derivation, but add it nonetheless
|
||||
if balance.BitLen() == 0 && nonce == 0 { |
||||
empty = true |
||||
} |
||||
// We've just self-derived a new account, start tracking it locally
|
||||
path := make(accounts.DerivationPath, len(nextPath)) |
||||
copy(path[:], nextPath[:]) |
||||
paths = append(paths, path) |
||||
|
||||
account := accounts.Account{ |
||||
Address: nextAddr, |
||||
URL: accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)}, |
||||
} |
||||
accs = append(accs, account) |
||||
|
||||
// Display a log message to the user for new (or previously empty accounts)
|
||||
if _, known := w.paths[nextAddr]; !known || (!empty && nextAddr == w.deriveNextAddr) { |
||||
glog.V(logger.Info).Infof("%s discovered %s (balance %22v, nonce %4d) at %s", w.url.String(), nextAddr.Hex(), balance, nonce, path) |
||||
} |
||||
// Fetch the next potential account
|
||||
if !empty { |
||||
nextAddr = common.Address{} |
||||
nextPath[len(nextPath)-1]++ |
||||
} |
||||
} |
||||
// Self derivation complete, release device lock
|
||||
w.commsLock <- struct{}{} |
||||
w.stateLock.RUnlock() |
||||
|
||||
// Insert any accounts successfully derived
|
||||
w.stateLock.Lock() |
||||
for i := 0; i < len(accs); i++ { |
||||
if _, ok := w.paths[accs[i].Address]; !ok { |
||||
w.accounts = append(w.accounts, accs[i]) |
||||
w.paths[accs[i].Address] = paths[i] |
||||
} |
||||
} |
||||
// Shift the self-derivation forward
|
||||
// TODO(karalabe): don't overwrite changes from wallet.SelfDerive
|
||||
w.deriveNextAddr = nextAddr |
||||
w.deriveNextPath = nextPath |
||||
w.stateLock.Unlock() |
||||
|
||||
// Notify the user of termination and loop after a bit of time (to avoid trashing)
|
||||
reqc <- struct{}{} |
||||
if err == nil { |
||||
select { |
||||
case errc = <-w.deriveQuit: |
||||
// Termination requested, abort
|
||||
case <-time.After(ledgerSelfDeriveThrottling): |
||||
// Waited enough, willing to self-derive again
|
||||
} |
||||
} |
||||
} |
||||
// In case of error, wait for termination
|
||||
if err != nil { |
||||
glog.V(logger.Debug).Infof("%s self-derivation failed: %s", w.url.String(), err) |
||||
errc = <-w.deriveQuit |
||||
} |
||||
errc <- err |
||||
} |
||||
|
||||
// Contains implements accounts.Wallet, returning whether a particular account is
|
||||
// or is not pinned into this Ledger instance. Although we could attempt to resolve
|
||||
// unpinned accounts, that would be an non-negligible hardware operation.
|
||||
func (w *ledgerWallet) Contains(account accounts.Account) bool { |
||||
w.stateLock.RLock() |
||||
defer w.stateLock.RUnlock() |
||||
|
||||
_, exists := w.paths[account.Address] |
||||
return exists |
||||
} |
||||
|
||||
// Derive implements accounts.Wallet, deriving a new account at the specific
|
||||
// derivation path. If pin is set to true, the account will be added to the list
|
||||
// of tracked accounts.
|
||||
func (w *ledgerWallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { |
||||
// Try to derive the actual account and update its URL if successful
|
||||
w.stateLock.RLock() // Avoid device disappearing during derivation
|
||||
|
||||
if w.device == nil || w.offline() { |
||||
w.stateLock.RUnlock() |
||||
return accounts.Account{}, accounts.ErrWalletClosed |
||||
} |
||||
<-w.commsLock // Avoid concurrent hardware access
|
||||
address, err := w.ledgerDerive(path) |
||||
w.commsLock <- struct{}{} |
||||
|
||||
w.stateLock.RUnlock() |
||||
|
||||
// If an error occurred or no pinning was requested, return
|
||||
if err != nil { |
||||
return accounts.Account{}, err |
||||
} |
||||
account := accounts.Account{ |
||||
Address: address, |
||||
URL: accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)}, |
||||
} |
||||
if !pin { |
||||
return account, nil |
||||
} |
||||
// Pinning needs to modify the state
|
||||
w.stateLock.Lock() |
||||
defer w.stateLock.Unlock() |
||||
|
||||
if _, ok := w.paths[address]; !ok { |
||||
w.accounts = append(w.accounts, account) |
||||
w.paths[address] = path |
||||
} |
||||
return account, nil |
||||
} |
||||
|
||||
// SelfDerive implements accounts.Wallet, trying to discover accounts that the
|
||||
// user used previously (based on the chain state), but ones that he/she did not
|
||||
// explicitly pin to the wallet manually. To avoid chain head monitoring, self
|
||||
// derivation only runs during account listing (and even then throttled).
|
||||
func (w *ledgerWallet) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainStateReader) { |
||||
w.stateLock.Lock() |
||||
defer w.stateLock.Unlock() |
||||
|
||||
w.deriveNextPath = make(accounts.DerivationPath, len(base)) |
||||
copy(w.deriveNextPath[:], base[:]) |
||||
|
||||
w.deriveNextAddr = common.Address{} |
||||
w.deriveChain = chain |
||||
} |
||||
|
||||
// SignHash implements accounts.Wallet, however signing arbitrary data is not
|
||||
// supported for Ledger wallets, so this method will always return an error.
|
||||
func (w *ledgerWallet) SignHash(acc accounts.Account, hash []byte) ([]byte, error) { |
||||
return nil, accounts.ErrNotSupported |
||||
} |
||||
|
||||
// SignTx implements accounts.Wallet. It sends the transaction over to the Ledger
|
||||
// wallet to request a confirmation from the user. It returns either the signed
|
||||
// transaction or a failure if the user denied the transaction.
|
||||
//
|
||||
// Note, if the version of the Ethereum application running on the Ledger wallet is
|
||||
// too old to sign EIP-155 transactions, but such is requested nonetheless, an error
|
||||
// will be returned opposed to silently signing in Homestead mode.
|
||||
func (w *ledgerWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { |
||||
w.stateLock.RLock() // Comms have own mutex, this is for the state fields
|
||||
defer w.stateLock.RUnlock() |
||||
|
||||
// If the wallet is closed, or the Ethereum app doesn't run, abort
|
||||
if w.device == nil || w.offline() { |
||||
return nil, accounts.ErrWalletClosed |
||||
} |
||||
// Make sure the requested account is contained within
|
||||
path, ok := w.paths[account.Address] |
||||
if !ok { |
||||
return nil, accounts.ErrUnknownAccount |
||||
} |
||||
// Ensure the wallet is capable of signing the given transaction
|
||||
if chainID != nil && w.version[0] <= 1 && w.version[1] <= 0 && w.version[2] <= 2 { |
||||
return nil, fmt.Errorf("Ledger v%d.%d.%d doesn't support signing this transaction, please update to v1.0.3 at least", w.version[0], w.version[1], w.version[2]) |
||||
} |
||||
// All infos gathered and metadata checks out, request signing
|
||||
<-w.commsLock |
||||
defer func() { w.commsLock <- struct{}{} }() |
||||
|
||||
return w.ledgerSign(path, account.Address, tx, chainID) |
||||
} |
||||
|
||||
// SignHashWithPassphrase implements accounts.Wallet, however signing arbitrary
|
||||
// data is not supported for Ledger wallets, so this method will always return
|
||||
// an error.
|
||||
func (w *ledgerWallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) { |
||||
return nil, accounts.ErrNotSupported |
||||
} |
||||
|
||||
// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
|
||||
// transaction with the given account using passphrase as extra authentication.
|
||||
// Since the Ledger does not support extra passphrases, it is silently ignored.
|
||||
func (w *ledgerWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { |
||||
return w.SignTx(account, tx, chainID) |
||||
} |
||||
|
||||
// ledgerVersion retrieves the current version of the Ethereum wallet app running
|
||||
// on the Ledger wallet.
|
||||
//
|
||||
// The version retrieval protocol is defined as follows:
|
||||
//
|
||||
// CLA | INS | P1 | P2 | Lc | Le
|
||||
// ----+-----+----+----+----+---
|
||||
// E0 | 06 | 00 | 00 | 00 | 04
|
||||
//
|
||||
// With no input data, and the output data being:
|
||||
//
|
||||
// Description | Length
|
||||
// ---------------------------------------------------+--------
|
||||
// Flags 01: arbitrary data signature enabled by user | 1 byte
|
||||
// Application major version | 1 byte
|
||||
// Application minor version | 1 byte
|
||||
// Application patch version | 1 byte
|
||||
func (w *ledgerWallet) ledgerVersion() ([3]byte, error) { |
||||
// Send the request and wait for the response
|
||||
reply, err := w.ledgerExchange(ledgerOpGetConfiguration, 0, 0, nil) |
||||
if err != nil { |
||||
return [3]byte{}, err |
||||
} |
||||
if len(reply) != 4 { |
||||
return [3]byte{}, errors.New("reply not of correct size") |
||||
} |
||||
// Cache the version for future reference
|
||||
var version [3]byte |
||||
copy(version[:], reply[1:]) |
||||
return version, nil |
||||
} |
||||
|
||||
// ledgerDerive retrieves the currently active Ethereum address from a Ledger
|
||||
// wallet at the specified derivation path.
|
||||
//
|
||||
// The address derivation protocol is defined as follows:
|
||||
//
|
||||
// CLA | INS | P1 | P2 | Lc | Le
|
||||
// ----+-----+----+----+-----+---
|
||||
// E0 | 02 | 00 return address
|
||||
// 01 display address and confirm before returning
|
||||
// | 00: do not return the chain code
|
||||
// | 01: return the chain code
|
||||
// | var | 00
|
||||
//
|
||||
// Where the input data is:
|
||||
//
|
||||
// Description | Length
|
||||
// -------------------------------------------------+--------
|
||||
// Number of BIP 32 derivations to perform (max 10) | 1 byte
|
||||
// First derivation index (big endian) | 4 bytes
|
||||
// ... | 4 bytes
|
||||
// Last derivation index (big endian) | 4 bytes
|
||||
//
|
||||
// And the output data is:
|
||||
//
|
||||
// Description | Length
|
||||
// ------------------------+-------------------
|
||||
// Public Key length | 1 byte
|
||||
// Uncompressed Public Key | arbitrary
|
||||
// Ethereum address length | 1 byte
|
||||
// Ethereum address | 40 bytes hex ascii
|
||||
// Chain code if requested | 32 bytes
|
||||
func (w *ledgerWallet) ledgerDerive(derivationPath []uint32) (common.Address, error) { |
||||
// Flatten the derivation path into the Ledger request
|
||||
path := make([]byte, 1+4*len(derivationPath)) |
||||
path[0] = byte(len(derivationPath)) |
||||
for i, component := range derivationPath { |
||||
binary.BigEndian.PutUint32(path[1+4*i:], component) |
||||
} |
||||
// Send the request and wait for the response
|
||||
reply, err := w.ledgerExchange(ledgerOpRetrieveAddress, ledgerP1DirectlyFetchAddress, ledgerP2DiscardAddressChainCode, path) |
||||
if err != nil { |
||||
return common.Address{}, err |
||||
} |
||||
// Discard the public key, we don't need that for now
|
||||
if len(reply) < 1 || len(reply) < 1+int(reply[0]) { |
||||
return common.Address{}, errors.New("reply lacks public key entry") |
||||
} |
||||
reply = reply[1+int(reply[0]):] |
||||
|
||||
// Extract the Ethereum hex address string
|
||||
if len(reply) < 1 || len(reply) < 1+int(reply[0]) { |
||||
return common.Address{}, errors.New("reply lacks address entry") |
||||
} |
||||
hexstr := reply[1 : 1+int(reply[0])] |
||||
|
||||
// Decode the hex sting into an Ethereum address and return
|
||||
var address common.Address |
||||
hex.Decode(address[:], hexstr) |
||||
return address, nil |
||||
} |
||||
|
||||
// ledgerSign sends the transaction to the Ledger wallet, and waits for the user
|
||||
// to confirm or deny the transaction.
|
||||
//
|
||||
// The transaction signing protocol is defined as follows:
|
||||
//
|
||||
// CLA | INS | P1 | P2 | Lc | Le
|
||||
// ----+-----+----+----+-----+---
|
||||
// E0 | 04 | 00: first transaction data block
|
||||
// 80: subsequent transaction data block
|
||||
// | 00 | variable | variable
|
||||
//
|
||||
// Where the input for the first transaction block (first 255 bytes) is:
|
||||
//
|
||||
// Description | Length
|
||||
// -------------------------------------------------+----------
|
||||
// Number of BIP 32 derivations to perform (max 10) | 1 byte
|
||||
// First derivation index (big endian) | 4 bytes
|
||||
// ... | 4 bytes
|
||||
// Last derivation index (big endian) | 4 bytes
|
||||
// RLP transaction chunk | arbitrary
|
||||
//
|
||||
// And the input for subsequent transaction blocks (first 255 bytes) are:
|
||||
//
|
||||
// Description | Length
|
||||
// ----------------------+----------
|
||||
// RLP transaction chunk | arbitrary
|
||||
//
|
||||
// And the output data is:
|
||||
//
|
||||
// Description | Length
|
||||
// ------------+---------
|
||||
// signature V | 1 byte
|
||||
// signature R | 32 bytes
|
||||
// signature S | 32 bytes
|
||||
func (w *ledgerWallet) ledgerSign(derivationPath []uint32, address common.Address, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { |
||||
// We need to modify the timeouts to account for user feedback
|
||||
defer func(old time.Duration) { w.device.ReadTimeout = old }(w.device.ReadTimeout) |
||||
w.device.ReadTimeout = time.Hour * 24 * 30 // Timeout requires a Ledger power cycle, only if you must
|
||||
|
||||
// Flatten the derivation path into the Ledger request
|
||||
path := make([]byte, 1+4*len(derivationPath)) |
||||
path[0] = byte(len(derivationPath)) |
||||
for i, component := range derivationPath { |
||||
binary.BigEndian.PutUint32(path[1+4*i:], component) |
||||
} |
||||
// Create the transaction RLP based on whether legacy or EIP155 signing was requeste
|
||||
var ( |
||||
txrlp []byte |
||||
err error |
||||
) |
||||
if chainID == nil { |
||||
if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data()}); err != nil { |
||||
return nil, err |
||||
} |
||||
} else { |
||||
if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), chainID, big.NewInt(0), big.NewInt(0)}); err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
payload := append(path, txrlp...) |
||||
|
||||
// Send the request and wait for the response
|
||||
var ( |
||||
op = ledgerP1InitTransactionData |
||||
reply []byte |
||||
) |
||||
for len(payload) > 0 { |
||||
// Calculate the size of the next data chunk
|
||||
chunk := 255 |
||||
if chunk > len(payload) { |
||||
chunk = len(payload) |
||||
} |
||||
// Send the chunk over, ensuring it's processed correctly
|
||||
reply, err = w.ledgerExchange(ledgerOpSignTransaction, op, 0, payload[:chunk]) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
// Shift the payload and ensure subsequent chunks are marked as such
|
||||
payload = payload[chunk:] |
||||
op = ledgerP1ContTransactionData |
||||
} |
||||
// Extract the Ethereum signature and do a sanity validation
|
||||
if len(reply) != 65 { |
||||
return nil, errors.New("reply lacks signature") |
||||
} |
||||
signature := append(reply[1:], reply[0]) |
||||
|
||||
// Create the correct signer and signature transform based on the chain ID
|
||||
var signer types.Signer |
||||
if chainID == nil { |
||||
signer = new(types.HomesteadSigner) |
||||
} else { |
||||
signer = types.NewEIP155Signer(chainID) |
||||
signature[64] = signature[64] - byte(chainID.Uint64()*2+35) |
||||
} |
||||
// Inject the final signature into the transaction and sanity check the sender
|
||||
signed, err := tx.WithSignature(signer, signature) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
sender, err := types.Sender(signer, signed) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if sender != address { |
||||
return nil, fmt.Errorf("signer mismatch: expected %s, got %s", address.Hex(), sender.Hex()) |
||||
} |
||||
return signed, nil |
||||
} |
||||
|
||||
// ledgerExchange performs a data exchange with the Ledger wallet, sending it a
|
||||
// message and retrieving the response.
|
||||
//
|
||||
// The common transport header is defined as follows:
|
||||
//
|
||||
// Description | Length
|
||||
// --------------------------------------+----------
|
||||
// Communication channel ID (big endian) | 2 bytes
|
||||
// Command tag | 1 byte
|
||||
// Packet sequence index (big endian) | 2 bytes
|
||||
// Payload | arbitrary
|
||||
//
|
||||
// The Communication channel ID allows commands multiplexing over the same
|
||||
// physical link. It is not used for the time being, and should be set to 0101
|
||||
// to avoid compatibility issues with implementations ignoring a leading 00 byte.
|
||||
//
|
||||
// The Command tag describes the message content. Use TAG_APDU (0x05) for standard
|
||||
// APDU payloads, or TAG_PING (0x02) for a simple link test.
|
||||
//
|
||||
// The Packet sequence index describes the current sequence for fragmented payloads.
|
||||
// The first fragment index is 0x00.
|
||||
//
|
||||
// APDU Command payloads are encoded as follows:
|
||||
//
|
||||
// Description | Length
|
||||
// -----------------------------------
|
||||
// APDU length (big endian) | 2 bytes
|
||||
// APDU CLA | 1 byte
|
||||
// APDU INS | 1 byte
|
||||
// APDU P1 | 1 byte
|
||||
// APDU P2 | 1 byte
|
||||
// APDU length | 1 byte
|
||||
// Optional APDU data | arbitrary
|
||||
func (w *ledgerWallet) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 ledgerParam2, data []byte) ([]byte, error) { |
||||
// Construct the message payload, possibly split into multiple chunks
|
||||
apdu := make([]byte, 2, 7+len(data)) |
||||
|
||||
binary.BigEndian.PutUint16(apdu, uint16(5+len(data))) |
||||
apdu = append(apdu, []byte{0xe0, byte(opcode), byte(p1), byte(p2), byte(len(data))}...) |
||||
apdu = append(apdu, data...) |
||||
|
||||
// Stream all the chunks to the device
|
||||
header := []byte{0x01, 0x01, 0x05, 0x00, 0x00} // Channel ID and command tag appended
|
||||
chunk := make([]byte, 64) |
||||
space := len(chunk) - len(header) |
||||
|
||||
for i := 0; len(apdu) > 0; i++ { |
||||
// Construct the new message to stream
|
||||
chunk = append(chunk[:0], header...) |
||||
binary.BigEndian.PutUint16(chunk[3:], uint16(i)) |
||||
|
||||
if len(apdu) > space { |
||||
chunk = append(chunk, apdu[:space]...) |
||||
apdu = apdu[space:] |
||||
} else { |
||||
chunk = append(chunk, apdu...) |
||||
apdu = nil |
||||
} |
||||
// Send over to the device
|
||||
if glog.V(logger.Detail) { |
||||
glog.Infof("-> %03d.%03d: %x", w.device.Bus, w.device.Address, chunk) |
||||
} |
||||
if _, err := w.input.Write(chunk); err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
// Stream the reply back from the wallet in 64 byte chunks
|
||||
var reply []byte |
||||
chunk = chunk[:64] // Yeah, we surely have enough space
|
||||
for { |
||||
// Read the next chunk from the Ledger wallet
|
||||
if _, err := io.ReadFull(w.output, chunk); err != nil { |
||||
return nil, err |
||||
} |
||||
if glog.V(logger.Detail) { |
||||
glog.Infof("<- %03d.%03d: %x", w.device.Bus, w.device.Address, chunk) |
||||
} |
||||
// Make sure the transport header matches
|
||||
if chunk[0] != 0x01 || chunk[1] != 0x01 || chunk[2] != 0x05 { |
||||
return nil, errReplyInvalidHeader |
||||
} |
||||
// If it's the first chunk, retrieve the total message length
|
||||
var payload []byte |
||||
|
||||
if chunk[3] == 0x00 && chunk[4] == 0x00 { |
||||
reply = make([]byte, 0, int(binary.BigEndian.Uint16(chunk[5:7]))) |
||||
payload = chunk[7:] |
||||
} else { |
||||
payload = chunk[5:] |
||||
} |
||||
// Append to the reply and stop when filled up
|
||||
if left := cap(reply) - len(reply); left > len(payload) { |
||||
reply = append(reply, payload...) |
||||
} else { |
||||
reply = append(reply, payload[:left]...) |
||||
break |
||||
} |
||||
} |
||||
return reply[:len(reply)-2], nil |
||||
} |
@ -0,0 +1,29 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// +build !ios
|
||||
|
||||
// Package usbwallet implements support for USB hardware wallets.
|
||||
package usbwallet |
||||
|
||||
import "github.com/karalabe/gousb/usb" |
||||
|
||||
// deviceID is a combined vendor/product identifier to uniquely identify a USB
|
||||
// hardware device.
|
||||
type deviceID struct { |
||||
Vendor usb.ID // The Vendor identifer
|
||||
Product usb.ID // The Product identifier
|
||||
} |
@ -0,0 +1,38 @@ |
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// This file contains the implementation for interacting with the Ledger hardware
|
||||
// wallets. The wire protocol spec can be found in the Ledger Blue GitHub repo:
|
||||
// https://raw.githubusercontent.com/LedgerHQ/blue-app-eth/master/doc/ethapp.asc
|
||||
|
||||
// +build ios
|
||||
|
||||
package usbwallet |
||||
|
||||
import ( |
||||
"errors" |
||||
|
||||
"github.com/ethereum/go-ethereum/accounts" |
||||
) |
||||
|
||||
// Here be dragons! There is no USB support on iOS.
|
||||
|
||||
// ErrIOSNotSupported is returned for all USB hardware backends on iOS.
|
||||
var ErrIOSNotSupported = errors.New("no USB support on iOS") |
||||
|
||||
func NewLedgerHub() (accounts.Backend, error) { |
||||
return nil, ErrIOSNotSupported |
||||
} |
@ -0,0 +1 @@ |
||||
*.sw[op] |
@ -0,0 +1,12 @@ |
||||
language: go |
||||
|
||||
matrix: |
||||
include: |
||||
- os: linux |
||||
dist: trusty |
||||
go: 1.7.4 |
||||
- os: osx |
||||
go: 1.7.4 |
||||
|
||||
script: |
||||
- go test -v -test.run='BCD|Parse' ./... |
@ -0,0 +1,202 @@ |
||||
|
||||
Apache License |
||||
Version 2.0, January 2004 |
||||
http://www.apache.org/licenses/ |
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||
|
||||
1. Definitions. |
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, |
||||
and distribution as defined by Sections 1 through 9 of this document. |
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by |
||||
the copyright owner that is granting the License. |
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all |
||||
other entities that control, are controlled by, or are under common |
||||
control with that entity. For the purposes of this definition, |
||||
"control" means (i) the power, direct or indirect, to cause the |
||||
direction or management of such entity, whether by contract or |
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||
outstanding shares, or (iii) beneficial ownership of such entity. |
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity |
||||
exercising permissions granted by this License. |
||||
|
||||
"Source" form shall mean the preferred form for making modifications, |
||||
including but not limited to software source code, documentation |
||||
source, and configuration files. |
||||
|
||||
"Object" form shall mean any form resulting from mechanical |
||||
transformation or translation of a Source form, including but |
||||
not limited to compiled object code, generated documentation, |
||||
and conversions to other media types. |
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or |
||||
Object form, made available under the License, as indicated by a |
||||
copyright notice that is included in or attached to the work |
||||
(an example is provided in the Appendix below). |
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object |
||||
form, that is based on (or derived from) the Work and for which the |
||||
editorial revisions, annotations, elaborations, or other modifications |
||||
represent, as a whole, an original work of authorship. For the purposes |
||||
of this License, Derivative Works shall not include works that remain |
||||
separable from, or merely link (or bind by name) to the interfaces of, |
||||
the Work and Derivative Works thereof. |
||||
|
||||
"Contribution" shall mean any work of authorship, including |
||||
the original version of the Work and any modifications or additions |
||||
to that Work or Derivative Works thereof, that is intentionally |
||||
submitted to Licensor for inclusion in the Work by the copyright owner |
||||
or by an individual or Legal Entity authorized to submit on behalf of |
||||
the copyright owner. For the purposes of this definition, "submitted" |
||||
means any form of electronic, verbal, or written communication sent |
||||
to the Licensor or its representatives, including but not limited to |
||||
communication on electronic mailing lists, source code control systems, |
||||
and issue tracking systems that are managed by, or on behalf of, the |
||||
Licensor for the purpose of discussing and improving the Work, but |
||||
excluding communication that is conspicuously marked or otherwise |
||||
designated in writing by the copyright owner as "Not a Contribution." |
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||
on behalf of whom a Contribution has been received by Licensor and |
||||
subsequently incorporated within the Work. |
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
copyright license to reproduce, prepare Derivative Works of, |
||||
publicly display, publicly perform, sublicense, and distribute the |
||||
Work and such Derivative Works in Source or Object form. |
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
(except as stated in this section) patent license to make, have made, |
||||
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||
where such license applies only to those patent claims licensable |
||||
by such Contributor that are necessarily infringed by their |
||||
Contribution(s) alone or by combination of their Contribution(s) |
||||
with the Work to which such Contribution(s) was submitted. If You |
||||
institute patent litigation against any entity (including a |
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||
or a Contribution incorporated within the Work constitutes direct |
||||
or contributory patent infringement, then any patent licenses |
||||
granted to You under this License for that Work shall terminate |
||||
as of the date such litigation is filed. |
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the |
||||
Work or Derivative Works thereof in any medium, with or without |
||||
modifications, and in Source or Object form, provided that You |
||||
meet the following conditions: |
||||
|
||||
(a) You must give any other recipients of the Work or |
||||
Derivative Works a copy of this License; and |
||||
|
||||
(b) You must cause any modified files to carry prominent notices |
||||
stating that You changed the files; and |
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works |
||||
that You distribute, all copyright, patent, trademark, and |
||||
attribution notices from the Source form of the Work, |
||||
excluding those notices that do not pertain to any part of |
||||
the Derivative Works; and |
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its |
||||
distribution, then any Derivative Works that You distribute must |
||||
include a readable copy of the attribution notices contained |
||||
within such NOTICE file, excluding those notices that do not |
||||
pertain to any part of the Derivative Works, in at least one |
||||
of the following places: within a NOTICE text file distributed |
||||
as part of the Derivative Works; within the Source form or |
||||
documentation, if provided along with the Derivative Works; or, |
||||
within a display generated by the Derivative Works, if and |
||||
wherever such third-party notices normally appear. The contents |
||||
of the NOTICE file are for informational purposes only and |
||||
do not modify the License. You may add Your own attribution |
||||
notices within Derivative Works that You distribute, alongside |
||||
or as an addendum to the NOTICE text from the Work, provided |
||||
that such additional attribution notices cannot be construed |
||||
as modifying the License. |
||||
|
||||
You may add Your own copyright statement to Your modifications and |
||||
may provide additional or different license terms and conditions |
||||
for use, reproduction, or distribution of Your modifications, or |
||||
for any such Derivative Works as a whole, provided Your use, |
||||
reproduction, and distribution of the Work otherwise complies with |
||||
the conditions stated in this License. |
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||
any Contribution intentionally submitted for inclusion in the Work |
||||
by You to the Licensor shall be under the terms and conditions of |
||||
this License, without any additional terms or conditions. |
||||
Notwithstanding the above, nothing herein shall supersede or modify |
||||
the terms of any separate license agreement you may have executed |
||||
with Licensor regarding such Contributions. |
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade |
||||
names, trademarks, service marks, or product names of the Licensor, |
||||
except as required for reasonable and customary use in describing the |
||||
origin of the Work and reproducing the content of the NOTICE file. |
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or |
||||
agreed to in writing, Licensor provides the Work (and each |
||||
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||
implied, including, without limitation, any warranties or conditions |
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||
appropriateness of using or redistributing the Work and assume any |
||||
risks associated with Your exercise of permissions under this License. |
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, |
||||
whether in tort (including negligence), contract, or otherwise, |
||||
unless required by applicable law (such as deliberate and grossly |
||||
negligent acts) or agreed to in writing, shall any Contributor be |
||||
liable to You for damages, including any direct, indirect, special, |
||||
incidental, or consequential damages of any character arising as a |
||||
result of this License or out of the use or inability to use the |
||||
Work (including but not limited to damages for loss of goodwill, |
||||
work stoppage, computer failure or malfunction, or any and all |
||||
other commercial damages or losses), even if such Contributor |
||||
has been advised of the possibility of such damages. |
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing |
||||
the Work or Derivative Works thereof, You may choose to offer, |
||||
and charge a fee for, acceptance of support, warranty, indemnity, |
||||
or other liability obligations and/or rights consistent with this |
||||
License. However, in accepting such obligations, You may act only |
||||
on Your own behalf and on Your sole responsibility, not on behalf |
||||
of any other Contributor, and only if You agree to indemnify, |
||||
defend, and hold each Contributor harmless for any liability |
||||
incurred by, or claims asserted against, such Contributor by reason |
||||
of your accepting any such warranty or additional liability. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work. |
||||
|
||||
To apply the Apache License to your work, attach the following |
||||
boilerplate notice, with the fields enclosed by brackets "[]" |
||||
replaced with your own identifying information. (Don't include |
||||
the brackets!) The text should be enclosed in the appropriate |
||||
comment syntax for the file format. We also recommend that a |
||||
file or class name and description of purpose be included on the |
||||
same "printed page" as the copyright notice for easier |
||||
identification within third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
you may not use this file except in compliance with the License. |
||||
You may obtain a copy of the License at |
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
Unless required by applicable law or agreed to in writing, software |
||||
distributed under the License is distributed on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
See the License for the specific language governing permissions and |
||||
limitations under the License. |
@ -0,0 +1,47 @@ |
||||
Introduction |
||||
============ |
||||
|
||||
[![Travis Build Status][travisimg]][travis] |
||||
[![AppVeyor Build Status][appveyorimg]][appveyor] |
||||
[![GoDoc][docimg]][doc] |
||||
|
||||
The gousb package is an attempt at wrapping the `libusb` library into a Go-like binding in a fully self-contained, go-gettable package. Supported platforms include Linux, macOS and Windows as well as the mobile platforms Android and iOS. |
||||
|
||||
This package is a fork of [`github.com/kylelemons/gousb`](https://github.com/kylelemons/gousb), which at the moment seems to be unmaintained. The current fork is different from the upstream package as it contains code to embed `libusb` directly into the Go package (thus becoming fully self-cotnained and go-gettable), as well as it features a few contributions and bugfixes that never really got addressed in the upstream package, but which address important issues nonetheless. |
||||
|
||||
*Note, if @kylelemons decides to pick development of the upstream project up again, consider all commits made by me to this repo as ready contributions. I cannot vouch for other commits as the upstream repo needs a signed CLA for Google.* |
||||
|
||||
[travisimg]: https://travis-ci.org/karalabe/gousb.svg?branch=master |
||||
[travis]: https://travis-ci.org/karalabe/gousb |
||||
[appveyorimg]: https://ci.appveyor.com/api/projects/status/84k9xse10rl72gn2/branch/master?svg=true |
||||
[appveyor]: https://ci.appveyor.com/project/karalabe/gousb |
||||
[docimg]: https://godoc.org/github.com/karalabe/gousb?status.svg |
||||
[doc]: https://godoc.org/github.com/karalabe/gousb |
||||
|
||||
Installation |
||||
============ |
||||
|
||||
Example: lsusb |
||||
-------------- |
||||
The gousb project provides a simple but useful example: lsusb. This binary will list the USB devices connected to your system and various interesting tidbits about them, their configurations, endpoints, etc. To install it, run the following command: |
||||
|
||||
go get -v github.com/karalabe/gousb/lsusb |
||||
|
||||
gousb |
||||
----- |
||||
If you installed the lsusb example, both libraries below are already installed. |
||||
|
||||
Installing the primary gousb package is really easy: |
||||
|
||||
go get -v github.com/karalabe/gousb/usb |
||||
|
||||
There is also a `usbid` package that will not be installed by default by this command, but which provides useful information including the human-readable vendor and product codes for detected hardware. It's not installed by default and not linked into the `usb` package by default because it adds ~400kb to the resulting binary. If you want both, they can be installed thus: |
||||
|
||||
go get -v github.com/karalabe/gousb/usb{,id} |
||||
|
||||
Documentation |
||||
============= |
||||
The documentation can be viewed via local godoc or via the excellent [godoc.org](http://godoc.org/): |
||||
|
||||
- [usb](http://godoc.org/github.com/karalabe/gousb/usb) |
||||
- [usbid](http://godoc.org/pkg/github.com/karalabe/gousb/usbid) |
@ -0,0 +1,34 @@ |
||||
os: Visual Studio 2015 |
||||
|
||||
# Clone directly into GOPATH. |
||||
clone_folder: C:\gopath\src\github.com\karalabe\gousb |
||||
clone_depth: 5 |
||||
version: "{branch}.{build}" |
||||
environment: |
||||
global: |
||||
GOPATH: C:\gopath |
||||
CC: gcc.exe |
||||
matrix: |
||||
- GOARCH: amd64 |
||||
MSYS2_ARCH: x86_64 |
||||
MSYS2_BITS: 64 |
||||
MSYSTEM: MINGW64 |
||||
PATH: C:\msys64\mingw64\bin\;%PATH% |
||||
- GOARCH: 386 |
||||
MSYS2_ARCH: i686 |
||||
MSYS2_BITS: 32 |
||||
MSYSTEM: MINGW32 |
||||
PATH: C:\msys64\mingw32\bin\;%PATH% |
||||
|
||||
install: |
||||
- rmdir C:\go /s /q |
||||
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.7.4.windows-%GOARCH%.zip |
||||
- 7z x go1.7.4.windows-%GOARCH%.zip -y -oC:\ > NUL |
||||
- go version |
||||
- gcc --version |
||||
|
||||
build_script: |
||||
- go install ./... |
||||
|
||||
test_script: |
||||
- go test -v -test.run="BCD|Parse" ./... |
@ -0,0 +1,89 @@ |
||||
Copyright © 2001 Johannes Erdfelt <johannes@erdfelt.com> |
||||
Copyright © 2007-2009 Daniel Drake <dsd@gentoo.org> |
||||
Copyright © 2010-2012 Peter Stuge <peter@stuge.se> |
||||
Copyright © 2008-2016 Nathan Hjelm <hjelmn@users.sourceforge.net> |
||||
Copyright © 2009-2013 Pete Batard <pete@akeo.ie> |
||||
Copyright © 2009-2013 Ludovic Rousseau <ludovic.rousseau@gmail.com> |
||||
Copyright © 2010-2012 Michael Plante <michael.plante@gmail.com> |
||||
Copyright © 2011-2013 Hans de Goede <hdegoede@redhat.com> |
||||
Copyright © 2012-2013 Martin Pieuchot <mpi@openbsd.org> |
||||
Copyright © 2012-2013 Toby Gray <toby.gray@realvnc.com> |
||||
Copyright © 2013-2015 Chris Dickens <christopher.a.dickens@gmail.com> |
||||
|
||||
Other contributors: |
||||
Akshay Jaggi |
||||
Alan Ott |
||||
Alan Stern |
||||
Alex Vatchenko |
||||
Andrew Fernandes |
||||
Anthony Clay |
||||
Antonio Ospite |
||||
Artem Egorkine |
||||
Aurelien Jarno |
||||
Bastien Nocera |
||||
Bei Zhang |
||||
Benjamin Dobell |
||||
Carl Karsten |
||||
Colin Walters |
||||
Dave Camarillo |
||||
David Engraf |
||||
David Moore |
||||
Davidlohr Bueso |
||||
Federico Manzan |
||||
Felipe Balbi |
||||
Florian Albrechtskirchinger |
||||
Francesco Montorsi |
||||
Francisco Facioni |
||||
Gaurav Gupta |
||||
Graeme Gill |
||||
Gustavo Zacarias |
||||
Hans Ulrich Niedermann |
||||
Hector Martin |
||||
Hoi-Ho Chan |
||||
Ilya Konstantinov |
||||
James Hanko |
||||
John Sheu |
||||
Joshua Blake |
||||
Justin Bischoff |
||||
Karsten Koenig |
||||
Konrad Rzepecki |
||||
Kuangye Guo |
||||
Lars Kanis |
||||
Lars Wirzenius |
||||
Luca Longinotti |
||||
Marcus Meissner |
||||
Markus Heidelberg |
||||
Martin Ettl |
||||
Martin Koegler |
||||
Matthias Bolte |
||||
Mike Frysinger |
||||
Mikhail Gusarov |
||||
Moritz Fischer |
||||
Ларионов Даниил |
||||
Nicholas Corgan |
||||
Omri Iluz |
||||
Orin Eman |
||||
Paul Fertser |
||||
Pekka Nikander |
||||
Rob Walker |
||||
Sean McBride |
||||
Sebastian Pipping |
||||
Simon Haggett |
||||
Simon Newton |
||||
Thomas Röfer |
||||
Tim Hutt |
||||
Tim Roberts |
||||
Tobias Klauser |
||||
Toby Peterson |
||||
Tormod Volden |
||||
Trygve Laugstøl |
||||
Uri Lublin |
||||
Vasily Khoruzhick |
||||
Vegard Storheil Eriksen |
||||
Venkatesh Shukla |
||||
Vitali Lovich |
||||
Xiaofan Chen |
||||
Zoltán Kovács |
||||
Роман Донченко |
||||
parafin |
||||
xantares |
@ -0,0 +1,3 @@ |
||||
#ifndef CONFIG_H |
||||
#define CONFIG_H |
||||
#endif |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,350 @@ |
||||
/* -*- Mode: C; indent-tabs-mode:t ; c-basic-offset:8 -*- */ |
||||
/*
|
||||
* Hotplug functions for libusb |
||||
* Copyright © 2012-2013 Nathan Hjelm <hjelmn@mac.com> |
||||
* Copyright © 2012-2013 Peter Stuge <peter@stuge.se> |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU Lesser General Public |
||||
* License as published by the Free Software Foundation; either |
||||
* version 2.1 of the License, or (at your option) any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
* Lesser General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Lesser General Public |
||||
* License along with this library; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
*/ |
||||
|
||||
#include <config.h> |
||||
|
||||
#include <errno.h> |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#ifdef HAVE_SYS_TYPES_H |
||||
#include <sys/types.h> |
||||
#endif |
||||
#include <assert.h> |
||||
|
||||
#include "libusbi.h" |
||||
#include "hotplug.h" |
||||
|
||||
/**
|
||||
* @defgroup libusb_hotplug Device hotplug event notification |
||||
* This page details how to use the libusb hotplug interface, where available. |
||||
* |
||||
* Be mindful that not all platforms currently implement hotplug notification and |
||||
* that you should first call on \ref libusb_has_capability() with parameter |
||||
* \ref LIBUSB_CAP_HAS_HOTPLUG to confirm that hotplug support is available. |
||||
* |
||||
* \page libusb_hotplug Device hotplug event notification |
||||
* |
||||
* \section hotplug_intro Introduction |
||||
* |
||||
* Version 1.0.16, \ref LIBUSB_API_VERSION >= 0x01000102, has added support |
||||
* for hotplug events on <b>some</b> platforms (you should test if your platform |
||||
* supports hotplug notification by calling \ref libusb_has_capability() with |
||||
* parameter \ref LIBUSB_CAP_HAS_HOTPLUG).
|
||||
* |
||||
* This interface allows you to request notification for the arrival and departure |
||||
* of matching USB devices. |
||||
* |
||||
* To receive hotplug notification you register a callback by calling |
||||
* \ref libusb_hotplug_register_callback(). This function will optionally return |
||||
* a callback handle that can be passed to \ref libusb_hotplug_deregister_callback(). |
||||
* |
||||
* A callback function must return an int (0 or 1) indicating whether the callback is |
||||
* expecting additional events. Returning 0 will rearm the callback and 1 will cause |
||||
* the callback to be deregistered. Note that when callbacks are called from |
||||
* libusb_hotplug_register_callback() because of the \ref LIBUSB_HOTPLUG_ENUMERATE |
||||
* flag, the callback return value is ignored, iow you cannot cause a callback |
||||
* to be deregistered by returning 1 when it is called from |
||||
* libusb_hotplug_register_callback(). |
||||
* |
||||
* Callbacks for a particular context are automatically deregistered by libusb_exit(). |
||||
* |
||||
* As of 1.0.16 there are two supported hotplug events: |
||||
* - LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED: A device has arrived and is ready to use |
||||
* - LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT: A device has left and is no longer available |
||||
* |
||||
* A hotplug event can listen for either or both of these events. |
||||
* |
||||
* Note: If you receive notification that a device has left and you have any |
||||
* a libusb_device_handles for the device it is up to you to call libusb_close() |
||||
* on each device handle to free up any remaining resources associated with the device. |
||||
* Once a device has left any libusb_device_handle associated with the device |
||||
* are invalid and will remain so even if the device comes back. |
||||
* |
||||
* When handling a LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED event it is considered |
||||
* safe to call any libusb function that takes a libusb_device. It also safe to |
||||
* open a device and submit asynchronous transfers. However, most other functions |
||||
* that take a libusb_device_handle are <b>not</b> safe to call. Examples of such |
||||
* functions are any of the \ref libusb_syncio "synchronous API" functions or the blocking |
||||
* functions that retrieve various \ref libusb_desc "USB descriptors". These functions must |
||||
* be used outside of the context of the hotplug callback. |
||||
* |
||||
* When handling a LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT event the only safe function |
||||
* is libusb_get_device_descriptor(). |
||||
* |
||||
* The following code provides an example of the usage of the hotplug interface: |
||||
\code |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <time.h> |
||||
#include <libusb.h> |
||||
|
||||
static int count = 0; |
||||
|
||||
int hotplug_callback(struct libusb_context *ctx, struct libusb_device *dev, |
||||
libusb_hotplug_event event, void *user_data) { |
||||
static libusb_device_handle *dev_handle = NULL; |
||||
struct libusb_device_descriptor desc; |
||||
int rc; |
||||
|
||||
(void)libusb_get_device_descriptor(dev, &desc); |
||||
|
||||
if (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED == event) { |
||||
rc = libusb_open(dev, &dev_handle); |
||||
if (LIBUSB_SUCCESS != rc) { |
||||
printf("Could not open USB device\n"); |
||||
} |
||||
} else if (LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT == event) { |
||||
if (dev_handle) { |
||||
libusb_close(dev_handle); |
||||
dev_handle = NULL; |
||||
} |
||||
} else { |
||||
printf("Unhandled event %d\n", event); |
||||
} |
||||
count++; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int main (void) { |
||||
libusb_hotplug_callback_handle callback_handle; |
||||
int rc; |
||||
|
||||
libusb_init(NULL); |
||||
|
||||
rc = libusb_hotplug_register_callback(NULL, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | |
||||
LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, 0, 0x045a, 0x5005, |
||||
LIBUSB_HOTPLUG_MATCH_ANY, hotplug_callback, NULL, |
||||
&callback_handle); |
||||
if (LIBUSB_SUCCESS != rc) { |
||||
printf("Error creating a hotplug callback\n"); |
||||
libusb_exit(NULL); |
||||
return EXIT_FAILURE; |
||||
} |
||||
|
||||
while (count < 2) { |
||||
libusb_handle_events_completed(NULL, NULL); |
||||
nanosleep(&(struct timespec){0, 10000000UL}, NULL); |
||||
} |
||||
|
||||
libusb_hotplug_deregister_callback(NULL, callback_handle); |
||||
libusb_exit(NULL); |
||||
|
||||
return 0; |
||||
} |
||||
\endcode |
||||
*/ |
||||
|
||||
static int usbi_hotplug_match_cb (struct libusb_context *ctx, |
||||
struct libusb_device *dev, libusb_hotplug_event event, |
||||
struct libusb_hotplug_callback *hotplug_cb) |
||||
{ |
||||
/* Handle lazy deregistration of callback */ |
||||
if (hotplug_cb->needs_free) { |
||||
/* Free callback */ |
||||
return 1; |
||||
} |
||||
|
||||
if (!(hotplug_cb->events & event)) { |
||||
return 0; |
||||
} |
||||
|
||||
if (LIBUSB_HOTPLUG_MATCH_ANY != hotplug_cb->vendor_id && |
||||
hotplug_cb->vendor_id != dev->device_descriptor.idVendor) { |
||||
return 0; |
||||
} |
||||
|
||||
if (LIBUSB_HOTPLUG_MATCH_ANY != hotplug_cb->product_id && |
||||
hotplug_cb->product_id != dev->device_descriptor.idProduct) { |
||||
return 0; |
||||
} |
||||
|
||||
if (LIBUSB_HOTPLUG_MATCH_ANY != hotplug_cb->dev_class && |
||||
hotplug_cb->dev_class != dev->device_descriptor.bDeviceClass) { |
||||
return 0; |
||||
} |
||||
|
||||
return hotplug_cb->cb (ctx, dev, event, hotplug_cb->user_data); |
||||
} |
||||
|
||||
void usbi_hotplug_match(struct libusb_context *ctx, struct libusb_device *dev, |
||||
libusb_hotplug_event event) |
||||
{ |
||||
struct libusb_hotplug_callback *hotplug_cb, *next; |
||||
int ret; |
||||
|
||||
usbi_mutex_lock(&ctx->hotplug_cbs_lock); |
||||
|
||||
list_for_each_entry_safe(hotplug_cb, next, &ctx->hotplug_cbs, list, struct libusb_hotplug_callback) { |
||||
usbi_mutex_unlock(&ctx->hotplug_cbs_lock); |
||||
ret = usbi_hotplug_match_cb (ctx, dev, event, hotplug_cb); |
||||
usbi_mutex_lock(&ctx->hotplug_cbs_lock); |
||||
|
||||
if (ret) { |
||||
list_del(&hotplug_cb->list); |
||||
free(hotplug_cb); |
||||
} |
||||
} |
||||
|
||||
usbi_mutex_unlock(&ctx->hotplug_cbs_lock); |
||||
|
||||
/* the backend is expected to call the callback for each active transfer */ |
||||
} |
||||
|
||||
void usbi_hotplug_notification(struct libusb_context *ctx, struct libusb_device *dev, |
||||
libusb_hotplug_event event) |
||||
{ |
||||
int pending_events; |
||||
libusb_hotplug_message *message = calloc(1, sizeof(*message)); |
||||
|
||||
if (!message) { |
||||
usbi_err(ctx, "error allocating hotplug message"); |
||||
return; |
||||
} |
||||
|
||||
message->event = event; |
||||
message->device = dev; |
||||
|
||||
/* Take the event data lock and add this message to the list.
|
||||
* Only signal an event if there are no prior pending events. */ |
||||
usbi_mutex_lock(&ctx->event_data_lock); |
||||
pending_events = usbi_pending_events(ctx); |
||||
list_add_tail(&message->list, &ctx->hotplug_msgs); |
||||
if (!pending_events) |
||||
usbi_signal_event(ctx); |
||||
usbi_mutex_unlock(&ctx->event_data_lock); |
||||
} |
||||
|
||||
int API_EXPORTED libusb_hotplug_register_callback(libusb_context *ctx, |
||||
libusb_hotplug_event events, libusb_hotplug_flag flags, |
||||
int vendor_id, int product_id, int dev_class, |
||||
libusb_hotplug_callback_fn cb_fn, void *user_data, |
||||
libusb_hotplug_callback_handle *callback_handle) |
||||
{ |
||||
libusb_hotplug_callback *new_callback; |
||||
static int handle_id = 1; |
||||
|
||||
/* check for hotplug support */ |
||||
if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { |
||||
return LIBUSB_ERROR_NOT_SUPPORTED; |
||||
} |
||||
|
||||
/* check for sane values */ |
||||
if ((LIBUSB_HOTPLUG_MATCH_ANY != vendor_id && (~0xffff & vendor_id)) || |
||||
(LIBUSB_HOTPLUG_MATCH_ANY != product_id && (~0xffff & product_id)) || |
||||
(LIBUSB_HOTPLUG_MATCH_ANY != dev_class && (~0xff & dev_class)) || |
||||
!cb_fn) { |
||||
return LIBUSB_ERROR_INVALID_PARAM; |
||||
} |
||||
|
||||
USBI_GET_CONTEXT(ctx); |
||||
|
||||
new_callback = (libusb_hotplug_callback *)calloc(1, sizeof (*new_callback)); |
||||
if (!new_callback) { |
||||
return LIBUSB_ERROR_NO_MEM; |
||||
} |
||||
|
||||
new_callback->ctx = ctx; |
||||
new_callback->vendor_id = vendor_id; |
||||
new_callback->product_id = product_id; |
||||
new_callback->dev_class = dev_class; |
||||
new_callback->flags = flags; |
||||
new_callback->events = events; |
||||
new_callback->cb = cb_fn; |
||||
new_callback->user_data = user_data; |
||||
new_callback->needs_free = 0; |
||||
|
||||
usbi_mutex_lock(&ctx->hotplug_cbs_lock); |
||||
|
||||
/* protect the handle by the context hotplug lock. it doesn't matter if the same handle
|
||||
* is used for different contexts only that the handle is unique for this context */ |
||||
new_callback->handle = handle_id++; |
||||
|
||||
list_add(&new_callback->list, &ctx->hotplug_cbs); |
||||
|
||||
usbi_mutex_unlock(&ctx->hotplug_cbs_lock); |
||||
|
||||
|
||||
if (flags & LIBUSB_HOTPLUG_ENUMERATE) { |
||||
int i, len; |
||||
struct libusb_device **devs; |
||||
|
||||
len = (int) libusb_get_device_list(ctx, &devs); |
||||
if (len < 0) { |
||||
libusb_hotplug_deregister_callback(ctx, |
||||
new_callback->handle); |
||||
return len; |
||||
} |
||||
|
||||
for (i = 0; i < len; i++) { |
||||
usbi_hotplug_match_cb(ctx, devs[i], |
||||
LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, |
||||
new_callback); |
||||
} |
||||
|
||||
libusb_free_device_list(devs, 1); |
||||
} |
||||
|
||||
|
||||
if (callback_handle) |
||||
*callback_handle = new_callback->handle; |
||||
|
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
void API_EXPORTED libusb_hotplug_deregister_callback (struct libusb_context *ctx, |
||||
libusb_hotplug_callback_handle callback_handle) |
||||
{ |
||||
struct libusb_hotplug_callback *hotplug_cb; |
||||
|
||||
/* check for hotplug support */ |
||||
if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { |
||||
return; |
||||
} |
||||
|
||||
USBI_GET_CONTEXT(ctx); |
||||
|
||||
usbi_mutex_lock(&ctx->hotplug_cbs_lock); |
||||
list_for_each_entry(hotplug_cb, &ctx->hotplug_cbs, list, |
||||
struct libusb_hotplug_callback) { |
||||
if (callback_handle == hotplug_cb->handle) { |
||||
/* Mark this callback for deregistration */ |
||||
hotplug_cb->needs_free = 1; |
||||
} |
||||
} |
||||
usbi_mutex_unlock(&ctx->hotplug_cbs_lock); |
||||
|
||||
usbi_hotplug_notification(ctx, NULL, 0); |
||||
} |
||||
|
||||
void usbi_hotplug_deregister_all(struct libusb_context *ctx) { |
||||
struct libusb_hotplug_callback *hotplug_cb, *next; |
||||
|
||||
usbi_mutex_lock(&ctx->hotplug_cbs_lock); |
||||
list_for_each_entry_safe(hotplug_cb, next, &ctx->hotplug_cbs, list, |
||||
struct libusb_hotplug_callback) { |
||||
list_del(&hotplug_cb->list); |
||||
free(hotplug_cb); |
||||
} |
||||
|
||||
usbi_mutex_unlock(&ctx->hotplug_cbs_lock); |
||||
} |
@ -0,0 +1,90 @@ |
||||
/* -*- Mode: C; indent-tabs-mode:t ; c-basic-offset:8 -*- */ |
||||
/*
|
||||
* Hotplug support for libusb |
||||
* Copyright © 2012-2013 Nathan Hjelm <hjelmn@mac.com> |
||||
* Copyright © 2012-2013 Peter Stuge <peter@stuge.se> |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU Lesser General Public |
||||
* License as published by the Free Software Foundation; either |
||||
* version 2.1 of the License, or (at your option) any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
* Lesser General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Lesser General Public |
||||
* License along with this library; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
*/ |
||||
|
||||
#if !defined(USBI_HOTPLUG_H) |
||||
#define USBI_HOTPLUG_H |
||||
|
||||
#ifndef LIBUSBI_H |
||||
#include "libusbi.h" |
||||
#endif |
||||
|
||||
/** \ingroup hotplug
|
||||
* The hotplug callback structure. The user populates this structure with |
||||
* libusb_hotplug_prepare_callback() and then calls libusb_hotplug_register_callback() |
||||
* to receive notification of hotplug events. |
||||
*/ |
||||
struct libusb_hotplug_callback { |
||||
/** Context this callback is associated with */ |
||||
struct libusb_context *ctx; |
||||
|
||||
/** Vendor ID to match or LIBUSB_HOTPLUG_MATCH_ANY */ |
||||
int vendor_id; |
||||
|
||||
/** Product ID to match or LIBUSB_HOTPLUG_MATCH_ANY */ |
||||
int product_id; |
||||
|
||||
/** Device class to match or LIBUSB_HOTPLUG_MATCH_ANY */ |
||||
int dev_class; |
||||
|
||||
/** Hotplug callback flags */ |
||||
libusb_hotplug_flag flags; |
||||
|
||||
/** Event(s) that will trigger this callback */ |
||||
libusb_hotplug_event events; |
||||
|
||||
/** Callback function to invoke for matching event/device */ |
||||
libusb_hotplug_callback_fn cb; |
||||
|
||||
/** Handle for this callback (used to match on deregister) */ |
||||
libusb_hotplug_callback_handle handle; |
||||
|
||||
/** User data that will be passed to the callback function */ |
||||
void *user_data; |
||||
|
||||
/** Callback is marked for deletion */ |
||||
int needs_free; |
||||
|
||||
/** List this callback is registered in (ctx->hotplug_cbs) */ |
||||
struct list_head list; |
||||
}; |
||||
|
||||
typedef struct libusb_hotplug_callback libusb_hotplug_callback; |
||||
|
||||
struct libusb_hotplug_message { |
||||
/** The hotplug event that occurred */ |
||||
libusb_hotplug_event event; |
||||
|
||||
/** The device for which this hotplug event occurred */ |
||||
struct libusb_device *device; |
||||
|
||||
/** List this message is contained in (ctx->hotplug_msgs) */ |
||||
struct list_head list; |
||||
}; |
||||
|
||||
typedef struct libusb_hotplug_message libusb_hotplug_message; |
||||
|
||||
void usbi_hotplug_deregister_all(struct libusb_context *ctx); |
||||
void usbi_hotplug_match(struct libusb_context *ctx, struct libusb_device *dev, |
||||
libusb_hotplug_event event); |
||||
void usbi_hotplug_notification(struct libusb_context *ctx, struct libusb_device *dev, |
||||
libusb_hotplug_event event); |
||||
|
||||
#endif |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,164 @@ |
||||
/*
|
||||
* darwin backend for libusb 1.0 |
||||
* Copyright © 2008-2015 Nathan Hjelm <hjelmn@users.sourceforge.net> |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU Lesser General Public |
||||
* License as published by the Free Software Foundation; either |
||||
* version 2.1 of the License, or (at your option) any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
* Lesser General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Lesser General Public |
||||
* License along with this library; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
*/ |
||||
|
||||
#if !defined(LIBUSB_DARWIN_H) |
||||
#define LIBUSB_DARWIN_H |
||||
|
||||
#include "libusbi.h" |
||||
|
||||
#include <IOKit/IOTypes.h> |
||||
#include <IOKit/IOCFBundle.h> |
||||
#include <IOKit/usb/IOUSBLib.h> |
||||
#include <IOKit/IOCFPlugIn.h> |
||||
|
||||
/* IOUSBInterfaceInferface */ |
||||
#if defined (kIOUSBInterfaceInterfaceID700) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_9 |
||||
|
||||
#define usb_interface_t IOUSBInterfaceInterface700 |
||||
#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID700 |
||||
#define InterfaceVersion 700 |
||||
|
||||
#elif defined (kIOUSBInterfaceInterfaceID550) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_9 |
||||
|
||||
#define usb_interface_t IOUSBInterfaceInterface550 |
||||
#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID550 |
||||
#define InterfaceVersion 550 |
||||
|
||||
#elif defined (kIOUSBInterfaceInterfaceID500) |
||||
|
||||
#define usb_interface_t IOUSBInterfaceInterface500 |
||||
#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID500 |
||||
#define InterfaceVersion 500 |
||||
|
||||
#elif defined (kIOUSBInterfaceInterfaceID300) |
||||
|
||||
#define usb_interface_t IOUSBInterfaceInterface300 |
||||
#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID300 |
||||
#define InterfaceVersion 300 |
||||
|
||||
#elif defined (kIOUSBInterfaceInterfaceID245) |
||||
|
||||
#define usb_interface_t IOUSBInterfaceInterface245 |
||||
#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID245 |
||||
#define InterfaceVersion 245 |
||||
|
||||
#elif defined (kIOUSBInterfaceInterfaceID220) |
||||
|
||||
#define usb_interface_t IOUSBInterfaceInterface220 |
||||
#define InterfaceInterfaceID kIOUSBInterfaceInterfaceID220 |
||||
#define InterfaceVersion 220 |
||||
|
||||
#else |
||||
|
||||
#error "IOUSBFamily is too old. Please upgrade your OS" |
||||
|
||||
#endif |
||||
|
||||
/* IOUSBDeviceInterface */ |
||||
#if defined (kIOUSBDeviceInterfaceID500) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_9 |
||||
|
||||
#define usb_device_t IOUSBDeviceInterface500 |
||||
#define DeviceInterfaceID kIOUSBDeviceInterfaceID500 |
||||
#define DeviceVersion 500 |
||||
|
||||
#elif defined (kIOUSBDeviceInterfaceID320) |
||||
|
||||
#define usb_device_t IOUSBDeviceInterface320 |
||||
#define DeviceInterfaceID kIOUSBDeviceInterfaceID320 |
||||
#define DeviceVersion 320 |
||||
|
||||
#elif defined (kIOUSBDeviceInterfaceID300) |
||||
|
||||
#define usb_device_t IOUSBDeviceInterface300 |
||||
#define DeviceInterfaceID kIOUSBDeviceInterfaceID300 |
||||
#define DeviceVersion 300 |
||||
|
||||
#elif defined (kIOUSBDeviceInterfaceID245) |
||||
|
||||
#define usb_device_t IOUSBDeviceInterface245 |
||||
#define DeviceInterfaceID kIOUSBDeviceInterfaceID245 |
||||
#define DeviceVersion 245 |
||||
|
||||
#elif defined (kIOUSBDeviceInterfaceID220) |
||||
#define usb_device_t IOUSBDeviceInterface197 |
||||
#define DeviceInterfaceID kIOUSBDeviceInterfaceID197 |
||||
#define DeviceVersion 197 |
||||
|
||||
#else |
||||
|
||||
#error "IOUSBFamily is too old. Please upgrade your OS" |
||||
|
||||
#endif |
||||
|
||||
#if !defined(IO_OBJECT_NULL) |
||||
#define IO_OBJECT_NULL ((io_object_t) 0) |
||||
#endif |
||||
|
||||
typedef IOCFPlugInInterface *io_cf_plugin_ref_t; |
||||
typedef IONotificationPortRef io_notification_port_t; |
||||
|
||||
/* private structures */ |
||||
struct darwin_cached_device { |
||||
struct list_head list; |
||||
IOUSBDeviceDescriptor dev_descriptor; |
||||
UInt32 location; |
||||
UInt64 parent_session; |
||||
UInt64 session; |
||||
UInt16 address; |
||||
char sys_path[21]; |
||||
usb_device_t **device; |
||||
int open_count; |
||||
UInt8 first_config, active_config, port;
|
||||
int can_enumerate; |
||||
int refcount; |
||||
}; |
||||
|
||||
struct darwin_device_priv { |
||||
struct darwin_cached_device *dev; |
||||
}; |
||||
|
||||
struct darwin_device_handle_priv { |
||||
int is_open; |
||||
CFRunLoopSourceRef cfSource; |
||||
|
||||
struct darwin_interface { |
||||
usb_interface_t **interface; |
||||
uint8_t num_endpoints; |
||||
CFRunLoopSourceRef cfSource; |
||||
uint64_t frames[256]; |
||||
uint8_t endpoint_addrs[USB_MAXENDPOINTS]; |
||||
} interfaces[USB_MAXINTERFACES]; |
||||
}; |
||||
|
||||
struct darwin_transfer_priv { |
||||
/* Isoc */ |
||||
IOUSBIsocFrame *isoc_framelist; |
||||
int num_iso_packets; |
||||
|
||||
/* Control */ |
||||
IOUSBDevRequestTO req; |
||||
|
||||
/* Bulk */ |
||||
|
||||
/* Completion status */ |
||||
IOReturn result; |
||||
UInt32 size; |
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,367 @@ |
||||
/*
|
||||
* Copyright 2007-2008, Haiku Inc. All rights reserved. |
||||
* Distributed under the terms of the MIT License. |
||||
* |
||||
* Authors: |
||||
* Michael Lotz <mmlr@mlotz.ch> |
||||
*/ |
||||
|
||||
#include "haiku_usb.h" |
||||
#include <cstdio> |
||||
#include <Directory.h> |
||||
#include <Entry.h> |
||||
#include <Looper.h> |
||||
#include <Messenger.h> |
||||
#include <Node.h> |
||||
#include <NodeMonitor.h> |
||||
#include <Path.h> |
||||
#include <cstring> |
||||
|
||||
class WatchedEntry { |
||||
public: |
||||
WatchedEntry(BMessenger *, entry_ref *); |
||||
~WatchedEntry(); |
||||
bool EntryCreated(entry_ref *ref); |
||||
bool EntryRemoved(ino_t node); |
||||
bool InitCheck(); |
||||
|
||||
private: |
||||
BMessenger* fMessenger; |
||||
node_ref fNode; |
||||
bool fIsDirectory; |
||||
USBDevice* fDevice; |
||||
WatchedEntry* fEntries; |
||||
WatchedEntry* fLink; |
||||
bool fInitCheck; |
||||
}; |
||||
|
||||
|
||||
class RosterLooper : public BLooper { |
||||
public: |
||||
RosterLooper(USBRoster *); |
||||
void Stop(); |
||||
virtual void MessageReceived(BMessage *); |
||||
bool InitCheck(); |
||||
|
||||
private: |
||||
USBRoster* fRoster; |
||||
WatchedEntry* fRoot; |
||||
BMessenger* fMessenger; |
||||
bool fInitCheck; |
||||
}; |
||||
|
||||
|
||||
WatchedEntry::WatchedEntry(BMessenger *messenger, entry_ref *ref) |
||||
: fMessenger(messenger), |
||||
fIsDirectory(false), |
||||
fDevice(NULL), |
||||
fEntries(NULL), |
||||
fLink(NULL), |
||||
fInitCheck(false) |
||||
{ |
||||
BEntry entry(ref); |
||||
entry.GetNodeRef(&fNode); |
||||
|
||||
BDirectory directory; |
||||
if (entry.IsDirectory() && directory.SetTo(ref) >= B_OK) { |
||||
fIsDirectory = true; |
||||
|
||||
while (directory.GetNextEntry(&entry) >= B_OK) { |
||||
if (entry.GetRef(ref) < B_OK) |
||||
continue; |
||||
|
||||
WatchedEntry *child = new(std::nothrow) WatchedEntry(fMessenger, ref); |
||||
if (child == NULL) |
||||
continue; |
||||
if (child->InitCheck() == false) { |
||||
delete child; |
||||
continue; |
||||
} |
||||
|
||||
child->fLink = fEntries; |
||||
fEntries = child; |
||||
} |
||||
|
||||
watch_node(&fNode, B_WATCH_DIRECTORY, *fMessenger); |
||||
} |
||||
else { |
||||
if (strncmp(ref->name, "raw", 3) == 0) |
||||
return; |
||||
|
||||
BPath path, parent_path; |
||||
entry.GetPath(&path); |
||||
fDevice = new(std::nothrow) USBDevice(path.Path()); |
||||
if (fDevice != NULL && fDevice->InitCheck() == true) { |
||||
// Add this new device to each active context's device list
|
||||
struct libusb_context *ctx; |
||||
unsigned long session_id = (unsigned long)&fDevice; |
||||
|
||||
usbi_mutex_lock(&active_contexts_lock); |
||||
list_for_each_entry(ctx, &active_contexts_list, list, struct libusb_context) { |
||||
struct libusb_device *dev = usbi_get_device_by_session_id(ctx, session_id); |
||||
if (dev) { |
||||
usbi_dbg("using previously allocated device with location %lu", session_id); |
||||
libusb_unref_device(dev); |
||||
continue; |
||||
} |
||||
usbi_dbg("allocating new device with location %lu", session_id); |
||||
dev = usbi_alloc_device(ctx, session_id); |
||||
if (!dev) { |
||||
usbi_dbg("device allocation failed"); |
||||
continue; |
||||
} |
||||
*((USBDevice **)dev->os_priv) = fDevice; |
||||
|
||||
// Calculate pseudo-device-address
|
||||
int addr, tmp; |
||||
if (strcmp(path.Leaf(), "hub") == 0) |
||||
tmp = 100; //Random Number
|
||||
else |
||||
sscanf(path.Leaf(), "%d", &tmp); |
||||
addr = tmp + 1; |
||||
path.GetParent(&parent_path); |
||||
while (strcmp(parent_path.Leaf(), "usb") != 0) { |
||||
sscanf(parent_path.Leaf(), "%d", &tmp); |
||||
addr += tmp + 1; |
||||
parent_path.GetParent(&parent_path); |
||||
} |
||||
sscanf(path.Path(), "/dev/bus/usb/%d", &dev->bus_number); |
||||
dev->device_address = addr - (dev->bus_number + 1); |
||||
|
||||
if (usbi_sanitize_device(dev) < 0) { |
||||
usbi_dbg("device sanitization failed"); |
||||
libusb_unref_device(dev); |
||||
continue; |
||||
} |
||||
usbi_connect_device(dev); |
||||
} |
||||
usbi_mutex_unlock(&active_contexts_lock); |
||||
} |
||||
else if (fDevice) { |
||||
delete fDevice; |
||||
fDevice = NULL; |
||||
return; |
||||
} |
||||
} |
||||
fInitCheck = true; |
||||
} |
||||
|
||||
|
||||
WatchedEntry::~WatchedEntry() |
||||
{ |
||||
if (fIsDirectory) { |
||||
watch_node(&fNode, B_STOP_WATCHING, *fMessenger); |
||||
|
||||
WatchedEntry *child = fEntries; |
||||
while (child) { |
||||
WatchedEntry *next = child->fLink; |
||||
delete child; |
||||
child = next; |
||||
} |
||||
} |
||||
|
||||
if (fDevice) { |
||||
// Remove this device from each active context's device list
|
||||
struct libusb_context *ctx; |
||||
struct libusb_device *dev; |
||||
unsigned long session_id = (unsigned long)&fDevice; |
||||
|
||||
usbi_mutex_lock(&active_contexts_lock); |
||||
list_for_each_entry(ctx, &active_contexts_list, list, struct libusb_context) { |
||||
dev = usbi_get_device_by_session_id(ctx, session_id); |
||||
if (dev != NULL) { |
||||
usbi_disconnect_device(dev); |
||||
libusb_unref_device(dev); |
||||
} else { |
||||
usbi_dbg("device with location %lu not found", session_id); |
||||
} |
||||
} |
||||
usbi_mutex_static_unlock(&active_contexts_lock); |
||||
delete fDevice; |
||||
} |
||||
} |
||||
|
||||
|
||||
bool |
||||
WatchedEntry::EntryCreated(entry_ref *ref) |
||||
{ |
||||
if (!fIsDirectory) |
||||
return false; |
||||
|
||||
if (ref->directory != fNode.node) { |
||||
WatchedEntry *child = fEntries; |
||||
while (child) { |
||||
if (child->EntryCreated(ref)) |
||||
return true; |
||||
child = child->fLink; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
WatchedEntry *child = new(std::nothrow) WatchedEntry(fMessenger, ref); |
||||
if (child == NULL) |
||||
return false; |
||||
child->fLink = fEntries; |
||||
fEntries = child; |
||||
return true; |
||||
} |
||||
|
||||
|
||||
bool |
||||
WatchedEntry::EntryRemoved(ino_t node) |
||||
{ |
||||
if (!fIsDirectory) |
||||
return false; |
||||
|
||||
WatchedEntry *child = fEntries; |
||||
WatchedEntry *lastChild = NULL; |
||||
while (child) { |
||||
if (child->fNode.node == node) { |
||||
if (lastChild) |
||||
lastChild->fLink = child->fLink; |
||||
else |
||||
fEntries = child->fLink; |
||||
delete child; |
||||
return true; |
||||
} |
||||
|
||||
if (child->EntryRemoved(node)) |
||||
return true; |
||||
|
||||
lastChild = child; |
||||
child = child->fLink; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
|
||||
bool |
||||
WatchedEntry::InitCheck() |
||||
{ |
||||
return fInitCheck; |
||||
} |
||||
|
||||
|
||||
RosterLooper::RosterLooper(USBRoster *roster) |
||||
: BLooper("LibusbRoster Looper"), |
||||
fRoster(roster), |
||||
fRoot(NULL), |
||||
fMessenger(NULL), |
||||
fInitCheck(false) |
||||
{ |
||||
BEntry entry("/dev/bus/usb"); |
||||
if (!entry.Exists()) { |
||||
usbi_err(NULL, "usb_raw not published"); |
||||
return; |
||||
} |
||||
|
||||
Run(); |
||||
fMessenger = new(std::nothrow) BMessenger(this); |
||||
if (fMessenger == NULL) { |
||||
usbi_err(NULL, "error creating BMessenger object"); |
||||
return; |
||||
} |
||||
|
||||
if (Lock()) { |
||||
entry_ref ref; |
||||
entry.GetRef(&ref); |
||||
fRoot = new(std::nothrow) WatchedEntry(fMessenger, &ref); |
||||
Unlock(); |
||||
if (fRoot == NULL) |
||||
return; |
||||
if (fRoot->InitCheck() == false) { |
||||
delete fRoot; |
||||
fRoot = NULL; |
||||
return; |
||||
} |
||||
} |
||||
fInitCheck = true; |
||||
} |
||||
|
||||
|
||||
void |
||||
RosterLooper::Stop() |
||||
{ |
||||
Lock(); |
||||
delete fRoot; |
||||
delete fMessenger; |
||||
Quit(); |
||||
} |
||||
|
||||
|
||||
void |
||||
RosterLooper::MessageReceived(BMessage *message) |
||||
{ |
||||
int32 opcode; |
||||
if (message->FindInt32("opcode", &opcode) < B_OK) |
||||
return; |
||||
|
||||
switch (opcode) { |
||||
case B_ENTRY_CREATED: |
||||
{ |
||||
dev_t device; |
||||
ino_t directory; |
||||
const char *name; |
||||
if (message->FindInt32("device", &device) < B_OK || |
||||
message->FindInt64("directory", &directory) < B_OK || |
||||
message->FindString("name", &name) < B_OK) |
||||
break; |
||||
|
||||
entry_ref ref(device, directory, name); |
||||
fRoot->EntryCreated(&ref); |
||||
break; |
||||
} |
||||
case B_ENTRY_REMOVED: |
||||
{ |
||||
ino_t node; |
||||
if (message->FindInt64("node", &node) < B_OK) |
||||
break; |
||||
fRoot->EntryRemoved(node); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
bool |
||||
RosterLooper::InitCheck() |
||||
{ |
||||
return fInitCheck; |
||||
} |
||||
|
||||
|
||||
USBRoster::USBRoster() |
||||
: fLooper(NULL) |
||||
{ |
||||
} |
||||
|
||||
|
||||
USBRoster::~USBRoster() |
||||
{ |
||||
Stop(); |
||||
} |
||||
|
||||
|
||||
int |
||||
USBRoster::Start() |
||||
{ |
||||
if (fLooper == NULL) { |
||||
fLooper = new(std::nothrow) RosterLooper(this); |
||||
if (fLooper == NULL || ((RosterLooper *)fLooper)->InitCheck() == false) { |
||||
if (fLooper) |
||||
fLooper = NULL; |
||||
return LIBUSB_ERROR_OTHER; |
||||
} |
||||
} |
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
|
||||
void |
||||
USBRoster::Stop() |
||||
{ |
||||
if (fLooper) { |
||||
((RosterLooper *)fLooper)->Stop(); |
||||
fLooper = NULL; |
||||
} |
||||
} |
@ -0,0 +1,112 @@ |
||||
/*
|
||||
* Haiku Backend for libusb |
||||
* Copyright © 2014 Akshay Jaggi <akshay1994.leo@gmail.com> |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU Lesser General Public |
||||
* License as published by the Free Software Foundation; either |
||||
* version 2.1 of the License, or (at your option) any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
* Lesser General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Lesser General Public |
||||
* License along with this library; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
*/ |
||||
|
||||
#include <List.h> |
||||
#include <Locker.h> |
||||
#include <Autolock.h> |
||||
#include <USBKit.h> |
||||
#include <map> |
||||
#include "libusbi.h" |
||||
#include "haiku_usb_raw.h" |
||||
|
||||
using namespace std; |
||||
|
||||
class USBDevice; |
||||
class USBDeviceHandle; |
||||
class USBTransfer; |
||||
|
||||
class USBDevice { |
||||
public: |
||||
USBDevice(const char *); |
||||
virtual ~USBDevice(); |
||||
const char* Location() const; |
||||
uint8 CountConfigurations() const; |
||||
const usb_device_descriptor* Descriptor() const; |
||||
const usb_configuration_descriptor* ConfigurationDescriptor(uint32) const; |
||||
const usb_configuration_descriptor* ActiveConfiguration() const; |
||||
uint8 EndpointToIndex(uint8) const; |
||||
uint8 EndpointToInterface(uint8) const; |
||||
int ClaimInterface(int); |
||||
int ReleaseInterface(int); |
||||
int CheckInterfacesFree(int); |
||||
int SetActiveConfiguration(int); |
||||
int ActiveConfigurationIndex() const; |
||||
bool InitCheck(); |
||||
private: |
||||
int Initialise(); |
||||
unsigned int fClaimedInterfaces; // Max Interfaces can be 32. Using a bitmask
|
||||
usb_device_descriptor fDeviceDescriptor; |
||||
unsigned char** fConfigurationDescriptors; |
||||
int fActiveConfiguration; |
||||
char* fPath; |
||||
map<uint8,uint8> fConfigToIndex; |
||||
map<uint8,uint8>* fEndpointToIndex; |
||||
map<uint8,uint8>* fEndpointToInterface; |
||||
bool fInitCheck; |
||||
}; |
||||
|
||||
class USBDeviceHandle { |
||||
public: |
||||
USBDeviceHandle(USBDevice *dev); |
||||
virtual ~USBDeviceHandle(); |
||||
int ClaimInterface(int); |
||||
int ReleaseInterface(int); |
||||
int SetConfiguration(int); |
||||
int SetAltSetting(int, int); |
||||
status_t SubmitTransfer(struct usbi_transfer *); |
||||
status_t CancelTransfer(USBTransfer *); |
||||
bool InitCheck(); |
||||
private: |
||||
int fRawFD; |
||||
static status_t TransfersThread(void *); |
||||
void TransfersWorker(); |
||||
USBDevice* fUSBDevice; |
||||
unsigned int fClaimedInterfaces; |
||||
BList fTransfers; |
||||
BLocker fTransfersLock; |
||||
sem_id fTransfersSem; |
||||
thread_id fTransfersThread; |
||||
bool fInitCheck; |
||||
}; |
||||
|
||||
class USBTransfer { |
||||
public: |
||||
USBTransfer(struct usbi_transfer *, USBDevice *); |
||||
virtual ~USBTransfer(); |
||||
void Do(int); |
||||
struct usbi_transfer* UsbiTransfer(); |
||||
void SetCancelled(); |
||||
bool IsCancelled(); |
||||
private: |
||||
struct usbi_transfer* fUsbiTransfer; |
||||
struct libusb_transfer* fLibusbTransfer; |
||||
USBDevice* fUSBDevice; |
||||
BLocker fStatusLock; |
||||
bool fCancelled; |
||||
}; |
||||
|
||||
class USBRoster { |
||||
public: |
||||
USBRoster(); |
||||
virtual ~USBRoster(); |
||||
int Start(); |
||||
void Stop(); |
||||
private: |
||||
void* fLooper; |
||||
}; |
517
vendor/github.com/karalabe/gousb/internal/libusb/libusb/os/haiku_usb_backend.cpp
generated
vendored
517
vendor/github.com/karalabe/gousb/internal/libusb/libusb/os/haiku_usb_backend.cpp
generated
vendored
@ -0,0 +1,517 @@ |
||||
/*
|
||||
* Haiku Backend for libusb |
||||
* Copyright © 2014 Akshay Jaggi <akshay1994.leo@gmail.com> |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU Lesser General Public |
||||
* License as published by the Free Software Foundation; either |
||||
* version 2.1 of the License, or (at your option) any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
* Lesser General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Lesser General Public |
||||
* License along with this library; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
*/ |
||||
|
||||
|
||||
#include <unistd.h> |
||||
#include <string.h> |
||||
#include <stdlib.h> |
||||
#include <new> |
||||
#include <vector> |
||||
|
||||
#include "haiku_usb.h" |
||||
|
||||
int _errno_to_libusb(int status) |
||||
{ |
||||
return status; |
||||
} |
||||
|
||||
USBTransfer::USBTransfer(struct usbi_transfer *itransfer, USBDevice *device) |
||||
{ |
||||
fUsbiTransfer = itransfer; |
||||
fLibusbTransfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); |
||||
fUSBDevice = device; |
||||
fCancelled = false; |
||||
} |
||||
|
||||
USBTransfer::~USBTransfer() |
||||
{ |
||||
} |
||||
|
||||
struct usbi_transfer * |
||||
USBTransfer::UsbiTransfer() |
||||
{ |
||||
return fUsbiTransfer; |
||||
} |
||||
|
||||
void |
||||
USBTransfer::SetCancelled() |
||||
{ |
||||
fCancelled = true; |
||||
} |
||||
|
||||
bool |
||||
USBTransfer::IsCancelled() |
||||
{ |
||||
return fCancelled; |
||||
} |
||||
|
||||
void |
||||
USBTransfer::Do(int fRawFD) |
||||
{ |
||||
switch (fLibusbTransfer->type) { |
||||
case LIBUSB_TRANSFER_TYPE_CONTROL: |
||||
{ |
||||
struct libusb_control_setup *setup = (struct libusb_control_setup *)fLibusbTransfer->buffer; |
||||
usb_raw_command command; |
||||
command.control.request_type = setup->bmRequestType; |
||||
command.control.request = setup->bRequest; |
||||
command.control.value = setup->wValue; |
||||
command.control.index = setup->wIndex; |
||||
command.control.length = setup->wLength; |
||||
command.control.data = fLibusbTransfer->buffer + LIBUSB_CONTROL_SETUP_SIZE; |
||||
if (fCancelled) |
||||
break; |
||||
if (ioctl(fRawFD, B_USB_RAW_COMMAND_CONTROL_TRANSFER, &command, sizeof(command)) || |
||||
command.control.status != B_USB_RAW_STATUS_SUCCESS) { |
||||
fUsbiTransfer->transferred = -1; |
||||
usbi_err(TRANSFER_CTX(fLibusbTransfer), "failed control transfer"); |
||||
break; |
||||
} |
||||
fUsbiTransfer->transferred = command.control.length; |
||||
} |
||||
break; |
||||
case LIBUSB_TRANSFER_TYPE_BULK: |
||||
case LIBUSB_TRANSFER_TYPE_INTERRUPT: |
||||
{ |
||||
usb_raw_command command; |
||||
command.transfer.interface = fUSBDevice->EndpointToInterface(fLibusbTransfer->endpoint); |
||||
command.transfer.endpoint = fUSBDevice->EndpointToIndex(fLibusbTransfer->endpoint); |
||||
command.transfer.data = fLibusbTransfer->buffer; |
||||
command.transfer.length = fLibusbTransfer->length; |
||||
if (fCancelled) |
||||
break; |
||||
if (fLibusbTransfer->type == LIBUSB_TRANSFER_TYPE_BULK) { |
||||
if (ioctl(fRawFD, B_USB_RAW_COMMAND_BULK_TRANSFER, &command, sizeof(command)) || |
||||
command.transfer.status != B_USB_RAW_STATUS_SUCCESS) { |
||||
fUsbiTransfer->transferred = -1; |
||||
usbi_err(TRANSFER_CTX(fLibusbTransfer), "failed bulk transfer"); |
||||
break; |
||||
} |
||||
} |
||||
else { |
||||
if (ioctl(fRawFD, B_USB_RAW_COMMAND_INTERRUPT_TRANSFER, &command, sizeof(command)) || |
||||
command.transfer.status != B_USB_RAW_STATUS_SUCCESS) { |
||||
fUsbiTransfer->transferred = -1; |
||||
usbi_err(TRANSFER_CTX(fLibusbTransfer), "failed interrupt transfer"); |
||||
break; |
||||
} |
||||
} |
||||
fUsbiTransfer->transferred = command.transfer.length; |
||||
} |
||||
break; |
||||
// IsochronousTransfers not tested
|
||||
case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: |
||||
{ |
||||
usb_raw_command command; |
||||
command.isochronous.interface = fUSBDevice->EndpointToInterface(fLibusbTransfer->endpoint); |
||||
command.isochronous.endpoint = fUSBDevice->EndpointToIndex(fLibusbTransfer->endpoint); |
||||
command.isochronous.data = fLibusbTransfer->buffer; |
||||
command.isochronous.length = fLibusbTransfer->length; |
||||
command.isochronous.packet_count = fLibusbTransfer->num_iso_packets; |
||||
int i; |
||||
usb_iso_packet_descriptor *packetDescriptors = new usb_iso_packet_descriptor[fLibusbTransfer->num_iso_packets]; |
||||
for (i = 0; i < fLibusbTransfer->num_iso_packets; i++) { |
||||
if ((int16)(fLibusbTransfer->iso_packet_desc[i]).length != (fLibusbTransfer->iso_packet_desc[i]).length) { |
||||
fUsbiTransfer->transferred = -1; |
||||
usbi_err(TRANSFER_CTX(fLibusbTransfer), "failed isochronous transfer"); |
||||
break; |
||||
} |
||||
packetDescriptors[i].request_length = (int16)(fLibusbTransfer->iso_packet_desc[i]).length; |
||||
} |
||||
if (i < fLibusbTransfer->num_iso_packets) |
||||
break; // TODO Handle this error
|
||||
command.isochronous.packet_descriptors = packetDescriptors; |
||||
if (fCancelled) |
||||
break; |
||||
if (ioctl(fRawFD, B_USB_RAW_COMMAND_ISOCHRONOUS_TRANSFER, &command, sizeof(command)) || |
||||
command.isochronous.status != B_USB_RAW_STATUS_SUCCESS) { |
||||
fUsbiTransfer->transferred = -1; |
||||
usbi_err(TRANSFER_CTX(fLibusbTransfer), "failed isochronous transfer"); |
||||
break; |
||||
} |
||||
for (i = 0; i < fLibusbTransfer->num_iso_packets; i++) { |
||||
(fLibusbTransfer->iso_packet_desc[i]).actual_length = packetDescriptors[i].actual_length; |
||||
switch (packetDescriptors[i].status) { |
||||
case B_OK: |
||||
(fLibusbTransfer->iso_packet_desc[i]).status = LIBUSB_TRANSFER_COMPLETED; |
||||
break; |
||||
default: |
||||
(fLibusbTransfer->iso_packet_desc[i]).status = LIBUSB_TRANSFER_ERROR; |
||||
break; |
||||
} |
||||
} |
||||
delete[] packetDescriptors; |
||||
// Do we put the length of transfer here, for isochronous transfers?
|
||||
fUsbiTransfer->transferred = command.transfer.length; |
||||
} |
||||
break; |
||||
default: |
||||
usbi_err(TRANSFER_CTX(fLibusbTransfer), "Unknown type of transfer"); |
||||
} |
||||
} |
||||
|
||||
bool |
||||
USBDeviceHandle::InitCheck() |
||||
{ |
||||
return fInitCheck; |
||||
} |
||||
|
||||
status_t |
||||
USBDeviceHandle::TransfersThread(void *self) |
||||
{ |
||||
USBDeviceHandle *handle = (USBDeviceHandle *)self; |
||||
handle->TransfersWorker(); |
||||
return B_OK; |
||||
} |
||||
|
||||
void |
||||
USBDeviceHandle::TransfersWorker() |
||||
{ |
||||
while (true) { |
||||
status_t status = acquire_sem(fTransfersSem); |
||||
if (status == B_BAD_SEM_ID) |
||||
break; |
||||
if (status == B_INTERRUPTED) |
||||
continue; |
||||
fTransfersLock.Lock(); |
||||
USBTransfer *fPendingTransfer = (USBTransfer *) fTransfers.RemoveItem((int32)0); |
||||
fTransfersLock.Unlock(); |
||||
fPendingTransfer->Do(fRawFD); |
||||
usbi_signal_transfer_completion(fPendingTransfer->UsbiTransfer()); |
||||
} |
||||
} |
||||
|
||||
status_t |
||||
USBDeviceHandle::SubmitTransfer(struct usbi_transfer *itransfer) |
||||
{ |
||||
USBTransfer *transfer = new USBTransfer(itransfer, fUSBDevice); |
||||
*((USBTransfer **)usbi_transfer_get_os_priv(itransfer)) = transfer; |
||||
BAutolock locker(fTransfersLock); |
||||
fTransfers.AddItem(transfer); |
||||
release_sem(fTransfersSem); |
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
status_t |
||||
USBDeviceHandle::CancelTransfer(USBTransfer *transfer) |
||||
{ |
||||
transfer->SetCancelled(); |
||||
fTransfersLock.Lock(); |
||||
bool removed = fTransfers.RemoveItem(transfer); |
||||
fTransfersLock.Unlock(); |
||||
if(removed) |
||||
usbi_signal_transfer_completion(transfer->UsbiTransfer()); |
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
USBDeviceHandle::USBDeviceHandle(USBDevice *dev) |
||||
: |
||||
fTransfersThread(-1), |
||||
fUSBDevice(dev), |
||||
fClaimedInterfaces(0), |
||||
fInitCheck(false) |
||||
{ |
||||
fRawFD = open(dev->Location(), O_RDWR | O_CLOEXEC); |
||||
if (fRawFD < 0) { |
||||
usbi_err(NULL,"failed to open device"); |
||||
return; |
||||
} |
||||
fTransfersSem = create_sem(0, "Transfers Queue Sem"); |
||||
fTransfersThread = spawn_thread(TransfersThread, "Transfer Worker", B_NORMAL_PRIORITY, this); |
||||
resume_thread(fTransfersThread); |
||||
fInitCheck = true; |
||||
} |
||||
|
||||
USBDeviceHandle::~USBDeviceHandle() |
||||
{ |
||||
if (fRawFD > 0) |
||||
close(fRawFD); |
||||
for(int i = 0; i < 32; i++) { |
||||
if (fClaimedInterfaces & (1 << i)) |
||||
ReleaseInterface(i); |
||||
} |
||||
delete_sem(fTransfersSem); |
||||
if (fTransfersThread > 0) |
||||
wait_for_thread(fTransfersThread, NULL); |
||||
} |
||||
|
||||
int |
||||
USBDeviceHandle::ClaimInterface(int inumber) |
||||
{ |
||||
int status = fUSBDevice->ClaimInterface(inumber); |
||||
if (status == LIBUSB_SUCCESS) |
||||
fClaimedInterfaces |= (1 << inumber); |
||||
return status; |
||||
} |
||||
|
||||
int |
||||
USBDeviceHandle::ReleaseInterface(int inumber) |
||||
{ |
||||
fUSBDevice->ReleaseInterface(inumber); |
||||
fClaimedInterfaces &= ~(1 << inumber); |
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
int |
||||
USBDeviceHandle::SetConfiguration(int config) |
||||
{ |
||||
int config_index = fUSBDevice->CheckInterfacesFree(config); |
||||
if(config_index == LIBUSB_ERROR_BUSY || config_index == LIBUSB_ERROR_NOT_FOUND) |
||||
return config_index; |
||||
usb_raw_command command; |
||||
command.config.config_index = config_index; |
||||
if (ioctl(fRawFD, B_USB_RAW_COMMAND_SET_CONFIGURATION, &command, sizeof(command)) || |
||||
command.config.status != B_USB_RAW_STATUS_SUCCESS) { |
||||
return _errno_to_libusb(command.config.status); |
||||
} |
||||
fUSBDevice->SetActiveConfiguration(config_index); |
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
int |
||||
USBDeviceHandle::SetAltSetting(int inumber, int alt) |
||||
{ |
||||
usb_raw_command command; |
||||
command.alternate.config_index = fUSBDevice->ActiveConfigurationIndex(); |
||||
command.alternate.interface_index = inumber; |
||||
if (ioctl(fRawFD, B_USB_RAW_COMMAND_GET_ACTIVE_ALT_INTERFACE_INDEX, &command, sizeof(command)) || |
||||
command.alternate.status != B_USB_RAW_STATUS_SUCCESS) { |
||||
usbi_err(NULL, "Error retrieving active alternate interface"); |
||||
return _errno_to_libusb(command.alternate.status); |
||||
} |
||||
if (command.alternate.alternate_info == alt) { |
||||
usbi_dbg("Setting alternate interface successful"); |
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
command.alternate.alternate_info = alt; |
||||
if (ioctl(fRawFD, B_USB_RAW_COMMAND_SET_ALT_INTERFACE, &command, sizeof(command)) || |
||||
command.alternate.status != B_USB_RAW_STATUS_SUCCESS) { //IF IOCTL FAILS DEVICE DISONNECTED PROBABLY
|
||||
usbi_err(NULL, "Error setting alternate interface"); |
||||
return _errno_to_libusb(command.alternate.status); |
||||
} |
||||
usbi_dbg("Setting alternate interface successful"); |
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
|
||||
USBDevice::USBDevice(const char *path) |
||||
: |
||||
fPath(NULL), |
||||
fActiveConfiguration(0), //0?
|
||||
fConfigurationDescriptors(NULL), |
||||
fClaimedInterfaces(0), |
||||
fEndpointToIndex(NULL), |
||||
fEndpointToInterface(NULL), |
||||
fInitCheck(false) |
||||
{ |
||||
fPath=strdup(path); |
||||
Initialise(); |
||||
} |
||||
|
||||
USBDevice::~USBDevice() |
||||
{ |
||||
free(fPath); |
||||
if (fConfigurationDescriptors) { |
||||
for(int i = 0; i < fDeviceDescriptor.num_configurations; i++) { |
||||
if (fConfigurationDescriptors[i]) |
||||
delete fConfigurationDescriptors[i]; |
||||
} |
||||
delete[] fConfigurationDescriptors; |
||||
} |
||||
if (fEndpointToIndex) |
||||
delete[] fEndpointToIndex; |
||||
if (fEndpointToInterface) |
||||
delete[] fEndpointToInterface; |
||||
} |
||||
|
||||
bool |
||||
USBDevice::InitCheck() |
||||
{ |
||||
return fInitCheck; |
||||
} |
||||
|
||||
const char * |
||||
USBDevice::Location() const |
||||
{ |
||||
return fPath; |
||||
} |
||||
|
||||
uint8 |
||||
USBDevice::CountConfigurations() const |
||||
{ |
||||
return fDeviceDescriptor.num_configurations; |
||||
} |
||||
|
||||
const usb_device_descriptor * |
||||
USBDevice::Descriptor() const |
||||
{ |
||||
return &fDeviceDescriptor; |
||||
} |
||||
|
||||
const usb_configuration_descriptor * |
||||
USBDevice::ConfigurationDescriptor(uint32 index) const |
||||
{ |
||||
if (index > CountConfigurations()) |
||||
return NULL; |
||||
return (usb_configuration_descriptor *) fConfigurationDescriptors[index]; |
||||
} |
||||
|
||||
const usb_configuration_descriptor * |
||||
USBDevice::ActiveConfiguration() const |
||||
{ |
||||
return (usb_configuration_descriptor *) fConfigurationDescriptors[fActiveConfiguration]; |
||||
} |
||||
|
||||
int |
||||
USBDevice::ActiveConfigurationIndex() const |
||||
{ |
||||
return fActiveConfiguration; |
||||
} |
||||
|
||||
int USBDevice::ClaimInterface(int interface) |
||||
{ |
||||
if (interface > ActiveConfiguration()->number_interfaces) |
||||
return LIBUSB_ERROR_NOT_FOUND; |
||||
if (fClaimedInterfaces & (1 << interface)) |
||||
return LIBUSB_ERROR_BUSY; |
||||
fClaimedInterfaces |= (1 << interface); |
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
int USBDevice::ReleaseInterface(int interface) |
||||
{ |
||||
fClaimedInterfaces &= ~(1 << interface); |
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
int |
||||
USBDevice::CheckInterfacesFree(int config) |
||||
{ |
||||
if (fConfigToIndex.count(config) == 0) |
||||
return LIBUSB_ERROR_NOT_FOUND; |
||||
if (fClaimedInterfaces == 0) |
||||
return fConfigToIndex[(uint8)config]; |
||||
return LIBUSB_ERROR_BUSY; |
||||
} |
||||
|
||||
int |
||||
USBDevice::SetActiveConfiguration(int config_index) |
||||
{ |
||||
fActiveConfiguration = config_index; |
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
uint8 |
||||
USBDevice::EndpointToIndex(uint8 address) const |
||||
{ |
||||
return fEndpointToIndex[fActiveConfiguration][address]; |
||||
} |
||||
|
||||
uint8 |
||||
USBDevice::EndpointToInterface(uint8 address) const |
||||
{ |
||||
return fEndpointToInterface[fActiveConfiguration][address]; |
||||
} |
||||
|
||||
int |
||||
USBDevice::Initialise() //Do we need more error checking, etc? How to report?
|
||||
{ |
||||
int fRawFD = open(fPath, O_RDWR | O_CLOEXEC); |
||||
if (fRawFD < 0) |
||||
return B_ERROR; |
||||
usb_raw_command command; |
||||
command.device.descriptor = &fDeviceDescriptor; |
||||
if (ioctl(fRawFD, B_USB_RAW_COMMAND_GET_DEVICE_DESCRIPTOR, &command, sizeof(command)) || |
||||
command.device.status != B_USB_RAW_STATUS_SUCCESS) { |
||||
close(fRawFD); |
||||
return B_ERROR; |
||||
} |
||||
|
||||
fConfigurationDescriptors = new(std::nothrow) unsigned char *[fDeviceDescriptor.num_configurations]; |
||||
fEndpointToIndex = new(std::nothrow) map<uint8,uint8> [fDeviceDescriptor.num_configurations]; |
||||
fEndpointToInterface = new(std::nothrow) map<uint8,uint8> [fDeviceDescriptor.num_configurations]; |
||||
for (int i = 0; i < fDeviceDescriptor.num_configurations; i++) { |
||||
usb_configuration_descriptor tmp_config; |
||||
command.config.descriptor = &tmp_config; |
||||
command.config.config_index = i; |
||||
if (ioctl(fRawFD, B_USB_RAW_COMMAND_GET_CONFIGURATION_DESCRIPTOR, &command, sizeof(command)) || |
||||
command.config.status != B_USB_RAW_STATUS_SUCCESS) { |
||||
usbi_err(NULL, "failed retrieving configuration descriptor"); |
||||
close(fRawFD); |
||||
return B_ERROR; |
||||
} |
||||
fConfigToIndex[tmp_config.configuration_value] = i; |
||||
fConfigurationDescriptors[i] = new(std::nothrow) unsigned char[tmp_config.total_length]; |
||||
command.control.request_type = 128; |
||||
command.control.request = 6; |
||||
command.control.value = (2 << 8) | i; |
||||
command.control.index = 0; |
||||
command.control.length = tmp_config.total_length; |
||||
command.control.data = fConfigurationDescriptors[i]; |
||||
if (ioctl(fRawFD, B_USB_RAW_COMMAND_CONTROL_TRANSFER, &command, sizeof(command)) || |
||||
command.control.status!=B_USB_RAW_STATUS_SUCCESS) { |
||||
usbi_err(NULL, "failed retrieving full configuration descriptor"); |
||||
close(fRawFD); |
||||
return B_ERROR; |
||||
} |
||||
for (int j = 0; j < tmp_config.number_interfaces; j++) { |
||||
command.alternate.config_index = i; |
||||
command.alternate.interface_index = j; |
||||
if (ioctl(fRawFD, B_USB_RAW_COMMAND_GET_ALT_INTERFACE_COUNT, &command, sizeof(command)) || |
||||
command.config.status != B_USB_RAW_STATUS_SUCCESS) { |
||||
usbi_err(NULL, "failed retrieving number of alternate interfaces"); |
||||
close(fRawFD); |
||||
return B_ERROR; |
||||
} |
||||
int num_alternate = command.alternate.alternate_info; |
||||
for (int k = 0; k < num_alternate; k++) { |
||||
usb_interface_descriptor tmp_interface; |
||||
command.interface_etc.config_index = i; |
||||
command.interface_etc.interface_index = j; |
||||
command.interface_etc.alternate_index = k; |
||||
command.interface_etc.descriptor = &tmp_interface; |
||||
if (ioctl(fRawFD, B_USB_RAW_COMMAND_GET_INTERFACE_DESCRIPTOR_ETC, &command, sizeof(command)) || |
||||
command.config.status != B_USB_RAW_STATUS_SUCCESS) { |
||||
usbi_err(NULL, "failed retrieving interface descriptor"); |
||||
close(fRawFD); |
||||
return B_ERROR; |
||||
} |
||||
for (int l = 0; l < tmp_interface.num_endpoints; l++) { |
||||
usb_endpoint_descriptor tmp_endpoint; |
||||
command.endpoint_etc.config_index = i; |
||||
command.endpoint_etc.interface_index = j; |
||||
command.endpoint_etc.alternate_index = k; |
||||
command.endpoint_etc.endpoint_index = l; |
||||
command.endpoint_etc.descriptor = &tmp_endpoint; |
||||
if (ioctl(fRawFD, B_USB_RAW_COMMAND_GET_ENDPOINT_DESCRIPTOR_ETC, &command, sizeof(command)) || |
||||
command.config.status != B_USB_RAW_STATUS_SUCCESS) { |
||||
usbi_err(NULL, "failed retrieving endpoint descriptor"); |
||||
close(fRawFD); |
||||
return B_ERROR; |
||||
} |
||||
fEndpointToIndex[i][tmp_endpoint.endpoint_address] = l; |
||||
fEndpointToInterface[i][tmp_endpoint.endpoint_address] = j; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
close(fRawFD); |
||||
fInitCheck = true; |
||||
return B_OK; |
||||
} |
@ -0,0 +1,250 @@ |
||||
/*
|
||||
* Haiku Backend for libusb |
||||
* Copyright © 2014 Akshay Jaggi <akshay1994.leo@gmail.com> |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU Lesser General Public |
||||
* License as published by the Free Software Foundation; either |
||||
* version 2.1 of the License, or (at your option) any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
* Lesser General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Lesser General Public |
||||
* License along with this library; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
*/ |
||||
|
||||
|
||||
#include <unistd.h> |
||||
#include <string.h> |
||||
#include <stdlib.h> |
||||
#include <new> |
||||
#include <vector> |
||||
|
||||
#include "haiku_usb.h" |
||||
|
||||
USBRoster gUsbRoster; |
||||
int32 gInitCount = 0; |
||||
|
||||
static int |
||||
haiku_init(struct libusb_context *ctx) |
||||
{ |
||||
if (atomic_add(&gInitCount, 1) == 0) |
||||
return gUsbRoster.Start(); |
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
static void |
||||
haiku_exit(void) |
||||
{ |
||||
if (atomic_add(&gInitCount, -1) == 1) |
||||
gUsbRoster.Stop(); |
||||
} |
||||
|
||||
static int |
||||
haiku_open(struct libusb_device_handle *dev_handle) |
||||
{ |
||||
USBDevice *dev = *((USBDevice **)dev_handle->dev->os_priv); |
||||
USBDeviceHandle *handle = new(std::nothrow) USBDeviceHandle(dev); |
||||
if (handle == NULL) |
||||
return LIBUSB_ERROR_NO_MEM; |
||||
if (handle->InitCheck() == false) { |
||||
delete handle; |
||||
return LIBUSB_ERROR_NO_DEVICE; |
||||
} |
||||
*((USBDeviceHandle **)dev_handle->os_priv) = handle; |
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
static void |
||||
haiku_close(struct libusb_device_handle *dev_handle) |
||||
{ |
||||
USBDeviceHandle *handle = *((USBDeviceHandle **)dev_handle->os_priv); |
||||
if (handle == NULL) |
||||
return; |
||||
delete handle; |
||||
*((USBDeviceHandle **)dev_handle->os_priv) = NULL; |
||||
} |
||||
|
||||
static int |
||||
haiku_get_device_descriptor(struct libusb_device *device, unsigned char *buffer, int *host_endian) |
||||
{ |
||||
USBDevice *dev = *((USBDevice **)device->os_priv); |
||||
memcpy(buffer, dev->Descriptor(), DEVICE_DESC_LENGTH); |
||||
*host_endian = 0; |
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
static int |
||||
haiku_get_active_config_descriptor(struct libusb_device *device, unsigned char *buffer, size_t len, int *host_endian) |
||||
{ |
||||
USBDevice *dev = *((USBDevice **)device->os_priv); |
||||
const usb_configuration_descriptor *act_config = dev->ActiveConfiguration(); |
||||
if (len > act_config->total_length) |
||||
return LIBUSB_ERROR_OVERFLOW; |
||||
memcpy(buffer, act_config, len); |
||||
*host_endian = 0; |
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
static int |
||||
haiku_get_config_descriptor(struct libusb_device *device, uint8_t config_index, unsigned char *buffer, size_t len, int *host_endian) |
||||
{ |
||||
USBDevice *dev = *((USBDevice **)device->os_priv); |
||||
const usb_configuration_descriptor *config = dev->ConfigurationDescriptor(config_index); |
||||
if (config == NULL) { |
||||
usbi_err(DEVICE_CTX(device), "failed getting configuration descriptor"); |
||||
return LIBUSB_ERROR_INVALID_PARAM; |
||||
} |
||||
if (len > config->total_length) |
||||
len = config->total_length; |
||||
memcpy(buffer, config, len); |
||||
*host_endian = 0; |
||||
return len; |
||||
} |
||||
|
||||
static int |
||||
haiku_set_configuration(struct libusb_device_handle *dev_handle, int config) |
||||
{ |
||||
USBDeviceHandle *handle= *((USBDeviceHandle **)dev_handle->os_priv); |
||||
return handle->SetConfiguration(config); |
||||
} |
||||
|
||||
static int |
||||
haiku_claim_interface(struct libusb_device_handle *dev_handle, int interface_number) |
||||
{ |
||||
USBDeviceHandle *handle = *((USBDeviceHandle **)dev_handle->os_priv); |
||||
return handle->ClaimInterface(interface_number); |
||||
} |
||||
|
||||
static int |
||||
haiku_set_altsetting(struct libusb_device_handle *dev_handle, int interface_number, int altsetting) |
||||
{ |
||||
USBDeviceHandle *handle = *((USBDeviceHandle **)dev_handle->os_priv); |
||||
return handle->SetAltSetting(interface_number, altsetting); |
||||
} |
||||
|
||||
static int |
||||
haiku_release_interface(struct libusb_device_handle *dev_handle, int interface_number) |
||||
{ |
||||
USBDeviceHandle *handle = *((USBDeviceHandle **)dev_handle->os_priv); |
||||
haiku_set_altsetting(dev_handle,interface_number, 0); |
||||
return handle->ReleaseInterface(interface_number); |
||||
} |
||||
|
||||
static int |
||||
haiku_submit_transfer(struct usbi_transfer *itransfer) |
||||
{ |
||||
struct libusb_transfer *fLibusbTransfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); |
||||
USBDeviceHandle *fDeviceHandle = *((USBDeviceHandle **)fLibusbTransfer->dev_handle->os_priv); |
||||
return fDeviceHandle->SubmitTransfer(itransfer); |
||||
} |
||||
|
||||
static int |
||||
haiku_cancel_transfer(struct usbi_transfer *itransfer) |
||||
{ |
||||
struct libusb_transfer *fLibusbTransfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); |
||||
USBDeviceHandle *fDeviceHandle = *((USBDeviceHandle **)fLibusbTransfer->dev_handle->os_priv); |
||||
return fDeviceHandle->CancelTransfer(*((USBTransfer **)usbi_transfer_get_os_priv(itransfer))); |
||||
} |
||||
|
||||
static void |
||||
haiku_clear_transfer_priv(struct usbi_transfer *itransfer) |
||||
{ |
||||
USBTransfer *transfer = *((USBTransfer **)usbi_transfer_get_os_priv(itransfer)); |
||||
delete transfer; |
||||
*((USBTransfer **)usbi_transfer_get_os_priv(itransfer)) = NULL; |
||||
} |
||||
|
||||
static int |
||||
haiku_handle_transfer_completion(struct usbi_transfer *itransfer) |
||||
{ |
||||
USBTransfer *transfer = *((USBTransfer **)usbi_transfer_get_os_priv(itransfer)); |
||||
|
||||
usbi_mutex_lock(&itransfer->lock); |
||||
if (transfer->IsCancelled()) { |
||||
delete transfer; |
||||
*((USBTransfer **)usbi_transfer_get_os_priv(itransfer)) = NULL; |
||||
usbi_mutex_unlock(&itransfer->lock); |
||||
if (itransfer->transferred < 0) |
||||
itransfer->transferred = 0; |
||||
return usbi_handle_transfer_cancellation(itransfer); |
||||
} |
||||
libusb_transfer_status status = LIBUSB_TRANSFER_COMPLETED; |
||||
if (itransfer->transferred < 0) { |
||||
usbi_err(ITRANSFER_CTX(itransfer), "error in transfer"); |
||||
status = LIBUSB_TRANSFER_ERROR; |
||||
itransfer->transferred = 0; |
||||
} |
||||
delete transfer; |
||||
*((USBTransfer **)usbi_transfer_get_os_priv(itransfer)) = NULL; |
||||
usbi_mutex_unlock(&itransfer->lock); |
||||
return usbi_handle_transfer_completion(itransfer, status); |
||||
} |
||||
|
||||
static int |
||||
haiku_clock_gettime(int clkid, struct timespec *tp) |
||||
{ |
||||
if (clkid == USBI_CLOCK_REALTIME) |
||||
return clock_gettime(CLOCK_REALTIME, tp); |
||||
if (clkid == USBI_CLOCK_MONOTONIC) |
||||
return clock_gettime(CLOCK_MONOTONIC, tp); |
||||
return LIBUSB_ERROR_INVALID_PARAM; |
||||
} |
||||
|
||||
const struct usbi_os_backend haiku_usb_raw_backend = { |
||||
/*.name =*/ "Haiku usbfs", |
||||
/*.caps =*/ 0, |
||||
/*.init =*/ haiku_init, |
||||
/*.exit =*/ haiku_exit, |
||||
/*.get_device_list =*/ NULL, |
||||
/*.hotplug_poll =*/ NULL, |
||||
/*.open =*/ haiku_open, |
||||
/*.close =*/ haiku_close, |
||||
/*.get_device_descriptor =*/ haiku_get_device_descriptor, |
||||
/*.get_active_config_descriptor =*/ haiku_get_active_config_descriptor, |
||||
/*.get_config_descriptor =*/ haiku_get_config_descriptor, |
||||
/*.get_config_descriptor_by_value =*/ NULL, |
||||
|
||||
|
||||
/*.get_configuration =*/ NULL, |
||||
/*.set_configuration =*/ haiku_set_configuration, |
||||
/*.claim_interface =*/ haiku_claim_interface, |
||||
/*.release_interface =*/ haiku_release_interface, |
||||
|
||||
/*.set_interface_altsetting =*/ haiku_set_altsetting, |
||||
/*.clear_halt =*/ NULL, |
||||
/*.reset_device =*/ NULL, |
||||
|
||||
/*.alloc_streams =*/ NULL, |
||||
/*.free_streams =*/ NULL, |
||||
|
||||
/*.dev_mem_alloc =*/ NULL, |
||||
/*.dev_mem_free =*/ NULL, |
||||
|
||||
/*.kernel_driver_active =*/ NULL, |
||||
/*.detach_kernel_driver =*/ NULL, |
||||
/*.attach_kernel_driver =*/ NULL, |
||||
|
||||
/*.destroy_device =*/ NULL, |
||||
|
||||
/*.submit_transfer =*/ haiku_submit_transfer, |
||||
/*.cancel_transfer =*/ haiku_cancel_transfer, |
||||
/*.clear_transfer_priv =*/ haiku_clear_transfer_priv, |
||||
|
||||
/*.handle_events =*/ NULL, |
||||
/*.handle_transfer_completion =*/ haiku_handle_transfer_completion, |
||||
|
||||
/*.clock_gettime =*/ haiku_clock_gettime, |
||||
|
||||
#ifdef USBI_TIMERFD_AVAILABLE |
||||
/*.get_timerfd_clockid =*/ NULL, |
||||
#endif |
||||
|
||||
/*.device_priv_size =*/ sizeof(USBDevice *), |
||||
/*.device_handle_priv_size =*/ sizeof(USBDeviceHandle *), |
||||
/*.transfer_priv_size =*/ sizeof(USBTransfer *), |
||||
}; |
@ -0,0 +1,180 @@ |
||||
/*
|
||||
* Copyright 2006-2008, Haiku Inc. All rights reserved. |
||||
* Distributed under the terms of the MIT License. |
||||
*/ |
||||
|
||||
#ifndef _USB_RAW_H_ |
||||
#define _USB_RAW_H_ |
||||
|
||||
#include <USB3.h> |
||||
|
||||
#define B_USB_RAW_PROTOCOL_VERSION 0x0015 |
||||
#define B_USB_RAW_ACTIVE_ALTERNATE 0xffffffff |
||||
|
||||
typedef enum { |
||||
B_USB_RAW_COMMAND_GET_VERSION = 0x1000, |
||||
|
||||
B_USB_RAW_COMMAND_GET_DEVICE_DESCRIPTOR = 0x2000, |
||||
B_USB_RAW_COMMAND_GET_CONFIGURATION_DESCRIPTOR, |
||||
B_USB_RAW_COMMAND_GET_INTERFACE_DESCRIPTOR, |
||||
B_USB_RAW_COMMAND_GET_ENDPOINT_DESCRIPTOR, |
||||
B_USB_RAW_COMMAND_GET_STRING_DESCRIPTOR, |
||||
B_USB_RAW_COMMAND_GET_GENERIC_DESCRIPTOR, |
||||
B_USB_RAW_COMMAND_GET_ALT_INTERFACE_COUNT, |
||||
B_USB_RAW_COMMAND_GET_ACTIVE_ALT_INTERFACE_INDEX, |
||||
B_USB_RAW_COMMAND_GET_INTERFACE_DESCRIPTOR_ETC, |
||||
B_USB_RAW_COMMAND_GET_ENDPOINT_DESCRIPTOR_ETC, |
||||
B_USB_RAW_COMMAND_GET_GENERIC_DESCRIPTOR_ETC, |
||||
|
||||
B_USB_RAW_COMMAND_SET_CONFIGURATION = 0x3000, |
||||
B_USB_RAW_COMMAND_SET_FEATURE, |
||||
B_USB_RAW_COMMAND_CLEAR_FEATURE, |
||||
B_USB_RAW_COMMAND_GET_STATUS, |
||||
B_USB_RAW_COMMAND_GET_DESCRIPTOR, |
||||
B_USB_RAW_COMMAND_SET_ALT_INTERFACE, |
||||
|
||||
B_USB_RAW_COMMAND_CONTROL_TRANSFER = 0x4000, |
||||
B_USB_RAW_COMMAND_INTERRUPT_TRANSFER, |
||||
B_USB_RAW_COMMAND_BULK_TRANSFER, |
||||
B_USB_RAW_COMMAND_ISOCHRONOUS_TRANSFER |
||||
} usb_raw_command_id; |
||||
|
||||
|
||||
typedef enum { |
||||
B_USB_RAW_STATUS_SUCCESS = 0, |
||||
|
||||
B_USB_RAW_STATUS_FAILED, |
||||
B_USB_RAW_STATUS_ABORTED, |
||||
B_USB_RAW_STATUS_STALLED, |
||||
B_USB_RAW_STATUS_CRC_ERROR, |
||||
B_USB_RAW_STATUS_TIMEOUT, |
||||
|
||||
B_USB_RAW_STATUS_INVALID_CONFIGURATION, |
||||
B_USB_RAW_STATUS_INVALID_INTERFACE, |
||||
B_USB_RAW_STATUS_INVALID_ENDPOINT, |
||||
B_USB_RAW_STATUS_INVALID_STRING, |
||||
|
||||
B_USB_RAW_STATUS_NO_MEMORY |
||||
} usb_raw_command_status; |
||||
|
||||
|
||||
typedef union { |
||||
struct { |
||||
status_t status; |
||||
} version; |
||||
|
||||
struct { |
||||
status_t status; |
||||
usb_device_descriptor *descriptor; |
||||
} device; |
||||
|
||||
struct { |
||||
status_t status; |
||||
usb_configuration_descriptor *descriptor; |
||||
uint32 config_index; |
||||
} config; |
||||
|
||||
struct { |
||||
status_t status; |
||||
uint32 alternate_info; |
||||
uint32 config_index; |
||||
uint32 interface_index; |
||||
} alternate; |
||||
|
||||
struct { |
||||
status_t status; |
||||
usb_interface_descriptor *descriptor; |
||||
uint32 config_index; |
||||
uint32 interface_index; |
||||
} interface; |
||||
|
||||
struct { |
||||
status_t status; |
||||
usb_interface_descriptor *descriptor; |
||||
uint32 config_index; |
||||
uint32 interface_index; |
||||
uint32 alternate_index; |
||||
} interface_etc; |
||||
|
||||
struct { |
||||
status_t status; |
||||
usb_endpoint_descriptor *descriptor; |
||||
uint32 config_index; |
||||
uint32 interface_index; |
||||
uint32 endpoint_index; |
||||
} endpoint; |
||||
|
||||
struct { |
||||
status_t status; |
||||
usb_endpoint_descriptor *descriptor; |
||||
uint32 config_index; |
||||
uint32 interface_index; |
||||
uint32 alternate_index; |
||||
uint32 endpoint_index; |
||||
} endpoint_etc; |
||||
|
||||
struct { |
||||
status_t status; |
||||
usb_descriptor *descriptor; |
||||
uint32 config_index; |
||||
uint32 interface_index; |
||||
uint32 generic_index; |
||||
size_t length; |
||||
} generic; |
||||
|
||||
struct { |
||||
status_t status; |
||||
usb_descriptor *descriptor; |
||||
uint32 config_index; |
||||
uint32 interface_index; |
||||
uint32 alternate_index; |
||||
uint32 generic_index; |
||||
size_t length; |
||||
} generic_etc; |
||||
|
||||
struct { |
||||
status_t status; |
||||
usb_string_descriptor *descriptor; |
||||
uint32 string_index; |
||||
size_t length; |
||||
} string; |
||||
|
||||
struct { |
||||
status_t status; |
||||
uint8 type; |
||||
uint8 index; |
||||
uint16 language_id; |
||||
void *data; |
||||
size_t length; |
||||
} descriptor; |
||||
|
||||
struct { |
||||
status_t status; |
||||
uint8 request_type; |
||||
uint8 request; |
||||
uint16 value; |
||||
uint16 index; |
||||
uint16 length; |
||||
void *data; |
||||
} control; |
||||
|
||||
struct { |
||||
status_t status; |
||||
uint32 interface; |
||||
uint32 endpoint; |
||||
void *data; |
||||
size_t length; |
||||
} transfer; |
||||
|
||||
struct { |
||||
status_t status; |
||||
uint32 interface; |
||||
uint32 endpoint; |
||||
void *data; |
||||
size_t length; |
||||
usb_iso_packet_descriptor *packet_descriptors; |
||||
uint32 packet_count; |
||||
} isochronous; |
||||
} usb_raw_command; |
||||
|
||||
#endif // _USB_RAW_H_
|
@ -0,0 +1,400 @@ |
||||
/* -*- Mode: C; c-basic-offset:8 ; indent-tabs-mode:t -*- */ |
||||
/*
|
||||
* Linux usbfs backend for libusb |
||||
* Copyright (C) 2007-2009 Daniel Drake <dsd@gentoo.org> |
||||
* Copyright (c) 2001 Johannes Erdfelt <johannes@erdfelt.com> |
||||
* Copyright (c) 2013 Nathan Hjelm <hjelmn@mac.com> |
||||
* Copyright (c) 2016 Chris Dickens <christopher.a.dickens@gmail.com> |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU Lesser General Public |
||||
* License as published by the Free Software Foundation; either |
||||
* version 2.1 of the License, or (at your option) any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
* Lesser General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Lesser General Public |
||||
* License along with this library; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
*/ |
||||
|
||||
#include <config.h> |
||||
|
||||
#include <assert.h> |
||||
#include <errno.h> |
||||
#include <fcntl.h> |
||||
#include <poll.h> |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <unistd.h> |
||||
#include <sys/types.h> |
||||
|
||||
#ifdef HAVE_ASM_TYPES_H |
||||
#include <asm/types.h> |
||||
#endif |
||||
|
||||
#include <sys/socket.h> |
||||
#include <linux/netlink.h> |
||||
|
||||
#include "libusbi.h" |
||||
#include "linux_usbfs.h" |
||||
|
||||
#define NL_GROUP_KERNEL 1 |
||||
|
||||
static int linux_netlink_socket = -1; |
||||
static int netlink_control_pipe[2] = { -1, -1 }; |
||||
static pthread_t libusb_linux_event_thread; |
||||
|
||||
static void *linux_netlink_event_thread_main(void *arg); |
||||
|
||||
static int set_fd_cloexec_nb(int fd) |
||||
{ |
||||
int flags; |
||||
|
||||
#if defined(FD_CLOEXEC) |
||||
flags = fcntl(fd, F_GETFD); |
||||
if (flags == -1) { |
||||
usbi_err(NULL, "failed to get netlink fd flags (%d)", errno); |
||||
return -1; |
||||
} |
||||
|
||||
if (!(flags & FD_CLOEXEC)) { |
||||
if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) { |
||||
usbi_err(NULL, "failed to set netlink fd flags (%d)", errno); |
||||
return -1; |
||||
} |
||||
} |
||||
#endif |
||||
|
||||
flags = fcntl(fd, F_GETFL); |
||||
if (flags == -1) { |
||||
usbi_err(NULL, "failed to get netlink fd status flags (%d)", errno); |
||||
return -1; |
||||
} |
||||
|
||||
if (!(flags & O_NONBLOCK)) { |
||||
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { |
||||
usbi_err(NULL, "failed to set netlink fd status flags (%d)", errno); |
||||
return -1; |
||||
} |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int linux_netlink_start_event_monitor(void) |
||||
{ |
||||
struct sockaddr_nl sa_nl = { .nl_family = AF_NETLINK, .nl_groups = NL_GROUP_KERNEL }; |
||||
int socktype = SOCK_RAW; |
||||
int opt = 1; |
||||
int ret; |
||||
|
||||
#if defined(SOCK_CLOEXEC) |
||||
socktype |= SOCK_CLOEXEC; |
||||
#endif |
||||
#if defined(SOCK_NONBLOCK) |
||||
socktype |= SOCK_NONBLOCK; |
||||
#endif |
||||
|
||||
linux_netlink_socket = socket(PF_NETLINK, socktype, NETLINK_KOBJECT_UEVENT); |
||||
if (linux_netlink_socket == -1 && errno == EINVAL) { |
||||
usbi_dbg("failed to create netlink socket of type %d, attempting SOCK_RAW", socktype); |
||||
linux_netlink_socket = socket(PF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT); |
||||
} |
||||
|
||||
if (linux_netlink_socket == -1) { |
||||
usbi_err(NULL, "failed to create netlink socket (%d)", errno); |
||||
goto err; |
||||
} |
||||
|
||||
ret = set_fd_cloexec_nb(linux_netlink_socket); |
||||
if (ret == -1) |
||||
goto err_close_socket; |
||||
|
||||
ret = bind(linux_netlink_socket, (struct sockaddr *)&sa_nl, sizeof(sa_nl)); |
||||
if (ret == -1) { |
||||
usbi_err(NULL, "failed to bind netlink socket (%d)", errno); |
||||
goto err_close_socket; |
||||
} |
||||
|
||||
ret = setsockopt(linux_netlink_socket, SOL_SOCKET, SO_PASSCRED, &opt, sizeof(opt)); |
||||
if (ret == -1) { |
||||
usbi_err(NULL, "failed to set netlink socket SO_PASSCRED option (%d)", errno); |
||||
goto err_close_socket; |
||||
} |
||||
|
||||
ret = usbi_pipe(netlink_control_pipe); |
||||
if (ret) { |
||||
usbi_err(NULL, "failed to create netlink control pipe"); |
||||
goto err_close_socket; |
||||
} |
||||
|
||||
ret = pthread_create(&libusb_linux_event_thread, NULL, linux_netlink_event_thread_main, NULL); |
||||
if (ret != 0) { |
||||
usbi_err(NULL, "failed to create netlink event thread (%d)", ret); |
||||
goto err_close_pipe; |
||||
} |
||||
|
||||
return LIBUSB_SUCCESS; |
||||
|
||||
err_close_pipe: |
||||
close(netlink_control_pipe[0]); |
||||
close(netlink_control_pipe[1]); |
||||
netlink_control_pipe[0] = -1; |
||||
netlink_control_pipe[1] = -1; |
||||
err_close_socket: |
||||
close(linux_netlink_socket); |
||||
linux_netlink_socket = -1; |
||||
err: |
||||
return LIBUSB_ERROR_OTHER; |
||||
} |
||||
|
||||
int linux_netlink_stop_event_monitor(void) |
||||
{ |
||||
char dummy = 1; |
||||
ssize_t r; |
||||
|
||||
assert(linux_netlink_socket != -1); |
||||
|
||||
/* Write some dummy data to the control pipe and
|
||||
* wait for the thread to exit */ |
||||
r = usbi_write(netlink_control_pipe[1], &dummy, sizeof(dummy)); |
||||
if (r <= 0) |
||||
usbi_warn(NULL, "netlink control pipe signal failed"); |
||||
|
||||
pthread_join(libusb_linux_event_thread, NULL); |
||||
|
||||
close(linux_netlink_socket); |
||||
linux_netlink_socket = -1; |
||||
|
||||
/* close and reset control pipe */ |
||||
close(netlink_control_pipe[0]); |
||||
close(netlink_control_pipe[1]); |
||||
netlink_control_pipe[0] = -1; |
||||
netlink_control_pipe[1] = -1; |
||||
|
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
static const char *netlink_message_parse(const char *buffer, size_t len, const char *key) |
||||
{ |
||||
const char *end = buffer + len; |
||||
size_t keylen = strlen(key); |
||||
|
||||
while (buffer < end && *buffer) { |
||||
if (strncmp(buffer, key, keylen) == 0 && buffer[keylen] == '=') |
||||
return buffer + keylen + 1; |
||||
buffer += strlen(buffer) + 1; |
||||
} |
||||
|
||||
return NULL; |
||||
} |
||||
|
||||
/* parse parts of netlink message common to both libudev and the kernel */ |
||||
static int linux_netlink_parse(const char *buffer, size_t len, int *detached, |
||||
const char **sys_name, uint8_t *busnum, uint8_t *devaddr) |
||||
{ |
||||
const char *tmp, *slash; |
||||
|
||||
errno = 0; |
||||
|
||||
*sys_name = NULL; |
||||
*detached = 0; |
||||
*busnum = 0; |
||||
*devaddr = 0; |
||||
|
||||
tmp = netlink_message_parse(buffer, len, "ACTION"); |
||||
if (!tmp) { |
||||
return -1; |
||||
} else if (strcmp(tmp, "remove") == 0) { |
||||
*detached = 1; |
||||
} else if (strcmp(tmp, "add") != 0) { |
||||
usbi_dbg("unknown device action %s", tmp); |
||||
return -1; |
||||
} |
||||
|
||||
/* check that this is a usb message */ |
||||
tmp = netlink_message_parse(buffer, len, "SUBSYSTEM"); |
||||
if (!tmp || strcmp(tmp, "usb") != 0) { |
||||
/* not usb. ignore */ |
||||
return -1; |
||||
} |
||||
|
||||
/* check that this is an actual usb device */ |
||||
tmp = netlink_message_parse(buffer, len, "DEVTYPE"); |
||||
if (!tmp || strcmp(tmp, "usb_device") != 0) { |
||||
/* not usb. ignore */ |
||||
return -1; |
||||
} |
||||
|
||||
tmp = netlink_message_parse(buffer, len, "BUSNUM"); |
||||
if (tmp) { |
||||
*busnum = (uint8_t)(strtoul(tmp, NULL, 10) & 0xff); |
||||
if (errno) { |
||||
errno = 0; |
||||
return -1; |
||||
} |
||||
|
||||
tmp = netlink_message_parse(buffer, len, "DEVNUM"); |
||||
if (NULL == tmp) |
||||
return -1; |
||||
|
||||
*devaddr = (uint8_t)(strtoul(tmp, NULL, 10) & 0xff); |
||||
if (errno) { |
||||
errno = 0; |
||||
return -1; |
||||
} |
||||
} else { |
||||
/* no bus number. try "DEVICE" */ |
||||
tmp = netlink_message_parse(buffer, len, "DEVICE"); |
||||
if (!tmp) { |
||||
/* not usb. ignore */ |
||||
return -1; |
||||
} |
||||
|
||||
/* Parse a device path such as /dev/bus/usb/003/004 */ |
||||
slash = strrchr(tmp, '/'); |
||||
if (!slash) |
||||
return -1; |
||||
|
||||
*busnum = (uint8_t)(strtoul(slash - 3, NULL, 10) & 0xff); |
||||
if (errno) { |
||||
errno = 0; |
||||
return -1; |
||||
} |
||||
|
||||
*devaddr = (uint8_t)(strtoul(slash + 1, NULL, 10) & 0xff); |
||||
if (errno) { |
||||
errno = 0; |
||||
return -1; |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
tmp = netlink_message_parse(buffer, len, "DEVPATH"); |
||||
if (!tmp) |
||||
return -1; |
||||
|
||||
slash = strrchr(tmp, '/'); |
||||
if (slash) |
||||
*sys_name = slash + 1; |
||||
|
||||
/* found a usb device */ |
||||
return 0; |
||||
} |
||||
|
||||
static int linux_netlink_read_message(void) |
||||
{ |
||||
char cred_buffer[CMSG_SPACE(sizeof(struct ucred))]; |
||||
char msg_buffer[2048]; |
||||
const char *sys_name = NULL; |
||||
uint8_t busnum, devaddr; |
||||
int detached, r; |
||||
ssize_t len; |
||||
struct cmsghdr *cmsg; |
||||
struct ucred *cred; |
||||
struct sockaddr_nl sa_nl; |
||||
struct iovec iov = { .iov_base = msg_buffer, .iov_len = sizeof(msg_buffer) }; |
||||
struct msghdr msg = { |
||||
.msg_iov = &iov, .msg_iovlen = 1, |
||||
.msg_control = cred_buffer, .msg_controllen = sizeof(cred_buffer), |
||||
.msg_name = &sa_nl, .msg_namelen = sizeof(sa_nl) |
||||
}; |
||||
|
||||
/* read netlink message */ |
||||
len = recvmsg(linux_netlink_socket, &msg, 0); |
||||
if (len == -1) { |
||||
if (errno != EAGAIN && errno != EINTR) |
||||
usbi_err(NULL, "error receiving message from netlink (%d)", errno); |
||||
return -1; |
||||
} |
||||
|
||||
if (len < 32 || (msg.msg_flags & MSG_TRUNC)) { |
||||
usbi_err(NULL, "invalid netlink message length"); |
||||
return -1; |
||||
} |
||||
|
||||
if (sa_nl.nl_groups != NL_GROUP_KERNEL || sa_nl.nl_pid != 0) { |
||||
usbi_dbg("ignoring netlink message from unknown group/PID (%u/%u)", |
||||
(unsigned int)sa_nl.nl_groups, (unsigned int)sa_nl.nl_pid); |
||||
return -1; |
||||
} |
||||
|
||||
cmsg = CMSG_FIRSTHDR(&msg); |
||||
if (!cmsg || cmsg->cmsg_type != SCM_CREDENTIALS) { |
||||
usbi_dbg("ignoring netlink message with no sender credentials"); |
||||
return -1; |
||||
} |
||||
|
||||
cred = (struct ucred *)CMSG_DATA(cmsg); |
||||
if (cred->uid != 0) { |
||||
usbi_dbg("ignoring netlink message with non-zero sender UID %u", (unsigned int)cred->uid); |
||||
return -1; |
||||
} |
||||
|
||||
r = linux_netlink_parse(msg_buffer, (size_t)len, &detached, &sys_name, &busnum, &devaddr); |
||||
if (r) |
||||
return r; |
||||
|
||||
usbi_dbg("netlink hotplug found device busnum: %hhu, devaddr: %hhu, sys_name: %s, removed: %s", |
||||
busnum, devaddr, sys_name, detached ? "yes" : "no"); |
||||
|
||||
/* signal device is available (or not) to all contexts */ |
||||
if (detached) |
||||
linux_device_disconnected(busnum, devaddr); |
||||
else |
||||
linux_hotplug_enumerate(busnum, devaddr, sys_name); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static void *linux_netlink_event_thread_main(void *arg) |
||||
{ |
||||
char dummy; |
||||
ssize_t r; |
||||
struct pollfd fds[] = { |
||||
{ .fd = netlink_control_pipe[0], |
||||
.events = POLLIN }, |
||||
{ .fd = linux_netlink_socket, |
||||
.events = POLLIN }, |
||||
}; |
||||
|
||||
UNUSED(arg); |
||||
|
||||
usbi_dbg("netlink event thread entering"); |
||||
|
||||
while (poll(fds, 2, -1) >= 0) { |
||||
if (fds[0].revents & POLLIN) { |
||||
/* activity on control pipe, read the byte and exit */ |
||||
r = usbi_read(netlink_control_pipe[0], &dummy, sizeof(dummy)); |
||||
if (r <= 0) |
||||
usbi_warn(NULL, "netlink control pipe read failed"); |
||||
break; |
||||
} |
||||
if (fds[1].revents & POLLIN) { |
||||
usbi_mutex_static_lock(&linux_hotplug_lock); |
||||
linux_netlink_read_message(); |
||||
usbi_mutex_static_unlock(&linux_hotplug_lock); |
||||
} |
||||
} |
||||
|
||||
usbi_dbg("netlink event thread exiting"); |
||||
|
||||
return NULL; |
||||
} |
||||
|
||||
void linux_netlink_hotplug_poll(void) |
||||
{ |
||||
int r; |
||||
|
||||
usbi_mutex_static_lock(&linux_hotplug_lock); |
||||
do { |
||||
r = linux_netlink_read_message(); |
||||
} while (r == 0); |
||||
usbi_mutex_static_unlock(&linux_hotplug_lock); |
||||
} |
@ -0,0 +1,311 @@ |
||||
/* -*- Mode: C; c-basic-offset:8 ; indent-tabs-mode:t -*- */ |
||||
/*
|
||||
* Linux usbfs backend for libusb |
||||
* Copyright (C) 2007-2009 Daniel Drake <dsd@gentoo.org> |
||||
* Copyright (c) 2001 Johannes Erdfelt <johannes@erdfelt.com> |
||||
* Copyright (c) 2012-2013 Nathan Hjelm <hjelmn@mac.com> |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU Lesser General Public |
||||
* License as published by the Free Software Foundation; either |
||||
* version 2.1 of the License, or (at your option) any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
* Lesser General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Lesser General Public |
||||
* License along with this library; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
*/ |
||||
|
||||
#include <config.h> |
||||
|
||||
#include <assert.h> |
||||
#include <ctype.h> |
||||
#include <dirent.h> |
||||
#include <errno.h> |
||||
#include <fcntl.h> |
||||
#include <poll.h> |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <sys/ioctl.h> |
||||
#include <sys/stat.h> |
||||
#include <sys/types.h> |
||||
#include <sys/utsname.h> |
||||
#include <sys/socket.h> |
||||
#include <unistd.h> |
||||
#include <libudev.h> |
||||
|
||||
#include "libusbi.h" |
||||
#include "linux_usbfs.h" |
||||
|
||||
/* udev context */ |
||||
static struct udev *udev_ctx = NULL; |
||||
static int udev_monitor_fd = -1; |
||||
static int udev_control_pipe[2] = {-1, -1}; |
||||
static struct udev_monitor *udev_monitor = NULL; |
||||
static pthread_t linux_event_thread; |
||||
|
||||
static void udev_hotplug_event(struct udev_device* udev_dev); |
||||
static void *linux_udev_event_thread_main(void *arg); |
||||
|
||||
int linux_udev_start_event_monitor(void) |
||||
{ |
||||
int r; |
||||
|
||||
assert(udev_ctx == NULL); |
||||
udev_ctx = udev_new(); |
||||
if (!udev_ctx) { |
||||
usbi_err(NULL, "could not create udev context"); |
||||
goto err; |
||||
} |
||||
|
||||
udev_monitor = udev_monitor_new_from_netlink(udev_ctx, "udev"); |
||||
if (!udev_monitor) { |
||||
usbi_err(NULL, "could not initialize udev monitor"); |
||||
goto err_free_ctx; |
||||
} |
||||
|
||||
r = udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "usb", "usb_device"); |
||||
if (r) { |
||||
usbi_err(NULL, "could not initialize udev monitor filter for \"usb\" subsystem"); |
||||
goto err_free_monitor; |
||||
} |
||||
|
||||
if (udev_monitor_enable_receiving(udev_monitor)) { |
||||
usbi_err(NULL, "failed to enable the udev monitor"); |
||||
goto err_free_monitor; |
||||
} |
||||
|
||||
udev_monitor_fd = udev_monitor_get_fd(udev_monitor); |
||||
|
||||
/* Some older versions of udev are not non-blocking by default,
|
||||
* so make sure this is set */ |
||||
r = fcntl(udev_monitor_fd, F_GETFL); |
||||
if (r == -1) { |
||||
usbi_err(NULL, "getting udev monitor fd flags (%d)", errno); |
||||
goto err_free_monitor; |
||||
} |
||||
r = fcntl(udev_monitor_fd, F_SETFL, r | O_NONBLOCK); |
||||
if (r) { |
||||
usbi_err(NULL, "setting udev monitor fd flags (%d)", errno); |
||||
goto err_free_monitor; |
||||
} |
||||
|
||||
r = usbi_pipe(udev_control_pipe); |
||||
if (r) { |
||||
usbi_err(NULL, "could not create udev control pipe"); |
||||
goto err_free_monitor; |
||||
} |
||||
|
||||
r = pthread_create(&linux_event_thread, NULL, linux_udev_event_thread_main, NULL); |
||||
if (r) { |
||||
usbi_err(NULL, "creating hotplug event thread (%d)", r); |
||||
goto err_close_pipe; |
||||
} |
||||
|
||||
return LIBUSB_SUCCESS; |
||||
|
||||
err_close_pipe: |
||||
close(udev_control_pipe[0]); |
||||
close(udev_control_pipe[1]); |
||||
err_free_monitor: |
||||
udev_monitor_unref(udev_monitor); |
||||
udev_monitor = NULL; |
||||
udev_monitor_fd = -1; |
||||
err_free_ctx: |
||||
udev_unref(udev_ctx); |
||||
err: |
||||
udev_ctx = NULL; |
||||
return LIBUSB_ERROR_OTHER; |
||||
} |
||||
|
||||
int linux_udev_stop_event_monitor(void) |
||||
{ |
||||
char dummy = 1; |
||||
int r; |
||||
|
||||
assert(udev_ctx != NULL); |
||||
assert(udev_monitor != NULL); |
||||
assert(udev_monitor_fd != -1); |
||||
|
||||
/* Write some dummy data to the control pipe and
|
||||
* wait for the thread to exit */ |
||||
r = usbi_write(udev_control_pipe[1], &dummy, sizeof(dummy)); |
||||
if (r <= 0) { |
||||
usbi_warn(NULL, "udev control pipe signal failed"); |
||||
} |
||||
pthread_join(linux_event_thread, NULL); |
||||
|
||||
/* Release the udev monitor */ |
||||
udev_monitor_unref(udev_monitor); |
||||
udev_monitor = NULL; |
||||
udev_monitor_fd = -1; |
||||
|
||||
/* Clean up the udev context */ |
||||
udev_unref(udev_ctx); |
||||
udev_ctx = NULL; |
||||
|
||||
/* close and reset control pipe */ |
||||
close(udev_control_pipe[0]); |
||||
close(udev_control_pipe[1]); |
||||
udev_control_pipe[0] = -1; |
||||
udev_control_pipe[1] = -1; |
||||
|
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
static void *linux_udev_event_thread_main(void *arg) |
||||
{ |
||||
char dummy; |
||||
int r; |
||||
struct udev_device* udev_dev; |
||||
struct pollfd fds[] = { |
||||
{.fd = udev_control_pipe[0], |
||||
.events = POLLIN}, |
||||
{.fd = udev_monitor_fd, |
||||
.events = POLLIN}, |
||||
}; |
||||
|
||||
usbi_dbg("udev event thread entering."); |
||||
|
||||
while ((r = poll(fds, 2, -1)) >= 0 || errno == EINTR) { |
||||
if (r < 0) { |
||||
/* temporary failure */ |
||||
continue; |
||||
} |
||||
if (fds[0].revents & POLLIN) { |
||||
/* activity on control pipe, read the byte and exit */ |
||||
r = usbi_read(udev_control_pipe[0], &dummy, sizeof(dummy)); |
||||
if (r <= 0) { |
||||
usbi_warn(NULL, "udev control pipe read failed"); |
||||
} |
||||
break; |
||||
} |
||||
if (fds[1].revents & POLLIN) { |
||||
usbi_mutex_static_lock(&linux_hotplug_lock); |
||||
udev_dev = udev_monitor_receive_device(udev_monitor); |
||||
if (udev_dev) |
||||
udev_hotplug_event(udev_dev); |
||||
usbi_mutex_static_unlock(&linux_hotplug_lock); |
||||
} |
||||
} |
||||
|
||||
usbi_dbg("udev event thread exiting"); |
||||
|
||||
return NULL; |
||||
} |
||||
|
||||
static int udev_device_info(struct libusb_context *ctx, int detached, |
||||
struct udev_device *udev_dev, uint8_t *busnum, |
||||
uint8_t *devaddr, const char **sys_name) { |
||||
const char *dev_node; |
||||
|
||||
dev_node = udev_device_get_devnode(udev_dev); |
||||
if (!dev_node) { |
||||
return LIBUSB_ERROR_OTHER; |
||||
} |
||||
|
||||
*sys_name = udev_device_get_sysname(udev_dev); |
||||
if (!*sys_name) { |
||||
return LIBUSB_ERROR_OTHER; |
||||
} |
||||
|
||||
return linux_get_device_address(ctx, detached, busnum, devaddr, |
||||
dev_node, *sys_name); |
||||
} |
||||
|
||||
static void udev_hotplug_event(struct udev_device* udev_dev) |
||||
{ |
||||
const char* udev_action; |
||||
const char* sys_name = NULL; |
||||
uint8_t busnum = 0, devaddr = 0; |
||||
int detached; |
||||
int r; |
||||
|
||||
do { |
||||
udev_action = udev_device_get_action(udev_dev); |
||||
if (!udev_action) { |
||||
break; |
||||
} |
||||
|
||||
detached = !strncmp(udev_action, "remove", 6); |
||||
|
||||
r = udev_device_info(NULL, detached, udev_dev, &busnum, &devaddr, &sys_name); |
||||
if (LIBUSB_SUCCESS != r) { |
||||
break; |
||||
} |
||||
|
||||
usbi_dbg("udev hotplug event. action: %s.", udev_action); |
||||
|
||||
if (strncmp(udev_action, "add", 3) == 0) { |
||||
linux_hotplug_enumerate(busnum, devaddr, sys_name); |
||||
} else if (detached) { |
||||
linux_device_disconnected(busnum, devaddr); |
||||
} else { |
||||
usbi_err(NULL, "ignoring udev action %s", udev_action); |
||||
} |
||||
} while (0); |
||||
|
||||
udev_device_unref(udev_dev); |
||||
} |
||||
|
||||
int linux_udev_scan_devices(struct libusb_context *ctx) |
||||
{ |
||||
struct udev_enumerate *enumerator; |
||||
struct udev_list_entry *devices, *entry; |
||||
struct udev_device *udev_dev; |
||||
const char *sys_name; |
||||
int r; |
||||
|
||||
assert(udev_ctx != NULL); |
||||
|
||||
enumerator = udev_enumerate_new(udev_ctx); |
||||
if (NULL == enumerator) { |
||||
usbi_err(ctx, "error creating udev enumerator"); |
||||
return LIBUSB_ERROR_OTHER; |
||||
} |
||||
|
||||
udev_enumerate_add_match_subsystem(enumerator, "usb"); |
||||
udev_enumerate_add_match_property(enumerator, "DEVTYPE", "usb_device"); |
||||
udev_enumerate_scan_devices(enumerator); |
||||
devices = udev_enumerate_get_list_entry(enumerator); |
||||
|
||||
udev_list_entry_foreach(entry, devices) { |
||||
const char *path = udev_list_entry_get_name(entry); |
||||
uint8_t busnum = 0, devaddr = 0; |
||||
|
||||
udev_dev = udev_device_new_from_syspath(udev_ctx, path); |
||||
|
||||
r = udev_device_info(ctx, 0, udev_dev, &busnum, &devaddr, &sys_name); |
||||
if (r) { |
||||
udev_device_unref(udev_dev); |
||||
continue; |
||||
} |
||||
|
||||
linux_enumerate_device(ctx, busnum, devaddr, sys_name); |
||||
udev_device_unref(udev_dev); |
||||
} |
||||
|
||||
udev_enumerate_unref(enumerator); |
||||
|
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
void linux_udev_hotplug_poll(void) |
||||
{ |
||||
struct udev_device* udev_dev; |
||||
|
||||
usbi_mutex_static_lock(&linux_hotplug_lock); |
||||
do { |
||||
udev_dev = udev_monitor_receive_device(udev_monitor); |
||||
if (udev_dev) { |
||||
usbi_dbg("Handling hotplug event from hotplug_poll"); |
||||
udev_hotplug_event(udev_dev); |
||||
} |
||||
} while (udev_dev); |
||||
usbi_mutex_static_unlock(&linux_hotplug_lock); |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,193 @@ |
||||
/*
|
||||
* usbfs header structures |
||||
* Copyright © 2007 Daniel Drake <dsd@gentoo.org> |
||||
* Copyright © 2001 Johannes Erdfelt <johannes@erdfelt.com> |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU Lesser General Public |
||||
* License as published by the Free Software Foundation; either |
||||
* version 2.1 of the License, or (at your option) any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
* Lesser General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Lesser General Public |
||||
* License along with this library; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
*/ |
||||
|
||||
#ifndef LIBUSB_USBFS_H |
||||
#define LIBUSB_USBFS_H |
||||
|
||||
#include <linux/types.h> |
||||
|
||||
#define SYSFS_DEVICE_PATH "/sys/bus/usb/devices" |
||||
|
||||
struct usbfs_ctrltransfer { |
||||
/* keep in sync with usbdevice_fs.h:usbdevfs_ctrltransfer */ |
||||
uint8_t bmRequestType; |
||||
uint8_t bRequest; |
||||
uint16_t wValue; |
||||
uint16_t wIndex; |
||||
uint16_t wLength; |
||||
|
||||
uint32_t timeout; /* in milliseconds */ |
||||
|
||||
/* pointer to data */ |
||||
void *data; |
||||
}; |
||||
|
||||
struct usbfs_bulktransfer { |
||||
/* keep in sync with usbdevice_fs.h:usbdevfs_bulktransfer */ |
||||
unsigned int ep; |
||||
unsigned int len; |
||||
unsigned int timeout; /* in milliseconds */ |
||||
|
||||
/* pointer to data */ |
||||
void *data; |
||||
}; |
||||
|
||||
struct usbfs_setinterface { |
||||
/* keep in sync with usbdevice_fs.h:usbdevfs_setinterface */ |
||||
unsigned int interface; |
||||
unsigned int altsetting; |
||||
}; |
||||
|
||||
#define USBFS_MAXDRIVERNAME 255 |
||||
|
||||
struct usbfs_getdriver { |
||||
unsigned int interface; |
||||
char driver[USBFS_MAXDRIVERNAME + 1]; |
||||
}; |
||||
|
||||
#define USBFS_URB_SHORT_NOT_OK 0x01 |
||||
#define USBFS_URB_ISO_ASAP 0x02 |
||||
#define USBFS_URB_BULK_CONTINUATION 0x04 |
||||
#define USBFS_URB_QUEUE_BULK 0x10 |
||||
#define USBFS_URB_ZERO_PACKET 0x40 |
||||
|
||||
enum usbfs_urb_type { |
||||
USBFS_URB_TYPE_ISO = 0, |
||||
USBFS_URB_TYPE_INTERRUPT = 1, |
||||
USBFS_URB_TYPE_CONTROL = 2, |
||||
USBFS_URB_TYPE_BULK = 3, |
||||
}; |
||||
|
||||
struct usbfs_iso_packet_desc { |
||||
unsigned int length; |
||||
unsigned int actual_length; |
||||
unsigned int status; |
||||
}; |
||||
|
||||
#define MAX_ISO_BUFFER_LENGTH 49152 * 128 |
||||
#define MAX_BULK_BUFFER_LENGTH 16384 |
||||
#define MAX_CTRL_BUFFER_LENGTH 4096 |
||||
|
||||
struct usbfs_urb { |
||||
unsigned char type; |
||||
unsigned char endpoint; |
||||
int status; |
||||
unsigned int flags; |
||||
void *buffer; |
||||
int buffer_length; |
||||
int actual_length; |
||||
int start_frame; |
||||
union { |
||||
int number_of_packets; /* Only used for isoc urbs */ |
||||
unsigned int stream_id; /* Only used with bulk streams */ |
||||
}; |
||||
int error_count; |
||||
unsigned int signr; |
||||
void *usercontext; |
||||
struct usbfs_iso_packet_desc iso_frame_desc[0]; |
||||
}; |
||||
|
||||
struct usbfs_connectinfo { |
||||
unsigned int devnum; |
||||
unsigned char slow; |
||||
}; |
||||
|
||||
struct usbfs_ioctl { |
||||
int ifno; /* interface 0..N ; negative numbers reserved */ |
||||
int ioctl_code; /* MUST encode size + direction of data so the
|
||||
* macros in <asm/ioctl.h> give correct values */ |
||||
void *data; /* param buffer (in, or out) */ |
||||
}; |
||||
|
||||
struct usbfs_hub_portinfo { |
||||
unsigned char numports; |
||||
unsigned char port[127]; /* port to device num mapping */ |
||||
}; |
||||
|
||||
#define USBFS_CAP_ZERO_PACKET 0x01 |
||||
#define USBFS_CAP_BULK_CONTINUATION 0x02 |
||||
#define USBFS_CAP_NO_PACKET_SIZE_LIM 0x04 |
||||
#define USBFS_CAP_BULK_SCATTER_GATHER 0x08 |
||||
#define USBFS_CAP_REAP_AFTER_DISCONNECT 0x10 |
||||
|
||||
#define USBFS_DISCONNECT_CLAIM_IF_DRIVER 0x01 |
||||
#define USBFS_DISCONNECT_CLAIM_EXCEPT_DRIVER 0x02 |
||||
|
||||
struct usbfs_disconnect_claim { |
||||
unsigned int interface; |
||||
unsigned int flags; |
||||
char driver[USBFS_MAXDRIVERNAME + 1]; |
||||
}; |
||||
|
||||
struct usbfs_streams { |
||||
unsigned int num_streams; /* Not used by USBDEVFS_FREE_STREAMS */ |
||||
unsigned int num_eps; |
||||
unsigned char eps[0]; |
||||
}; |
||||
|
||||
#define IOCTL_USBFS_CONTROL _IOWR('U', 0, struct usbfs_ctrltransfer) |
||||
#define IOCTL_USBFS_BULK _IOWR('U', 2, struct usbfs_bulktransfer) |
||||
#define IOCTL_USBFS_RESETEP _IOR('U', 3, unsigned int) |
||||
#define IOCTL_USBFS_SETINTF _IOR('U', 4, struct usbfs_setinterface) |
||||
#define IOCTL_USBFS_SETCONFIG _IOR('U', 5, unsigned int) |
||||
#define IOCTL_USBFS_GETDRIVER _IOW('U', 8, struct usbfs_getdriver) |
||||
#define IOCTL_USBFS_SUBMITURB _IOR('U', 10, struct usbfs_urb) |
||||
#define IOCTL_USBFS_DISCARDURB _IO('U', 11) |
||||
#define IOCTL_USBFS_REAPURB _IOW('U', 12, void *) |
||||
#define IOCTL_USBFS_REAPURBNDELAY _IOW('U', 13, void *) |
||||
#define IOCTL_USBFS_CLAIMINTF _IOR('U', 15, unsigned int) |
||||
#define IOCTL_USBFS_RELEASEINTF _IOR('U', 16, unsigned int) |
||||
#define IOCTL_USBFS_CONNECTINFO _IOW('U', 17, struct usbfs_connectinfo) |
||||
#define IOCTL_USBFS_IOCTL _IOWR('U', 18, struct usbfs_ioctl) |
||||
#define IOCTL_USBFS_HUB_PORTINFO _IOR('U', 19, struct usbfs_hub_portinfo) |
||||
#define IOCTL_USBFS_RESET _IO('U', 20) |
||||
#define IOCTL_USBFS_CLEAR_HALT _IOR('U', 21, unsigned int) |
||||
#define IOCTL_USBFS_DISCONNECT _IO('U', 22) |
||||
#define IOCTL_USBFS_CONNECT _IO('U', 23) |
||||
#define IOCTL_USBFS_CLAIM_PORT _IOR('U', 24, unsigned int) |
||||
#define IOCTL_USBFS_RELEASE_PORT _IOR('U', 25, unsigned int) |
||||
#define IOCTL_USBFS_GET_CAPABILITIES _IOR('U', 26, __u32) |
||||
#define IOCTL_USBFS_DISCONNECT_CLAIM _IOR('U', 27, struct usbfs_disconnect_claim) |
||||
#define IOCTL_USBFS_ALLOC_STREAMS _IOR('U', 28, struct usbfs_streams) |
||||
#define IOCTL_USBFS_FREE_STREAMS _IOR('U', 29, struct usbfs_streams) |
||||
|
||||
extern usbi_mutex_static_t linux_hotplug_lock; |
||||
|
||||
#if defined(HAVE_LIBUDEV) |
||||
int linux_udev_start_event_monitor(void); |
||||
int linux_udev_stop_event_monitor(void); |
||||
int linux_udev_scan_devices(struct libusb_context *ctx); |
||||
void linux_udev_hotplug_poll(void); |
||||
#else |
||||
int linux_netlink_start_event_monitor(void); |
||||
int linux_netlink_stop_event_monitor(void); |
||||
void linux_netlink_hotplug_poll(void); |
||||
#endif |
||||
|
||||
void linux_hotplug_enumerate(uint8_t busnum, uint8_t devaddr, const char *sys_name); |
||||
void linux_device_disconnected(uint8_t busnum, uint8_t devaddr); |
||||
|
||||
int linux_get_device_address (struct libusb_context *ctx, int detached, |
||||
uint8_t *busnum, uint8_t *devaddr, const char *dev_node, |
||||
const char *sys_name); |
||||
int linux_enumerate_device(struct libusb_context *ctx, |
||||
uint8_t busnum, uint8_t devaddr, const char *sysfs_dir); |
||||
|
||||
#endif |
@ -0,0 +1,677 @@ |
||||
/*
|
||||
* Copyright © 2011 Martin Pieuchot <mpi@openbsd.org> |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU Lesser General Public |
||||
* License as published by the Free Software Foundation; either |
||||
* version 2.1 of the License, or (at your option) any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
* Lesser General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Lesser General Public |
||||
* License along with this library; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
*/ |
||||
|
||||
#include <config.h> |
||||
|
||||
#include <sys/time.h> |
||||
#include <sys/types.h> |
||||
|
||||
#include <errno.h> |
||||
#include <fcntl.h> |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <unistd.h> |
||||
|
||||
#include <dev/usb/usb.h> |
||||
|
||||
#include "libusbi.h" |
||||
|
||||
struct device_priv { |
||||
char devnode[16]; |
||||
int fd; |
||||
|
||||
unsigned char *cdesc; /* active config descriptor */ |
||||
usb_device_descriptor_t ddesc; /* usb device descriptor */ |
||||
}; |
||||
|
||||
struct handle_priv { |
||||
int endpoints[USB_MAX_ENDPOINTS]; |
||||
}; |
||||
|
||||
/*
|
||||
* Backend functions |
||||
*/ |
||||
static int netbsd_get_device_list(struct libusb_context *, |
||||
struct discovered_devs **); |
||||
static int netbsd_open(struct libusb_device_handle *); |
||||
static void netbsd_close(struct libusb_device_handle *); |
||||
|
||||
static int netbsd_get_device_descriptor(struct libusb_device *, unsigned char *, |
||||
int *); |
||||
static int netbsd_get_active_config_descriptor(struct libusb_device *, |
||||
unsigned char *, size_t, int *); |
||||
static int netbsd_get_config_descriptor(struct libusb_device *, uint8_t, |
||||
unsigned char *, size_t, int *); |
||||
|
||||
static int netbsd_get_configuration(struct libusb_device_handle *, int *); |
||||
static int netbsd_set_configuration(struct libusb_device_handle *, int); |
||||
|
||||
static int netbsd_claim_interface(struct libusb_device_handle *, int); |
||||
static int netbsd_release_interface(struct libusb_device_handle *, int); |
||||
|
||||
static int netbsd_set_interface_altsetting(struct libusb_device_handle *, int, |
||||
int); |
||||
static int netbsd_clear_halt(struct libusb_device_handle *, unsigned char); |
||||
static int netbsd_reset_device(struct libusb_device_handle *); |
||||
static void netbsd_destroy_device(struct libusb_device *); |
||||
|
||||
static int netbsd_submit_transfer(struct usbi_transfer *); |
||||
static int netbsd_cancel_transfer(struct usbi_transfer *); |
||||
static void netbsd_clear_transfer_priv(struct usbi_transfer *); |
||||
static int netbsd_handle_transfer_completion(struct usbi_transfer *); |
||||
static int netbsd_clock_gettime(int, struct timespec *); |
||||
|
||||
/*
|
||||
* Private functions |
||||
*/ |
||||
static int _errno_to_libusb(int); |
||||
static int _cache_active_config_descriptor(struct libusb_device *, int); |
||||
static int _sync_control_transfer(struct usbi_transfer *); |
||||
static int _sync_gen_transfer(struct usbi_transfer *); |
||||
static int _access_endpoint(struct libusb_transfer *); |
||||
|
||||
const struct usbi_os_backend netbsd_backend = { |
||||
"Synchronous NetBSD backend", |
||||
0, |
||||
NULL, /* init() */ |
||||
NULL, /* exit() */ |
||||
netbsd_get_device_list, |
||||
NULL, /* hotplug_poll */ |
||||
netbsd_open, |
||||
netbsd_close, |
||||
|
||||
netbsd_get_device_descriptor, |
||||
netbsd_get_active_config_descriptor, |
||||
netbsd_get_config_descriptor, |
||||
NULL, /* get_config_descriptor_by_value() */ |
||||
|
||||
netbsd_get_configuration, |
||||
netbsd_set_configuration, |
||||
|
||||
netbsd_claim_interface, |
||||
netbsd_release_interface, |
||||
|
||||
netbsd_set_interface_altsetting, |
||||
netbsd_clear_halt, |
||||
netbsd_reset_device, |
||||
|
||||
NULL, /* alloc_streams */ |
||||
NULL, /* free_streams */ |
||||
|
||||
NULL, /* dev_mem_alloc() */ |
||||
NULL, /* dev_mem_free() */ |
||||
|
||||
NULL, /* kernel_driver_active() */ |
||||
NULL, /* detach_kernel_driver() */ |
||||
NULL, /* attach_kernel_driver() */ |
||||
|
||||
netbsd_destroy_device, |
||||
|
||||
netbsd_submit_transfer, |
||||
netbsd_cancel_transfer, |
||||
netbsd_clear_transfer_priv, |
||||
|
||||
NULL, /* handle_events() */ |
||||
netbsd_handle_transfer_completion, |
||||
|
||||
netbsd_clock_gettime, |
||||
sizeof(struct device_priv), |
||||
sizeof(struct handle_priv), |
||||
0, /* transfer_priv_size */ |
||||
}; |
||||
|
||||
int |
||||
netbsd_get_device_list(struct libusb_context * ctx, |
||||
struct discovered_devs **discdevs) |
||||
{ |
||||
struct libusb_device *dev; |
||||
struct device_priv *dpriv; |
||||
struct usb_device_info di; |
||||
unsigned long session_id; |
||||
char devnode[16]; |
||||
int fd, err, i; |
||||
|
||||
usbi_dbg(""); |
||||
|
||||
/* Only ugen(4) is supported */ |
||||
for (i = 0; i < USB_MAX_DEVICES; i++) { |
||||
/* Control endpoint is always .00 */ |
||||
snprintf(devnode, sizeof(devnode), "/dev/ugen%d.00", i); |
||||
|
||||
if ((fd = open(devnode, O_RDONLY)) < 0) { |
||||
if (errno != ENOENT && errno != ENXIO) |
||||
usbi_err(ctx, "could not open %s", devnode); |
||||
continue; |
||||
} |
||||
|
||||
if (ioctl(fd, USB_GET_DEVICEINFO, &di) < 0) |
||||
continue; |
||||
|
||||
session_id = (di.udi_bus << 8 | di.udi_addr); |
||||
dev = usbi_get_device_by_session_id(ctx, session_id); |
||||
|
||||
if (dev == NULL) { |
||||
dev = usbi_alloc_device(ctx, session_id); |
||||
if (dev == NULL) |
||||
return (LIBUSB_ERROR_NO_MEM); |
||||
|
||||
dev->bus_number = di.udi_bus; |
||||
dev->device_address = di.udi_addr; |
||||
dev->speed = di.udi_speed; |
||||
|
||||
dpriv = (struct device_priv *)dev->os_priv; |
||||
strlcpy(dpriv->devnode, devnode, sizeof(devnode)); |
||||
dpriv->fd = -1; |
||||
|
||||
if (ioctl(fd, USB_GET_DEVICE_DESC, &dpriv->ddesc) < 0) { |
||||
err = errno; |
||||
goto error; |
||||
} |
||||
|
||||
dpriv->cdesc = NULL; |
||||
if (_cache_active_config_descriptor(dev, fd)) { |
||||
err = errno; |
||||
goto error; |
||||
} |
||||
|
||||
if ((err = usbi_sanitize_device(dev))) |
||||
goto error; |
||||
} |
||||
close(fd); |
||||
|
||||
if (discovered_devs_append(*discdevs, dev) == NULL) |
||||
return (LIBUSB_ERROR_NO_MEM); |
||||
|
||||
libusb_unref_device(dev); |
||||
} |
||||
|
||||
return (LIBUSB_SUCCESS); |
||||
|
||||
error: |
||||
close(fd); |
||||
libusb_unref_device(dev); |
||||
return _errno_to_libusb(err); |
||||
} |
||||
|
||||
int |
||||
netbsd_open(struct libusb_device_handle *handle) |
||||
{ |
||||
struct handle_priv *hpriv = (struct handle_priv *)handle->os_priv; |
||||
struct device_priv *dpriv = (struct device_priv *)handle->dev->os_priv; |
||||
|
||||
dpriv->fd = open(dpriv->devnode, O_RDWR); |
||||
if (dpriv->fd < 0) { |
||||
dpriv->fd = open(dpriv->devnode, O_RDONLY); |
||||
if (dpriv->fd < 0) |
||||
return _errno_to_libusb(errno); |
||||
} |
||||
|
||||
usbi_dbg("open %s: fd %d", dpriv->devnode, dpriv->fd); |
||||
|
||||
return (LIBUSB_SUCCESS); |
||||
} |
||||
|
||||
void |
||||
netbsd_close(struct libusb_device_handle *handle) |
||||
{ |
||||
struct handle_priv *hpriv = (struct handle_priv *)handle->os_priv; |
||||
struct device_priv *dpriv = (struct device_priv *)handle->dev->os_priv; |
||||
|
||||
usbi_dbg("close: fd %d", dpriv->fd); |
||||
|
||||
close(dpriv->fd); |
||||
dpriv->fd = -1; |
||||
} |
||||
|
||||
int |
||||
netbsd_get_device_descriptor(struct libusb_device *dev, unsigned char *buf, |
||||
int *host_endian) |
||||
{ |
||||
struct device_priv *dpriv = (struct device_priv *)dev->os_priv; |
||||
|
||||
usbi_dbg(""); |
||||
|
||||
memcpy(buf, &dpriv->ddesc, DEVICE_DESC_LENGTH); |
||||
|
||||
*host_endian = 0; |
||||
|
||||
return (LIBUSB_SUCCESS); |
||||
} |
||||
|
||||
int |
||||
netbsd_get_active_config_descriptor(struct libusb_device *dev, |
||||
unsigned char *buf, size_t len, int *host_endian) |
||||
{ |
||||
struct device_priv *dpriv = (struct device_priv *)dev->os_priv; |
||||
usb_config_descriptor_t *ucd; |
||||
|
||||
ucd = (usb_config_descriptor_t *) dpriv->cdesc; |
||||
len = MIN(len, UGETW(ucd->wTotalLength)); |
||||
|
||||
usbi_dbg("len %d", len); |
||||
|
||||
memcpy(buf, dpriv->cdesc, len); |
||||
|
||||
*host_endian = 0; |
||||
|
||||
return len; |
||||
} |
||||
|
||||
int |
||||
netbsd_get_config_descriptor(struct libusb_device *dev, uint8_t idx, |
||||
unsigned char *buf, size_t len, int *host_endian) |
||||
{ |
||||
struct device_priv *dpriv = (struct device_priv *)dev->os_priv; |
||||
struct usb_full_desc ufd; |
||||
int fd, err; |
||||
|
||||
usbi_dbg("index %d, len %d", idx, len); |
||||
|
||||
/* A config descriptor may be requested before opening the device */ |
||||
if (dpriv->fd >= 0) { |
||||
fd = dpriv->fd; |
||||
} else { |
||||
fd = open(dpriv->devnode, O_RDONLY); |
||||
if (fd < 0) |
||||
return _errno_to_libusb(errno); |
||||
} |
||||
|
||||
ufd.ufd_config_index = idx; |
||||
ufd.ufd_size = len; |
||||
ufd.ufd_data = buf; |
||||
|
||||
if ((ioctl(fd, USB_GET_FULL_DESC, &ufd)) < 0) { |
||||
err = errno; |
||||
if (dpriv->fd < 0) |
||||
close(fd); |
||||
return _errno_to_libusb(err); |
||||
} |
||||
|
||||
if (dpriv->fd < 0) |
||||
close(fd); |
||||
|
||||
*host_endian = 0; |
||||
|
||||
return len; |
||||
} |
||||
|
||||
int |
||||
netbsd_get_configuration(struct libusb_device_handle *handle, int *config) |
||||
{ |
||||
struct device_priv *dpriv = (struct device_priv *)handle->dev->os_priv; |
||||
|
||||
usbi_dbg(""); |
||||
|
||||
if (ioctl(dpriv->fd, USB_GET_CONFIG, config) < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
usbi_dbg("configuration %d", *config); |
||||
|
||||
return (LIBUSB_SUCCESS); |
||||
} |
||||
|
||||
int |
||||
netbsd_set_configuration(struct libusb_device_handle *handle, int config) |
||||
{ |
||||
struct device_priv *dpriv = (struct device_priv *)handle->dev->os_priv; |
||||
|
||||
usbi_dbg("configuration %d", config); |
||||
|
||||
if (ioctl(dpriv->fd, USB_SET_CONFIG, &config) < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
return _cache_active_config_descriptor(handle->dev, dpriv->fd); |
||||
} |
||||
|
||||
int |
||||
netbsd_claim_interface(struct libusb_device_handle *handle, int iface) |
||||
{ |
||||
struct handle_priv *hpriv = (struct handle_priv *)handle->os_priv; |
||||
int i; |
||||
|
||||
for (i = 0; i < USB_MAX_ENDPOINTS; i++) |
||||
hpriv->endpoints[i] = -1; |
||||
|
||||
return (LIBUSB_SUCCESS); |
||||
} |
||||
|
||||
int |
||||
netbsd_release_interface(struct libusb_device_handle *handle, int iface) |
||||
{ |
||||
struct handle_priv *hpriv = (struct handle_priv *)handle->os_priv; |
||||
int i; |
||||
|
||||
for (i = 0; i < USB_MAX_ENDPOINTS; i++) |
||||
if (hpriv->endpoints[i] >= 0) |
||||
close(hpriv->endpoints[i]); |
||||
|
||||
return (LIBUSB_SUCCESS); |
||||
} |
||||
|
||||
int |
||||
netbsd_set_interface_altsetting(struct libusb_device_handle *handle, int iface, |
||||
int altsetting) |
||||
{ |
||||
struct device_priv *dpriv = (struct device_priv *)handle->dev->os_priv; |
||||
struct usb_alt_interface intf; |
||||
|
||||
usbi_dbg("iface %d, setting %d", iface, altsetting); |
||||
|
||||
memset(&intf, 0, sizeof(intf)); |
||||
|
||||
intf.uai_interface_index = iface; |
||||
intf.uai_alt_no = altsetting; |
||||
|
||||
if (ioctl(dpriv->fd, USB_SET_ALTINTERFACE, &intf) < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
return (LIBUSB_SUCCESS); |
||||
} |
||||
|
||||
int |
||||
netbsd_clear_halt(struct libusb_device_handle *handle, unsigned char endpoint) |
||||
{ |
||||
struct device_priv *dpriv = (struct device_priv *)handle->dev->os_priv; |
||||
struct usb_ctl_request req; |
||||
|
||||
usbi_dbg(""); |
||||
|
||||
req.ucr_request.bmRequestType = UT_WRITE_ENDPOINT; |
||||
req.ucr_request.bRequest = UR_CLEAR_FEATURE; |
||||
USETW(req.ucr_request.wValue, UF_ENDPOINT_HALT); |
||||
USETW(req.ucr_request.wIndex, endpoint); |
||||
USETW(req.ucr_request.wLength, 0); |
||||
|
||||
if (ioctl(dpriv->fd, USB_DO_REQUEST, &req) < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
return (LIBUSB_SUCCESS); |
||||
} |
||||
|
||||
int |
||||
netbsd_reset_device(struct libusb_device_handle *handle) |
||||
{ |
||||
usbi_dbg(""); |
||||
|
||||
return (LIBUSB_ERROR_NOT_SUPPORTED); |
||||
} |
||||
|
||||
void |
||||
netbsd_destroy_device(struct libusb_device *dev) |
||||
{ |
||||
struct device_priv *dpriv = (struct device_priv *)dev->os_priv; |
||||
|
||||
usbi_dbg(""); |
||||
|
||||
free(dpriv->cdesc); |
||||
} |
||||
|
||||
int |
||||
netbsd_submit_transfer(struct usbi_transfer *itransfer) |
||||
{ |
||||
struct libusb_transfer *transfer; |
||||
struct handle_priv *hpriv; |
||||
int err = 0; |
||||
|
||||
usbi_dbg(""); |
||||
|
||||
transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); |
||||
hpriv = (struct handle_priv *)transfer->dev_handle->os_priv; |
||||
|
||||
switch (transfer->type) { |
||||
case LIBUSB_TRANSFER_TYPE_CONTROL: |
||||
err = _sync_control_transfer(itransfer); |
||||
break; |
||||
case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: |
||||
if (IS_XFEROUT(transfer)) { |
||||
/* Isochronous write is not supported */ |
||||
err = LIBUSB_ERROR_NOT_SUPPORTED; |
||||
break; |
||||
} |
||||
err = _sync_gen_transfer(itransfer); |
||||
break; |
||||
case LIBUSB_TRANSFER_TYPE_BULK: |
||||
case LIBUSB_TRANSFER_TYPE_INTERRUPT: |
||||
if (IS_XFEROUT(transfer) && |
||||
transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET) { |
||||
err = LIBUSB_ERROR_NOT_SUPPORTED; |
||||
break; |
||||
} |
||||
err = _sync_gen_transfer(itransfer); |
||||
break; |
||||
case LIBUSB_TRANSFER_TYPE_BULK_STREAM: |
||||
err = LIBUSB_ERROR_NOT_SUPPORTED; |
||||
break; |
||||
} |
||||
|
||||
if (err) |
||||
return (err); |
||||
|
||||
usbi_signal_transfer_completion(itransfer); |
||||
|
||||
return (LIBUSB_SUCCESS); |
||||
} |
||||
|
||||
int |
||||
netbsd_cancel_transfer(struct usbi_transfer *itransfer) |
||||
{ |
||||
usbi_dbg(""); |
||||
|
||||
return (LIBUSB_ERROR_NOT_SUPPORTED); |
||||
} |
||||
|
||||
void |
||||
netbsd_clear_transfer_priv(struct usbi_transfer *itransfer) |
||||
{ |
||||
usbi_dbg(""); |
||||
|
||||
/* Nothing to do */ |
||||
} |
||||
|
||||
int |
||||
netbsd_handle_transfer_completion(struct usbi_transfer *itransfer) |
||||
{ |
||||
return usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_COMPLETED); |
||||
} |
||||
|
||||
int |
||||
netbsd_clock_gettime(int clkid, struct timespec *tp) |
||||
{ |
||||
usbi_dbg("clock %d", clkid); |
||||
|
||||
if (clkid == USBI_CLOCK_REALTIME) |
||||
return clock_gettime(CLOCK_REALTIME, tp); |
||||
|
||||
if (clkid == USBI_CLOCK_MONOTONIC) |
||||
return clock_gettime(CLOCK_MONOTONIC, tp); |
||||
|
||||
return (LIBUSB_ERROR_INVALID_PARAM); |
||||
} |
||||
|
||||
int |
||||
_errno_to_libusb(int err) |
||||
{ |
||||
switch (err) { |
||||
case EIO: |
||||
return (LIBUSB_ERROR_IO); |
||||
case EACCES: |
||||
return (LIBUSB_ERROR_ACCESS); |
||||
case ENOENT: |
||||
return (LIBUSB_ERROR_NO_DEVICE); |
||||
case ENOMEM: |
||||
return (LIBUSB_ERROR_NO_MEM); |
||||
} |
||||
|
||||
usbi_dbg("error: %s", strerror(err)); |
||||
|
||||
return (LIBUSB_ERROR_OTHER); |
||||
} |
||||
|
||||
int |
||||
_cache_active_config_descriptor(struct libusb_device *dev, int fd) |
||||
{ |
||||
struct device_priv *dpriv = (struct device_priv *)dev->os_priv; |
||||
struct usb_config_desc ucd; |
||||
struct usb_full_desc ufd; |
||||
unsigned char* buf; |
||||
int len; |
||||
|
||||
usbi_dbg("fd %d", fd); |
||||
|
||||
ucd.ucd_config_index = USB_CURRENT_CONFIG_INDEX; |
||||
|
||||
if ((ioctl(fd, USB_GET_CONFIG_DESC, &ucd)) < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
usbi_dbg("active bLength %d", ucd.ucd_desc.bLength); |
||||
|
||||
len = UGETW(ucd.ucd_desc.wTotalLength); |
||||
buf = malloc(len); |
||||
if (buf == NULL) |
||||
return (LIBUSB_ERROR_NO_MEM); |
||||
|
||||
ufd.ufd_config_index = ucd.ucd_config_index; |
||||
ufd.ufd_size = len; |
||||
ufd.ufd_data = buf; |
||||
|
||||
usbi_dbg("index %d, len %d", ufd.ufd_config_index, len); |
||||
|
||||
if ((ioctl(fd, USB_GET_FULL_DESC, &ufd)) < 0) { |
||||
free(buf); |
||||
return _errno_to_libusb(errno); |
||||
} |
||||
|
||||
if (dpriv->cdesc) |
||||
free(dpriv->cdesc); |
||||
dpriv->cdesc = buf; |
||||
|
||||
return (0); |
||||
} |
||||
|
||||
int |
||||
_sync_control_transfer(struct usbi_transfer *itransfer) |
||||
{ |
||||
struct libusb_transfer *transfer; |
||||
struct libusb_control_setup *setup; |
||||
struct device_priv *dpriv; |
||||
struct usb_ctl_request req; |
||||
|
||||
transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); |
||||
dpriv = (struct device_priv *)transfer->dev_handle->dev->os_priv; |
||||
setup = (struct libusb_control_setup *)transfer->buffer; |
||||
|
||||
usbi_dbg("type %d request %d value %d index %d length %d timeout %d", |
||||
setup->bmRequestType, setup->bRequest, |
||||
libusb_le16_to_cpu(setup->wValue), |
||||
libusb_le16_to_cpu(setup->wIndex), |
||||
libusb_le16_to_cpu(setup->wLength), transfer->timeout); |
||||
|
||||
req.ucr_request.bmRequestType = setup->bmRequestType; |
||||
req.ucr_request.bRequest = setup->bRequest; |
||||
/* Don't use USETW, libusb already deals with the endianness */ |
||||
(*(uint16_t *)req.ucr_request.wValue) = setup->wValue; |
||||
(*(uint16_t *)req.ucr_request.wIndex) = setup->wIndex; |
||||
(*(uint16_t *)req.ucr_request.wLength) = setup->wLength; |
||||
req.ucr_data = transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE; |
||||
|
||||
if ((transfer->flags & LIBUSB_TRANSFER_SHORT_NOT_OK) == 0) |
||||
req.ucr_flags = USBD_SHORT_XFER_OK; |
||||
|
||||
if ((ioctl(dpriv->fd, USB_SET_TIMEOUT, &transfer->timeout)) < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
if ((ioctl(dpriv->fd, USB_DO_REQUEST, &req)) < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
itransfer->transferred = req.ucr_actlen; |
||||
|
||||
usbi_dbg("transferred %d", itransfer->transferred); |
||||
|
||||
return (0); |
||||
} |
||||
|
||||
int |
||||
_access_endpoint(struct libusb_transfer *transfer) |
||||
{ |
||||
struct handle_priv *hpriv; |
||||
struct device_priv *dpriv; |
||||
char *s, devnode[16]; |
||||
int fd, endpt; |
||||
mode_t mode; |
||||
|
||||
hpriv = (struct handle_priv *)transfer->dev_handle->os_priv; |
||||
dpriv = (struct device_priv *)transfer->dev_handle->dev->os_priv; |
||||
|
||||
endpt = UE_GET_ADDR(transfer->endpoint); |
||||
mode = IS_XFERIN(transfer) ? O_RDONLY : O_WRONLY; |
||||
|
||||
usbi_dbg("endpoint %d mode %d", endpt, mode); |
||||
|
||||
if (hpriv->endpoints[endpt] < 0) { |
||||
/* Pick the right node given the control one */ |
||||
strlcpy(devnode, dpriv->devnode, sizeof(devnode)); |
||||
s = strchr(devnode, '.'); |
||||
snprintf(s, 4, ".%02d", endpt); |
||||
|
||||
/* We may need to read/write to the same endpoint later. */ |
||||
if (((fd = open(devnode, O_RDWR)) < 0) && (errno == ENXIO)) |
||||
if ((fd = open(devnode, mode)) < 0) |
||||
return (-1); |
||||
|
||||
hpriv->endpoints[endpt] = fd; |
||||
} |
||||
|
||||
return (hpriv->endpoints[endpt]); |
||||
} |
||||
|
||||
int |
||||
_sync_gen_transfer(struct usbi_transfer *itransfer) |
||||
{ |
||||
struct libusb_transfer *transfer; |
||||
int fd, nr = 1; |
||||
|
||||
transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); |
||||
|
||||
/*
|
||||
* Bulk, Interrupt or Isochronous transfer depends on the |
||||
* endpoint and thus the node to open. |
||||
*/ |
||||
if ((fd = _access_endpoint(transfer)) < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
if ((ioctl(fd, USB_SET_TIMEOUT, &transfer->timeout)) < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
if (IS_XFERIN(transfer)) { |
||||
if ((transfer->flags & LIBUSB_TRANSFER_SHORT_NOT_OK) == 0) |
||||
if ((ioctl(fd, USB_SET_SHORT_XFER, &nr)) < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
nr = read(fd, transfer->buffer, transfer->length); |
||||
} else { |
||||
nr = write(fd, transfer->buffer, transfer->length); |
||||
} |
||||
|
||||
if (nr < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
itransfer->transferred = nr; |
||||
|
||||
return (0); |
||||
} |
@ -0,0 +1,771 @@ |
||||
/*
|
||||
* Copyright © 2011-2013 Martin Pieuchot <mpi@openbsd.org> |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU Lesser General Public |
||||
* License as published by the Free Software Foundation; either |
||||
* version 2.1 of the License, or (at your option) any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
* Lesser General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Lesser General Public |
||||
* License along with this library; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
*/ |
||||
|
||||
#include <config.h> |
||||
|
||||
#include <sys/time.h> |
||||
#include <sys/types.h> |
||||
|
||||
#include <errno.h> |
||||
#include <fcntl.h> |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include <unistd.h> |
||||
|
||||
#include <dev/usb/usb.h> |
||||
|
||||
#include "libusbi.h" |
||||
|
||||
struct device_priv { |
||||
char *devname; /* name of the ugen(4) node */ |
||||
int fd; /* device file descriptor */ |
||||
|
||||
unsigned char *cdesc; /* active config descriptor */ |
||||
usb_device_descriptor_t ddesc; /* usb device descriptor */ |
||||
}; |
||||
|
||||
struct handle_priv { |
||||
int endpoints[USB_MAX_ENDPOINTS]; |
||||
}; |
||||
|
||||
/*
|
||||
* Backend functions |
||||
*/ |
||||
static int obsd_get_device_list(struct libusb_context *, |
||||
struct discovered_devs **); |
||||
static int obsd_open(struct libusb_device_handle *); |
||||
static void obsd_close(struct libusb_device_handle *); |
||||
|
||||
static int obsd_get_device_descriptor(struct libusb_device *, unsigned char *, |
||||
int *); |
||||
static int obsd_get_active_config_descriptor(struct libusb_device *, |
||||
unsigned char *, size_t, int *); |
||||
static int obsd_get_config_descriptor(struct libusb_device *, uint8_t, |
||||
unsigned char *, size_t, int *); |
||||
|
||||
static int obsd_get_configuration(struct libusb_device_handle *, int *); |
||||
static int obsd_set_configuration(struct libusb_device_handle *, int); |
||||
|
||||
static int obsd_claim_interface(struct libusb_device_handle *, int); |
||||
static int obsd_release_interface(struct libusb_device_handle *, int); |
||||
|
||||
static int obsd_set_interface_altsetting(struct libusb_device_handle *, int, |
||||
int); |
||||
static int obsd_clear_halt(struct libusb_device_handle *, unsigned char); |
||||
static int obsd_reset_device(struct libusb_device_handle *); |
||||
static void obsd_destroy_device(struct libusb_device *); |
||||
|
||||
static int obsd_submit_transfer(struct usbi_transfer *); |
||||
static int obsd_cancel_transfer(struct usbi_transfer *); |
||||
static void obsd_clear_transfer_priv(struct usbi_transfer *); |
||||
static int obsd_handle_transfer_completion(struct usbi_transfer *); |
||||
static int obsd_clock_gettime(int, struct timespec *); |
||||
|
||||
/*
|
||||
* Private functions |
||||
*/ |
||||
static int _errno_to_libusb(int); |
||||
static int _cache_active_config_descriptor(struct libusb_device *); |
||||
static int _sync_control_transfer(struct usbi_transfer *); |
||||
static int _sync_gen_transfer(struct usbi_transfer *); |
||||
static int _access_endpoint(struct libusb_transfer *); |
||||
|
||||
static int _bus_open(int); |
||||
|
||||
|
||||
const struct usbi_os_backend openbsd_backend = { |
||||
"Synchronous OpenBSD backend", |
||||
0, |
||||
NULL, /* init() */ |
||||
NULL, /* exit() */ |
||||
obsd_get_device_list, |
||||
NULL, /* hotplug_poll */ |
||||
obsd_open, |
||||
obsd_close, |
||||
|
||||
obsd_get_device_descriptor, |
||||
obsd_get_active_config_descriptor, |
||||
obsd_get_config_descriptor, |
||||
NULL, /* get_config_descriptor_by_value() */ |
||||
|
||||
obsd_get_configuration, |
||||
obsd_set_configuration, |
||||
|
||||
obsd_claim_interface, |
||||
obsd_release_interface, |
||||
|
||||
obsd_set_interface_altsetting, |
||||
obsd_clear_halt, |
||||
obsd_reset_device, |
||||
|
||||
NULL, /* alloc_streams */ |
||||
NULL, /* free_streams */ |
||||
|
||||
NULL, /* dev_mem_alloc() */ |
||||
NULL, /* dev_mem_free() */ |
||||
|
||||
NULL, /* kernel_driver_active() */ |
||||
NULL, /* detach_kernel_driver() */ |
||||
NULL, /* attach_kernel_driver() */ |
||||
|
||||
obsd_destroy_device, |
||||
|
||||
obsd_submit_transfer, |
||||
obsd_cancel_transfer, |
||||
obsd_clear_transfer_priv, |
||||
|
||||
NULL, /* handle_events() */ |
||||
obsd_handle_transfer_completion, |
||||
|
||||
obsd_clock_gettime, |
||||
sizeof(struct device_priv), |
||||
sizeof(struct handle_priv), |
||||
0, /* transfer_priv_size */ |
||||
}; |
||||
|
||||
#define DEVPATH "/dev/" |
||||
#define USBDEV DEVPATH "usb" |
||||
|
||||
int |
||||
obsd_get_device_list(struct libusb_context * ctx, |
||||
struct discovered_devs **discdevs) |
||||
{ |
||||
struct discovered_devs *ddd; |
||||
struct libusb_device *dev; |
||||
struct device_priv *dpriv; |
||||
struct usb_device_info di; |
||||
struct usb_device_ddesc dd; |
||||
unsigned long session_id; |
||||
char devices[USB_MAX_DEVICES]; |
||||
char busnode[16]; |
||||
char *udevname; |
||||
int fd, addr, i, j; |
||||
|
||||
usbi_dbg(""); |
||||
|
||||
for (i = 0; i < 8; i++) { |
||||
snprintf(busnode, sizeof(busnode), USBDEV "%d", i); |
||||
|
||||
if ((fd = open(busnode, O_RDWR)) < 0) { |
||||
if (errno != ENOENT && errno != ENXIO) |
||||
usbi_err(ctx, "could not open %s", busnode); |
||||
continue; |
||||
} |
||||
|
||||
bzero(devices, sizeof(devices)); |
||||
for (addr = 1; addr < USB_MAX_DEVICES; addr++) { |
||||
if (devices[addr]) |
||||
continue; |
||||
|
||||
di.udi_addr = addr; |
||||
if (ioctl(fd, USB_DEVICEINFO, &di) < 0) |
||||
continue; |
||||
|
||||
/*
|
||||
* XXX If ugen(4) is attached to the USB device |
||||
* it will be used. |
||||
*/ |
||||
udevname = NULL; |
||||
for (j = 0; j < USB_MAX_DEVNAMES; j++) |
||||
if (!strncmp("ugen", di.udi_devnames[j], 4)) { |
||||
udevname = strdup(di.udi_devnames[j]); |
||||
break; |
||||
} |
||||
|
||||
session_id = (di.udi_bus << 8 | di.udi_addr); |
||||
dev = usbi_get_device_by_session_id(ctx, session_id); |
||||
|
||||
if (dev == NULL) { |
||||
dev = usbi_alloc_device(ctx, session_id); |
||||
if (dev == NULL) { |
||||
close(fd); |
||||
return (LIBUSB_ERROR_NO_MEM); |
||||
} |
||||
|
||||
dev->bus_number = di.udi_bus; |
||||
dev->device_address = di.udi_addr; |
||||
dev->speed = di.udi_speed; |
||||
|
||||
dpriv = (struct device_priv *)dev->os_priv; |
||||
dpriv->fd = -1; |
||||
dpriv->cdesc = NULL; |
||||
dpriv->devname = udevname; |
||||
|
||||
dd.udd_bus = di.udi_bus; |
||||
dd.udd_addr = di.udi_addr; |
||||
if (ioctl(fd, USB_DEVICE_GET_DDESC, &dd) < 0) { |
||||
libusb_unref_device(dev); |
||||
continue; |
||||
} |
||||
dpriv->ddesc = dd.udd_desc; |
||||
|
||||
if (_cache_active_config_descriptor(dev)) { |
||||
libusb_unref_device(dev); |
||||
continue; |
||||
} |
||||
|
||||
if (usbi_sanitize_device(dev)) { |
||||
libusb_unref_device(dev); |
||||
continue; |
||||
} |
||||
} |
||||
|
||||
ddd = discovered_devs_append(*discdevs, dev); |
||||
if (ddd == NULL) { |
||||
close(fd); |
||||
return (LIBUSB_ERROR_NO_MEM); |
||||
} |
||||
libusb_unref_device(dev); |
||||
|
||||
*discdevs = ddd; |
||||
devices[addr] = 1; |
||||
} |
||||
|
||||
close(fd); |
||||
} |
||||
|
||||
return (LIBUSB_SUCCESS); |
||||
} |
||||
|
||||
int |
||||
obsd_open(struct libusb_device_handle *handle) |
||||
{ |
||||
struct handle_priv *hpriv = (struct handle_priv *)handle->os_priv; |
||||
struct device_priv *dpriv = (struct device_priv *)handle->dev->os_priv; |
||||
char devnode[16]; |
||||
|
||||
if (dpriv->devname) { |
||||
/*
|
||||
* Only open ugen(4) attached devices read-write, all |
||||
* read-only operations are done through the bus node. |
||||
*/ |
||||
snprintf(devnode, sizeof(devnode), DEVPATH "%s.00", |
||||
dpriv->devname); |
||||
dpriv->fd = open(devnode, O_RDWR); |
||||
if (dpriv->fd < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
usbi_dbg("open %s: fd %d", devnode, dpriv->fd); |
||||
} |
||||
|
||||
return (LIBUSB_SUCCESS); |
||||
} |
||||
|
||||
void |
||||
obsd_close(struct libusb_device_handle *handle) |
||||
{ |
||||
struct handle_priv *hpriv = (struct handle_priv *)handle->os_priv; |
||||
struct device_priv *dpriv = (struct device_priv *)handle->dev->os_priv; |
||||
|
||||
if (dpriv->devname) { |
||||
usbi_dbg("close: fd %d", dpriv->fd); |
||||
|
||||
close(dpriv->fd); |
||||
dpriv->fd = -1; |
||||
} |
||||
} |
||||
|
||||
int |
||||
obsd_get_device_descriptor(struct libusb_device *dev, unsigned char *buf, |
||||
int *host_endian) |
||||
{ |
||||
struct device_priv *dpriv = (struct device_priv *)dev->os_priv; |
||||
|
||||
usbi_dbg(""); |
||||
|
||||
memcpy(buf, &dpriv->ddesc, DEVICE_DESC_LENGTH); |
||||
|
||||
*host_endian = 0; |
||||
|
||||
return (LIBUSB_SUCCESS); |
||||
} |
||||
|
||||
int |
||||
obsd_get_active_config_descriptor(struct libusb_device *dev, |
||||
unsigned char *buf, size_t len, int *host_endian) |
||||
{ |
||||
struct device_priv *dpriv = (struct device_priv *)dev->os_priv; |
||||
usb_config_descriptor_t *ucd = (usb_config_descriptor_t *)dpriv->cdesc; |
||||
|
||||
len = MIN(len, UGETW(ucd->wTotalLength)); |
||||
|
||||
usbi_dbg("len %d", len); |
||||
|
||||
memcpy(buf, dpriv->cdesc, len); |
||||
|
||||
*host_endian = 0; |
||||
|
||||
return (len); |
||||
} |
||||
|
||||
int |
||||
obsd_get_config_descriptor(struct libusb_device *dev, uint8_t idx, |
||||
unsigned char *buf, size_t len, int *host_endian) |
||||
{ |
||||
struct usb_device_fdesc udf; |
||||
int fd, err; |
||||
|
||||
if ((fd = _bus_open(dev->bus_number)) < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
udf.udf_bus = dev->bus_number; |
||||
udf.udf_addr = dev->device_address; |
||||
udf.udf_config_index = idx; |
||||
udf.udf_size = len; |
||||
udf.udf_data = buf; |
||||
|
||||
usbi_dbg("index %d, len %d", udf.udf_config_index, len); |
||||
|
||||
if (ioctl(fd, USB_DEVICE_GET_FDESC, &udf) < 0) { |
||||
err = errno; |
||||
close(fd); |
||||
return _errno_to_libusb(err); |
||||
} |
||||
close(fd); |
||||
|
||||
*host_endian = 0; |
||||
|
||||
return (len); |
||||
} |
||||
|
||||
int |
||||
obsd_get_configuration(struct libusb_device_handle *handle, int *config) |
||||
{ |
||||
struct device_priv *dpriv = (struct device_priv *)handle->dev->os_priv; |
||||
usb_config_descriptor_t *ucd = (usb_config_descriptor_t *)dpriv->cdesc; |
||||
|
||||
*config = ucd->bConfigurationValue; |
||||
|
||||
usbi_dbg("bConfigurationValue %d", *config); |
||||
|
||||
return (LIBUSB_SUCCESS); |
||||
} |
||||
|
||||
int |
||||
obsd_set_configuration(struct libusb_device_handle *handle, int config) |
||||
{ |
||||
struct device_priv *dpriv = (struct device_priv *)handle->dev->os_priv; |
||||
|
||||
if (dpriv->devname == NULL) |
||||
return (LIBUSB_ERROR_NOT_SUPPORTED); |
||||
|
||||
usbi_dbg("bConfigurationValue %d", config); |
||||
|
||||
if (ioctl(dpriv->fd, USB_SET_CONFIG, &config) < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
return _cache_active_config_descriptor(handle->dev); |
||||
} |
||||
|
||||
int |
||||
obsd_claim_interface(struct libusb_device_handle *handle, int iface) |
||||
{ |
||||
struct handle_priv *hpriv = (struct handle_priv *)handle->os_priv; |
||||
int i; |
||||
|
||||
for (i = 0; i < USB_MAX_ENDPOINTS; i++) |
||||
hpriv->endpoints[i] = -1; |
||||
|
||||
return (LIBUSB_SUCCESS); |
||||
} |
||||
|
||||
int |
||||
obsd_release_interface(struct libusb_device_handle *handle, int iface) |
||||
{ |
||||
struct handle_priv *hpriv = (struct handle_priv *)handle->os_priv; |
||||
int i; |
||||
|
||||
for (i = 0; i < USB_MAX_ENDPOINTS; i++) |
||||
if (hpriv->endpoints[i] >= 0) |
||||
close(hpriv->endpoints[i]); |
||||
|
||||
return (LIBUSB_SUCCESS); |
||||
} |
||||
|
||||
int |
||||
obsd_set_interface_altsetting(struct libusb_device_handle *handle, int iface, |
||||
int altsetting) |
||||
{ |
||||
struct device_priv *dpriv = (struct device_priv *)handle->dev->os_priv; |
||||
struct usb_alt_interface intf; |
||||
|
||||
if (dpriv->devname == NULL) |
||||
return (LIBUSB_ERROR_NOT_SUPPORTED); |
||||
|
||||
usbi_dbg("iface %d, setting %d", iface, altsetting); |
||||
|
||||
memset(&intf, 0, sizeof(intf)); |
||||
|
||||
intf.uai_interface_index = iface; |
||||
intf.uai_alt_no = altsetting; |
||||
|
||||
if (ioctl(dpriv->fd, USB_SET_ALTINTERFACE, &intf) < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
return (LIBUSB_SUCCESS); |
||||
} |
||||
|
||||
int |
||||
obsd_clear_halt(struct libusb_device_handle *handle, unsigned char endpoint) |
||||
{ |
||||
struct usb_ctl_request req; |
||||
int fd, err; |
||||
|
||||
if ((fd = _bus_open(handle->dev->bus_number)) < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
usbi_dbg(""); |
||||
|
||||
req.ucr_addr = handle->dev->device_address; |
||||
req.ucr_request.bmRequestType = UT_WRITE_ENDPOINT; |
||||
req.ucr_request.bRequest = UR_CLEAR_FEATURE; |
||||
USETW(req.ucr_request.wValue, UF_ENDPOINT_HALT); |
||||
USETW(req.ucr_request.wIndex, endpoint); |
||||
USETW(req.ucr_request.wLength, 0); |
||||
|
||||
if (ioctl(fd, USB_REQUEST, &req) < 0) { |
||||
err = errno; |
||||
close(fd); |
||||
return _errno_to_libusb(err); |
||||
} |
||||
close(fd); |
||||
|
||||
return (LIBUSB_SUCCESS); |
||||
} |
||||
|
||||
int |
||||
obsd_reset_device(struct libusb_device_handle *handle) |
||||
{ |
||||
usbi_dbg(""); |
||||
|
||||
return (LIBUSB_ERROR_NOT_SUPPORTED); |
||||
} |
||||
|
||||
void |
||||
obsd_destroy_device(struct libusb_device *dev) |
||||
{ |
||||
struct device_priv *dpriv = (struct device_priv *)dev->os_priv; |
||||
|
||||
usbi_dbg(""); |
||||
|
||||
free(dpriv->cdesc); |
||||
free(dpriv->devname); |
||||
} |
||||
|
||||
int |
||||
obsd_submit_transfer(struct usbi_transfer *itransfer) |
||||
{ |
||||
struct libusb_transfer *transfer; |
||||
struct handle_priv *hpriv; |
||||
int err = 0; |
||||
|
||||
usbi_dbg(""); |
||||
|
||||
transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); |
||||
hpriv = (struct handle_priv *)transfer->dev_handle->os_priv; |
||||
|
||||
switch (transfer->type) { |
||||
case LIBUSB_TRANSFER_TYPE_CONTROL: |
||||
err = _sync_control_transfer(itransfer); |
||||
break; |
||||
case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: |
||||
if (IS_XFEROUT(transfer)) { |
||||
/* Isochronous write is not supported */ |
||||
err = LIBUSB_ERROR_NOT_SUPPORTED; |
||||
break; |
||||
} |
||||
err = _sync_gen_transfer(itransfer); |
||||
break; |
||||
case LIBUSB_TRANSFER_TYPE_BULK: |
||||
case LIBUSB_TRANSFER_TYPE_INTERRUPT: |
||||
if (IS_XFEROUT(transfer) && |
||||
transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET) { |
||||
err = LIBUSB_ERROR_NOT_SUPPORTED; |
||||
break; |
||||
} |
||||
err = _sync_gen_transfer(itransfer); |
||||
break; |
||||
case LIBUSB_TRANSFER_TYPE_BULK_STREAM: |
||||
err = LIBUSB_ERROR_NOT_SUPPORTED; |
||||
break; |
||||
} |
||||
|
||||
if (err) |
||||
return (err); |
||||
|
||||
usbi_signal_transfer_completion(itransfer); |
||||
|
||||
return (LIBUSB_SUCCESS); |
||||
} |
||||
|
||||
int |
||||
obsd_cancel_transfer(struct usbi_transfer *itransfer) |
||||
{ |
||||
usbi_dbg(""); |
||||
|
||||
return (LIBUSB_ERROR_NOT_SUPPORTED); |
||||
} |
||||
|
||||
void |
||||
obsd_clear_transfer_priv(struct usbi_transfer *itransfer) |
||||
{ |
||||
usbi_dbg(""); |
||||
|
||||
/* Nothing to do */ |
||||
} |
||||
|
||||
int |
||||
obsd_handle_transfer_completion(struct usbi_transfer *itransfer) |
||||
{ |
||||
return usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_COMPLETED); |
||||
} |
||||
|
||||
int |
||||
obsd_clock_gettime(int clkid, struct timespec *tp) |
||||
{ |
||||
usbi_dbg("clock %d", clkid); |
||||
|
||||
if (clkid == USBI_CLOCK_REALTIME) |
||||
return clock_gettime(CLOCK_REALTIME, tp); |
||||
|
||||
if (clkid == USBI_CLOCK_MONOTONIC) |
||||
return clock_gettime(CLOCK_MONOTONIC, tp); |
||||
|
||||
return (LIBUSB_ERROR_INVALID_PARAM); |
||||
} |
||||
|
||||
int |
||||
_errno_to_libusb(int err) |
||||
{ |
||||
usbi_dbg("error: %s (%d)", strerror(err), err); |
||||
|
||||
switch (err) { |
||||
case EIO: |
||||
return (LIBUSB_ERROR_IO); |
||||
case EACCES: |
||||
return (LIBUSB_ERROR_ACCESS); |
||||
case ENOENT: |
||||
return (LIBUSB_ERROR_NO_DEVICE); |
||||
case ENOMEM: |
||||
return (LIBUSB_ERROR_NO_MEM); |
||||
case ETIMEDOUT: |
||||
return (LIBUSB_ERROR_TIMEOUT); |
||||
} |
||||
|
||||
return (LIBUSB_ERROR_OTHER); |
||||
} |
||||
|
||||
int |
||||
_cache_active_config_descriptor(struct libusb_device *dev) |
||||
{ |
||||
struct device_priv *dpriv = (struct device_priv *)dev->os_priv; |
||||
struct usb_device_cdesc udc; |
||||
struct usb_device_fdesc udf; |
||||
unsigned char* buf; |
||||
int fd, len, err; |
||||
|
||||
if ((fd = _bus_open(dev->bus_number)) < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
usbi_dbg("fd %d, addr %d", fd, dev->device_address); |
||||
|
||||
udc.udc_bus = dev->bus_number; |
||||
udc.udc_addr = dev->device_address; |
||||
udc.udc_config_index = USB_CURRENT_CONFIG_INDEX; |
||||
if (ioctl(fd, USB_DEVICE_GET_CDESC, &udc) < 0) { |
||||
err = errno; |
||||
close(fd); |
||||
return _errno_to_libusb(errno); |
||||
} |
||||
|
||||
usbi_dbg("active bLength %d", udc.udc_desc.bLength); |
||||
|
||||
len = UGETW(udc.udc_desc.wTotalLength); |
||||
buf = malloc(len); |
||||
if (buf == NULL) |
||||
return (LIBUSB_ERROR_NO_MEM); |
||||
|
||||
udf.udf_bus = dev->bus_number; |
||||
udf.udf_addr = dev->device_address; |
||||
udf.udf_config_index = udc.udc_config_index; |
||||
udf.udf_size = len; |
||||
udf.udf_data = buf; |
||||
|
||||
usbi_dbg("index %d, len %d", udf.udf_config_index, len); |
||||
|
||||
if (ioctl(fd, USB_DEVICE_GET_FDESC, &udf) < 0) { |
||||
err = errno; |
||||
close(fd); |
||||
free(buf); |
||||
return _errno_to_libusb(err); |
||||
} |
||||
close(fd); |
||||
|
||||
if (dpriv->cdesc) |
||||
free(dpriv->cdesc); |
||||
dpriv->cdesc = buf; |
||||
|
||||
return (LIBUSB_SUCCESS); |
||||
} |
||||
|
||||
int |
||||
_sync_control_transfer(struct usbi_transfer *itransfer) |
||||
{ |
||||
struct libusb_transfer *transfer; |
||||
struct libusb_control_setup *setup; |
||||
struct device_priv *dpriv; |
||||
struct usb_ctl_request req; |
||||
|
||||
transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); |
||||
dpriv = (struct device_priv *)transfer->dev_handle->dev->os_priv; |
||||
setup = (struct libusb_control_setup *)transfer->buffer; |
||||
|
||||
usbi_dbg("type %x request %x value %x index %d length %d timeout %d", |
||||
setup->bmRequestType, setup->bRequest, |
||||
libusb_le16_to_cpu(setup->wValue), |
||||
libusb_le16_to_cpu(setup->wIndex), |
||||
libusb_le16_to_cpu(setup->wLength), transfer->timeout); |
||||
|
||||
req.ucr_addr = transfer->dev_handle->dev->device_address; |
||||
req.ucr_request.bmRequestType = setup->bmRequestType; |
||||
req.ucr_request.bRequest = setup->bRequest; |
||||
/* Don't use USETW, libusb already deals with the endianness */ |
||||
(*(uint16_t *)req.ucr_request.wValue) = setup->wValue; |
||||
(*(uint16_t *)req.ucr_request.wIndex) = setup->wIndex; |
||||
(*(uint16_t *)req.ucr_request.wLength) = setup->wLength; |
||||
req.ucr_data = transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE; |
||||
|
||||
if ((transfer->flags & LIBUSB_TRANSFER_SHORT_NOT_OK) == 0) |
||||
req.ucr_flags = USBD_SHORT_XFER_OK; |
||||
|
||||
if (dpriv->devname == NULL) { |
||||
/*
|
||||
* XXX If the device is not attached to ugen(4) it is |
||||
* XXX still possible to submit a control transfer but |
||||
* XXX with the default timeout only. |
||||
*/ |
||||
int fd, err; |
||||
|
||||
if ((fd = _bus_open(transfer->dev_handle->dev->bus_number)) < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
if ((ioctl(fd, USB_REQUEST, &req)) < 0) { |
||||
err = errno; |
||||
close(fd); |
||||
return _errno_to_libusb(err); |
||||
} |
||||
close(fd); |
||||
} else { |
||||
if ((ioctl(dpriv->fd, USB_SET_TIMEOUT, &transfer->timeout)) < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
if ((ioctl(dpriv->fd, USB_DO_REQUEST, &req)) < 0) |
||||
return _errno_to_libusb(errno); |
||||
} |
||||
|
||||
itransfer->transferred = req.ucr_actlen; |
||||
|
||||
usbi_dbg("transferred %d", itransfer->transferred); |
||||
|
||||
return (0); |
||||
} |
||||
|
||||
int |
||||
_access_endpoint(struct libusb_transfer *transfer) |
||||
{ |
||||
struct handle_priv *hpriv; |
||||
struct device_priv *dpriv; |
||||
char devnode[16]; |
||||
int fd, endpt; |
||||
mode_t mode; |
||||
|
||||
hpriv = (struct handle_priv *)transfer->dev_handle->os_priv; |
||||
dpriv = (struct device_priv *)transfer->dev_handle->dev->os_priv; |
||||
|
||||
endpt = UE_GET_ADDR(transfer->endpoint); |
||||
mode = IS_XFERIN(transfer) ? O_RDONLY : O_WRONLY; |
||||
|
||||
usbi_dbg("endpoint %d mode %d", endpt, mode); |
||||
|
||||
if (hpriv->endpoints[endpt] < 0) { |
||||
/* Pick the right endpoint node */ |
||||
snprintf(devnode, sizeof(devnode), DEVPATH "%s.%02d", |
||||
dpriv->devname, endpt); |
||||
|
||||
/* We may need to read/write to the same endpoint later. */ |
||||
if (((fd = open(devnode, O_RDWR)) < 0) && (errno == ENXIO)) |
||||
if ((fd = open(devnode, mode)) < 0) |
||||
return (-1); |
||||
|
||||
hpriv->endpoints[endpt] = fd; |
||||
} |
||||
|
||||
return (hpriv->endpoints[endpt]); |
||||
} |
||||
|
||||
int |
||||
_sync_gen_transfer(struct usbi_transfer *itransfer) |
||||
{ |
||||
struct libusb_transfer *transfer; |
||||
struct device_priv *dpriv; |
||||
int fd, nr = 1; |
||||
|
||||
transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); |
||||
dpriv = (struct device_priv *)transfer->dev_handle->dev->os_priv; |
||||
|
||||
if (dpriv->devname == NULL) |
||||
return (LIBUSB_ERROR_NOT_SUPPORTED); |
||||
|
||||
/*
|
||||
* Bulk, Interrupt or Isochronous transfer depends on the |
||||
* endpoint and thus the node to open. |
||||
*/ |
||||
if ((fd = _access_endpoint(transfer)) < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
if ((ioctl(fd, USB_SET_TIMEOUT, &transfer->timeout)) < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
if (IS_XFERIN(transfer)) { |
||||
if ((transfer->flags & LIBUSB_TRANSFER_SHORT_NOT_OK) == 0) |
||||
if ((ioctl(fd, USB_SET_SHORT_XFER, &nr)) < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
nr = read(fd, transfer->buffer, transfer->length); |
||||
} else { |
||||
nr = write(fd, transfer->buffer, transfer->length); |
||||
} |
||||
|
||||
if (nr < 0) |
||||
return _errno_to_libusb(errno); |
||||
|
||||
itransfer->transferred = nr; |
||||
|
||||
return (0); |
||||
} |
||||
|
||||
int |
||||
_bus_open(int number) |
||||
{ |
||||
char busnode[16]; |
||||
|
||||
snprintf(busnode, sizeof(busnode), USBDEV "%d", number); |
||||
|
||||
return open(busnode, O_RDWR); |
||||
} |
@ -0,0 +1,53 @@ |
||||
/*
|
||||
* poll_posix: poll compatibility wrapper for POSIX systems |
||||
* Copyright © 2013 RealVNC Ltd. |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU Lesser General Public |
||||
* License as published by the Free Software Foundation; either |
||||
* version 2.1 of the License, or (at your option) any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
* Lesser General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Lesser General Public |
||||
* License along with this library; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
* |
||||
*/ |
||||
|
||||
#include <config.h> |
||||
|
||||
#include <unistd.h> |
||||
#include <fcntl.h> |
||||
#include <errno.h> |
||||
#include <stdlib.h> |
||||
|
||||
#include "libusbi.h" |
||||
|
||||
int usbi_pipe(int pipefd[2]) |
||||
{ |
||||
int ret = pipe(pipefd); |
||||
if (ret != 0) { |
||||
return ret; |
||||
} |
||||
ret = fcntl(pipefd[1], F_GETFL); |
||||
if (ret == -1) { |
||||
usbi_dbg("Failed to get pipe fd flags: %d", errno); |
||||
goto err_close_pipe; |
||||
} |
||||
ret = fcntl(pipefd[1], F_SETFL, ret | O_NONBLOCK); |
||||
if (ret != 0) { |
||||
usbi_dbg("Failed to set non-blocking on new pipe: %d", errno); |
||||
goto err_close_pipe; |
||||
} |
||||
|
||||
return 0; |
||||
|
||||
err_close_pipe: |
||||
usbi_close(pipefd[0]); |
||||
usbi_close(pipefd[1]); |
||||
return ret; |
||||
} |
@ -0,0 +1,11 @@ |
||||
#ifndef LIBUSB_POLL_POSIX_H |
||||
#define LIBUSB_POLL_POSIX_H |
||||
|
||||
#define usbi_write write |
||||
#define usbi_read read |
||||
#define usbi_close close |
||||
#define usbi_poll poll |
||||
|
||||
int usbi_pipe(int pipefd[2]); |
||||
|
||||
#endif /* LIBUSB_POLL_POSIX_H */ |
@ -0,0 +1,728 @@ |
||||
/*
|
||||
* poll_windows: poll compatibility wrapper for Windows |
||||
* Copyright © 2012-2013 RealVNC Ltd. |
||||
* Copyright © 2009-2010 Pete Batard <pete@akeo.ie> |
||||
* With contributions from Michael Plante, Orin Eman et al. |
||||
* Parts of poll implementation from libusb-win32, by Stephan Meyer et al. |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU Lesser General Public |
||||
* License as published by the Free Software Foundation; either |
||||
* version 2.1 of the License, or (at your option) any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
* Lesser General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Lesser General Public |
||||
* License along with this library; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
* |
||||
*/ |
||||
|
||||
/*
|
||||
* poll() and pipe() Windows compatibility layer for libusb 1.0 |
||||
* |
||||
* The way this layer works is by using OVERLAPPED with async I/O transfers, as |
||||
* OVERLAPPED have an associated event which is flagged for I/O completion. |
||||
* |
||||
* For USB pollable async I/O, you would typically: |
||||
* - obtain a Windows HANDLE to a file or device that has been opened in |
||||
* OVERLAPPED mode |
||||
* - call usbi_create_fd with this handle to obtain a custom fd. |
||||
* Note that if you need simultaneous R/W access, you need to call create_fd |
||||
* twice, once in RW_READ and once in RW_WRITE mode to obtain 2 separate |
||||
* pollable fds |
||||
* - leave the core functions call the poll routine and flag POLLIN/POLLOUT |
||||
* |
||||
* The pipe pollable synchronous I/O works using the overlapped event associated |
||||
* with a fake pipe. The read/write functions are only meant to be used in that |
||||
* context. |
||||
*/ |
||||
#include <config.h> |
||||
|
||||
#include <errno.h> |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
|
||||
#include "libusbi.h" |
||||
|
||||
// Uncomment to debug the polling layer
|
||||
//#define DEBUG_POLL_WINDOWS
|
||||
#if defined(DEBUG_POLL_WINDOWS) |
||||
#define poll_dbg usbi_dbg |
||||
#else |
||||
// MSVC++ < 2005 cannot use a variadic argument and non MSVC
|
||||
// compilers produce warnings if parenthesis are omitted.
|
||||
#if defined(_MSC_VER) && (_MSC_VER < 1400) |
||||
#define poll_dbg |
||||
#else |
||||
#define poll_dbg(...) |
||||
#endif |
||||
#endif |
||||
|
||||
#if defined(_PREFAST_) |
||||
#pragma warning(disable:28719) |
||||
#endif |
||||
|
||||
#define CHECK_INIT_POLLING do {if(!is_polling_set) init_polling();} while(0) |
||||
|
||||
// public fd data
|
||||
const struct winfd INVALID_WINFD = {-1, INVALID_HANDLE_VALUE, NULL, NULL, NULL, RW_NONE}; |
||||
struct winfd poll_fd[MAX_FDS]; |
||||
// internal fd data
|
||||
struct { |
||||
CRITICAL_SECTION mutex; // lock for fds
|
||||
// Additional variables for XP CancelIoEx partial emulation
|
||||
HANDLE original_handle; |
||||
DWORD thread_id; |
||||
} _poll_fd[MAX_FDS]; |
||||
|
||||
// globals
|
||||
BOOLEAN is_polling_set = FALSE; |
||||
LONG pipe_number = 0; |
||||
static volatile LONG compat_spinlock = 0; |
||||
|
||||
#if !defined(_WIN32_WCE) |
||||
// CancelIoEx, available on Vista and later only, provides the ability to cancel
|
||||
// a single transfer (OVERLAPPED) when used. As it may not be part of any of the
|
||||
// platform headers, we hook into the Kernel32 system DLL directly to seek it.
|
||||
static BOOL (__stdcall *pCancelIoEx)(HANDLE, LPOVERLAPPED) = NULL; |
||||
#define Use_Duplicate_Handles (pCancelIoEx == NULL) |
||||
|
||||
static inline void setup_cancel_io(void) |
||||
{ |
||||
HMODULE hKernel32 = GetModuleHandleA("KERNEL32"); |
||||
if (hKernel32 != NULL) { |
||||
pCancelIoEx = (BOOL (__stdcall *)(HANDLE,LPOVERLAPPED)) |
||||
GetProcAddress(hKernel32, "CancelIoEx"); |
||||
} |
||||
usbi_dbg("Will use CancelIo%s for I/O cancellation", |
||||
Use_Duplicate_Handles?"":"Ex"); |
||||
} |
||||
|
||||
static inline BOOL cancel_io(int _index) |
||||
{ |
||||
if ((_index < 0) || (_index >= MAX_FDS)) { |
||||
return FALSE; |
||||
} |
||||
|
||||
if ( (poll_fd[_index].fd < 0) || (poll_fd[_index].handle == INVALID_HANDLE_VALUE) |
||||
|| (poll_fd[_index].handle == 0) || (poll_fd[_index].overlapped == NULL) ) { |
||||
return TRUE; |
||||
} |
||||
if (poll_fd[_index].itransfer && poll_fd[_index].cancel_fn) { |
||||
// Cancel outstanding transfer via the specific callback
|
||||
(*poll_fd[_index].cancel_fn)(poll_fd[_index].itransfer); |
||||
return TRUE; |
||||
} |
||||
if (pCancelIoEx != NULL) { |
||||
return (*pCancelIoEx)(poll_fd[_index].handle, poll_fd[_index].overlapped); |
||||
} |
||||
if (_poll_fd[_index].thread_id == GetCurrentThreadId()) { |
||||
return CancelIo(poll_fd[_index].handle); |
||||
} |
||||
usbi_warn(NULL, "Unable to cancel I/O that was started from another thread"); |
||||
return FALSE; |
||||
} |
||||
#else |
||||
#define Use_Duplicate_Handles FALSE |
||||
|
||||
static __inline void setup_cancel_io() |
||||
{ |
||||
// No setup needed on WinCE
|
||||
} |
||||
|
||||
static __inline BOOL cancel_io(int _index) |
||||
{ |
||||
if ((_index < 0) || (_index >= MAX_FDS)) { |
||||
return FALSE; |
||||
} |
||||
if ( (poll_fd[_index].fd < 0) || (poll_fd[_index].handle == INVALID_HANDLE_VALUE) |
||||
|| (poll_fd[_index].handle == 0) || (poll_fd[_index].overlapped == NULL) ) { |
||||
return TRUE; |
||||
} |
||||
if (poll_fd[_index].itransfer && poll_fd[_index].cancel_fn) { |
||||
// Cancel outstanding transfer via the specific callback
|
||||
(*poll_fd[_index].cancel_fn)(poll_fd[_index].itransfer); |
||||
} |
||||
return TRUE; |
||||
} |
||||
#endif |
||||
|
||||
// Init
|
||||
void init_polling(void) |
||||
{ |
||||
int i; |
||||
|
||||
while (InterlockedExchange((LONG *)&compat_spinlock, 1) == 1) { |
||||
SleepEx(0, TRUE); |
||||
} |
||||
if (!is_polling_set) { |
||||
setup_cancel_io(); |
||||
for (i=0; i<MAX_FDS; i++) { |
||||
poll_fd[i] = INVALID_WINFD; |
||||
_poll_fd[i].original_handle = INVALID_HANDLE_VALUE; |
||||
_poll_fd[i].thread_id = 0; |
||||
InitializeCriticalSection(&_poll_fd[i].mutex); |
||||
} |
||||
is_polling_set = TRUE; |
||||
} |
||||
InterlockedExchange((LONG *)&compat_spinlock, 0); |
||||
} |
||||
|
||||
// Internal function to retrieve the table index (and lock the fd mutex)
|
||||
static int _fd_to_index_and_lock(int fd) |
||||
{ |
||||
int i; |
||||
|
||||
if (fd < 0) |
||||
return -1; |
||||
|
||||
for (i=0; i<MAX_FDS; i++) { |
||||
if (poll_fd[i].fd == fd) { |
||||
EnterCriticalSection(&_poll_fd[i].mutex); |
||||
// fd might have changed before we got to critical
|
||||
if (poll_fd[i].fd != fd) { |
||||
LeaveCriticalSection(&_poll_fd[i].mutex); |
||||
continue; |
||||
} |
||||
return i; |
||||
} |
||||
} |
||||
return -1; |
||||
} |
||||
|
||||
static OVERLAPPED *create_overlapped(void) |
||||
{ |
||||
OVERLAPPED *overlapped = (OVERLAPPED*) calloc(1, sizeof(OVERLAPPED)); |
||||
if (overlapped == NULL) { |
||||
return NULL; |
||||
} |
||||
overlapped->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); |
||||
if(overlapped->hEvent == NULL) { |
||||
free (overlapped); |
||||
return NULL; |
||||
} |
||||
return overlapped; |
||||
} |
||||
|
||||
static void free_overlapped(OVERLAPPED *overlapped) |
||||
{ |
||||
if (overlapped == NULL) |
||||
return; |
||||
|
||||
if ( (overlapped->hEvent != 0) |
||||
&& (overlapped->hEvent != INVALID_HANDLE_VALUE) ) { |
||||
CloseHandle(overlapped->hEvent); |
||||
} |
||||
free(overlapped); |
||||
} |
||||
|
||||
void exit_polling(void) |
||||
{ |
||||
int i; |
||||
|
||||
while (InterlockedExchange((LONG *)&compat_spinlock, 1) == 1) { |
||||
SleepEx(0, TRUE); |
||||
} |
||||
if (is_polling_set) { |
||||
is_polling_set = FALSE; |
||||
|
||||
for (i=0; i<MAX_FDS; i++) { |
||||
// Cancel any async I/O (handle can be invalid)
|
||||
cancel_io(i); |
||||
// If anything was pending on that I/O, it should be
|
||||
// terminating, and we should be able to access the fd
|
||||
// mutex lock before too long
|
||||
EnterCriticalSection(&_poll_fd[i].mutex); |
||||
free_overlapped(poll_fd[i].overlapped); |
||||
if (Use_Duplicate_Handles) { |
||||
// Close duplicate handle
|
||||
if (_poll_fd[i].original_handle != INVALID_HANDLE_VALUE) { |
||||
CloseHandle(poll_fd[i].handle); |
||||
} |
||||
} |
||||
poll_fd[i] = INVALID_WINFD; |
||||
LeaveCriticalSection(&_poll_fd[i].mutex); |
||||
DeleteCriticalSection(&_poll_fd[i].mutex); |
||||
} |
||||
} |
||||
InterlockedExchange((LONG *)&compat_spinlock, 0); |
||||
} |
||||
|
||||
/*
|
||||
* Create a fake pipe. |
||||
* As libusb only uses pipes for signaling, all we need from a pipe is an |
||||
* event. To that extent, we create a single wfd and overlapped as a means |
||||
* to access that event. |
||||
*/ |
||||
int usbi_pipe(int filedes[2]) |
||||
{ |
||||
int i; |
||||
OVERLAPPED* overlapped; |
||||
|
||||
CHECK_INIT_POLLING; |
||||
|
||||
overlapped = create_overlapped(); |
||||
|
||||
if (overlapped == NULL) { |
||||
return -1; |
||||
} |
||||
// The overlapped must have status pending for signaling to work in poll
|
||||
overlapped->Internal = STATUS_PENDING; |
||||
overlapped->InternalHigh = 0; |
||||
|
||||
for (i=0; i<MAX_FDS; i++) { |
||||
if (poll_fd[i].fd < 0) { |
||||
EnterCriticalSection(&_poll_fd[i].mutex); |
||||
// fd might have been allocated before we got to critical
|
||||
if (poll_fd[i].fd >= 0) { |
||||
LeaveCriticalSection(&_poll_fd[i].mutex); |
||||
continue; |
||||
} |
||||
|
||||
// Use index as the unique fd number
|
||||
poll_fd[i].fd = i; |
||||
// Read end of the "pipe"
|
||||
filedes[0] = poll_fd[i].fd; |
||||
// We can use the same handle for both ends
|
||||
filedes[1] = filedes[0]; |
||||
|
||||
poll_fd[i].handle = DUMMY_HANDLE; |
||||
poll_fd[i].overlapped = overlapped; |
||||
// There's no polling on the write end, so we just use READ for our needs
|
||||
poll_fd[i].rw = RW_READ; |
||||
_poll_fd[i].original_handle = INVALID_HANDLE_VALUE; |
||||
LeaveCriticalSection(&_poll_fd[i].mutex); |
||||
return 0; |
||||
} |
||||
} |
||||
free_overlapped(overlapped); |
||||
return -1; |
||||
} |
||||
|
||||
/*
|
||||
* Create both an fd and an OVERLAPPED from an open Windows handle, so that |
||||
* it can be used with our polling function |
||||
* The handle MUST support overlapped transfers (usually requires CreateFile |
||||
* with FILE_FLAG_OVERLAPPED) |
||||
* Return a pollable file descriptor struct, or INVALID_WINFD on error |
||||
* |
||||
* Note that the fd returned by this function is a per-transfer fd, rather |
||||
* than a per-session fd and cannot be used for anything else but our |
||||
* custom functions (the fd itself points to the NUL: device) |
||||
* if you plan to do R/W on the same handle, you MUST create 2 fds: one for |
||||
* read and one for write. Using a single R/W fd is unsupported and will |
||||
* produce unexpected results |
||||
*/ |
||||
struct winfd usbi_create_fd(HANDLE handle, int access_mode, struct usbi_transfer *itransfer, cancel_transfer *cancel_fn) |
||||
{ |
||||
int i; |
||||
struct winfd wfd = INVALID_WINFD; |
||||
OVERLAPPED* overlapped = NULL; |
||||
|
||||
CHECK_INIT_POLLING; |
||||
|
||||
if ((handle == 0) || (handle == INVALID_HANDLE_VALUE)) { |
||||
return INVALID_WINFD; |
||||
} |
||||
|
||||
wfd.itransfer = itransfer; |
||||
wfd.cancel_fn = cancel_fn; |
||||
|
||||
if ((access_mode != RW_READ) && (access_mode != RW_WRITE)) { |
||||
usbi_warn(NULL, "only one of RW_READ or RW_WRITE are supported. " |
||||
"If you want to poll for R/W simultaneously, create multiple fds from the same handle."); |
||||
return INVALID_WINFD; |
||||
} |
||||
if (access_mode == RW_READ) { |
||||
wfd.rw = RW_READ; |
||||
} else { |
||||
wfd.rw = RW_WRITE; |
||||
} |
||||
|
||||
overlapped = create_overlapped(); |
||||
if(overlapped == NULL) { |
||||
return INVALID_WINFD; |
||||
} |
||||
|
||||
for (i=0; i<MAX_FDS; i++) { |
||||
if (poll_fd[i].fd < 0) { |
||||
EnterCriticalSection(&_poll_fd[i].mutex); |
||||
// fd might have been removed before we got to critical
|
||||
if (poll_fd[i].fd >= 0) { |
||||
LeaveCriticalSection(&_poll_fd[i].mutex); |
||||
continue; |
||||
} |
||||
// Use index as the unique fd number
|
||||
wfd.fd = i; |
||||
// Attempt to emulate some of the CancelIoEx behaviour on platforms
|
||||
// that don't have it
|
||||
if (Use_Duplicate_Handles) { |
||||
_poll_fd[i].thread_id = GetCurrentThreadId(); |
||||
if (!DuplicateHandle(GetCurrentProcess(), handle, GetCurrentProcess(), |
||||
&wfd.handle, 0, TRUE, DUPLICATE_SAME_ACCESS)) { |
||||
usbi_dbg("could not duplicate handle for CancelIo - using original one"); |
||||
wfd.handle = handle; |
||||
// Make sure we won't close the original handle on fd deletion then
|
||||
_poll_fd[i].original_handle = INVALID_HANDLE_VALUE; |
||||
} else { |
||||
_poll_fd[i].original_handle = handle; |
||||
} |
||||
} else { |
||||
wfd.handle = handle; |
||||
} |
||||
wfd.overlapped = overlapped; |
||||
memcpy(&poll_fd[i], &wfd, sizeof(struct winfd)); |
||||
LeaveCriticalSection(&_poll_fd[i].mutex); |
||||
return wfd; |
||||
} |
||||
} |
||||
free_overlapped(overlapped); |
||||
return INVALID_WINFD; |
||||
} |
||||
|
||||
static void _free_index(int _index) |
||||
{ |
||||
// Cancel any async IO (Don't care about the validity of our handles for this)
|
||||
cancel_io(_index); |
||||
// close the duplicate handle (if we have an actual duplicate)
|
||||
if (Use_Duplicate_Handles) { |
||||
if (_poll_fd[_index].original_handle != INVALID_HANDLE_VALUE) { |
||||
CloseHandle(poll_fd[_index].handle); |
||||
} |
||||
_poll_fd[_index].original_handle = INVALID_HANDLE_VALUE; |
||||
_poll_fd[_index].thread_id = 0; |
||||
} |
||||
free_overlapped(poll_fd[_index].overlapped); |
||||
poll_fd[_index] = INVALID_WINFD; |
||||
} |
||||
|
||||
/*
|
||||
* Release a pollable file descriptor. |
||||
* |
||||
* Note that the associated Windows handle is not closed by this call |
||||
*/ |
||||
void usbi_free_fd(struct winfd *wfd) |
||||
{ |
||||
int _index; |
||||
|
||||
CHECK_INIT_POLLING; |
||||
|
||||
_index = _fd_to_index_and_lock(wfd->fd); |
||||
if (_index < 0) { |
||||
return; |
||||
} |
||||
_free_index(_index); |
||||
*wfd = INVALID_WINFD; |
||||
LeaveCriticalSection(&_poll_fd[_index].mutex); |
||||
} |
||||
|
||||
/*
|
||||
* The functions below perform various conversions between fd, handle and OVERLAPPED |
||||
*/ |
||||
struct winfd fd_to_winfd(int fd) |
||||
{ |
||||
int i; |
||||
struct winfd wfd; |
||||
|
||||
CHECK_INIT_POLLING; |
||||
|
||||
if (fd < 0) |
||||
return INVALID_WINFD; |
||||
|
||||
for (i=0; i<MAX_FDS; i++) { |
||||
if (poll_fd[i].fd == fd) { |
||||
EnterCriticalSection(&_poll_fd[i].mutex); |
||||
// fd might have been deleted before we got to critical
|
||||
if (poll_fd[i].fd != fd) { |
||||
LeaveCriticalSection(&_poll_fd[i].mutex); |
||||
continue; |
||||
} |
||||
memcpy(&wfd, &poll_fd[i], sizeof(struct winfd)); |
||||
LeaveCriticalSection(&_poll_fd[i].mutex); |
||||
return wfd; |
||||
} |
||||
} |
||||
return INVALID_WINFD; |
||||
} |
||||
|
||||
struct winfd handle_to_winfd(HANDLE handle) |
||||
{ |
||||
int i; |
||||
struct winfd wfd; |
||||
|
||||
CHECK_INIT_POLLING; |
||||
|
||||
if ((handle == 0) || (handle == INVALID_HANDLE_VALUE)) |
||||
return INVALID_WINFD; |
||||
|
||||
for (i=0; i<MAX_FDS; i++) { |
||||
if (poll_fd[i].handle == handle) { |
||||
EnterCriticalSection(&_poll_fd[i].mutex); |
||||
// fd might have been deleted before we got to critical
|
||||
if (poll_fd[i].handle != handle) { |
||||
LeaveCriticalSection(&_poll_fd[i].mutex); |
||||
continue; |
||||
} |
||||
memcpy(&wfd, &poll_fd[i], sizeof(struct winfd)); |
||||
LeaveCriticalSection(&_poll_fd[i].mutex); |
||||
return wfd; |
||||
} |
||||
} |
||||
return INVALID_WINFD; |
||||
} |
||||
|
||||
struct winfd overlapped_to_winfd(OVERLAPPED* overlapped) |
||||
{ |
||||
int i; |
||||
struct winfd wfd; |
||||
|
||||
CHECK_INIT_POLLING; |
||||
|
||||
if (overlapped == NULL) |
||||
return INVALID_WINFD; |
||||
|
||||
for (i=0; i<MAX_FDS; i++) { |
||||
if (poll_fd[i].overlapped == overlapped) { |
||||
EnterCriticalSection(&_poll_fd[i].mutex); |
||||
// fd might have been deleted before we got to critical
|
||||
if (poll_fd[i].overlapped != overlapped) { |
||||
LeaveCriticalSection(&_poll_fd[i].mutex); |
||||
continue; |
||||
} |
||||
memcpy(&wfd, &poll_fd[i], sizeof(struct winfd)); |
||||
LeaveCriticalSection(&_poll_fd[i].mutex); |
||||
return wfd; |
||||
} |
||||
} |
||||
return INVALID_WINFD; |
||||
} |
||||
|
||||
/*
|
||||
* POSIX poll equivalent, using Windows OVERLAPPED |
||||
* Currently, this function only accepts one of POLLIN or POLLOUT per fd |
||||
* (but you can create multiple fds from the same handle for read and write) |
||||
*/ |
||||
int usbi_poll(struct pollfd *fds, unsigned int nfds, int timeout) |
||||
{ |
||||
unsigned i; |
||||
int _index, object_index, triggered; |
||||
HANDLE *handles_to_wait_on; |
||||
int *handle_to_index; |
||||
DWORD nb_handles_to_wait_on = 0; |
||||
DWORD ret; |
||||
|
||||
CHECK_INIT_POLLING; |
||||
|
||||
triggered = 0; |
||||
handles_to_wait_on = (HANDLE*) calloc(nfds+1, sizeof(HANDLE)); // +1 for fd_update
|
||||
handle_to_index = (int*) calloc(nfds, sizeof(int)); |
||||
if ((handles_to_wait_on == NULL) || (handle_to_index == NULL)) { |
||||
errno = ENOMEM; |
||||
triggered = -1; |
||||
goto poll_exit; |
||||
} |
||||
|
||||
for (i = 0; i < nfds; ++i) { |
||||
fds[i].revents = 0; |
||||
|
||||
// Only one of POLLIN or POLLOUT can be selected with this version of poll (not both)
|
||||
if ((fds[i].events & ~POLLIN) && (!(fds[i].events & POLLOUT))) { |
||||
fds[i].revents |= POLLERR; |
||||
errno = EACCES; |
||||
usbi_warn(NULL, "unsupported set of events"); |
||||
triggered = -1; |
||||
goto poll_exit; |
||||
} |
||||
|
||||
_index = _fd_to_index_and_lock(fds[i].fd); |
||||
poll_dbg("fd[%d]=%d: (overlapped=%p) got events %04X", i, poll_fd[_index].fd, poll_fd[_index].overlapped, fds[i].events); |
||||
|
||||
if ( (_index < 0) || (poll_fd[_index].handle == INVALID_HANDLE_VALUE) |
||||
|| (poll_fd[_index].handle == 0) || (poll_fd[_index].overlapped == NULL)) { |
||||
fds[i].revents |= POLLNVAL | POLLERR; |
||||
errno = EBADF; |
||||
if (_index >= 0) { |
||||
LeaveCriticalSection(&_poll_fd[_index].mutex); |
||||
} |
||||
usbi_warn(NULL, "invalid fd"); |
||||
triggered = -1; |
||||
goto poll_exit; |
||||
} |
||||
|
||||
// IN or OUT must match our fd direction
|
||||
if ((fds[i].events & POLLIN) && (poll_fd[_index].rw != RW_READ)) { |
||||
fds[i].revents |= POLLNVAL | POLLERR; |
||||
errno = EBADF; |
||||
usbi_warn(NULL, "attempted POLLIN on fd without READ access"); |
||||
LeaveCriticalSection(&_poll_fd[_index].mutex); |
||||
triggered = -1; |
||||
goto poll_exit; |
||||
} |
||||
|
||||
if ((fds[i].events & POLLOUT) && (poll_fd[_index].rw != RW_WRITE)) { |
||||
fds[i].revents |= POLLNVAL | POLLERR; |
||||
errno = EBADF; |
||||
usbi_warn(NULL, "attempted POLLOUT on fd without WRITE access"); |
||||
LeaveCriticalSection(&_poll_fd[_index].mutex); |
||||
triggered = -1; |
||||
goto poll_exit; |
||||
} |
||||
|
||||
// The following macro only works if overlapped I/O was reported pending
|
||||
if ( (HasOverlappedIoCompleted(poll_fd[_index].overlapped)) |
||||
|| (HasOverlappedIoCompletedSync(poll_fd[_index].overlapped)) ) { |
||||
poll_dbg(" completed"); |
||||
// checks above should ensure this works:
|
||||
fds[i].revents = fds[i].events; |
||||
triggered++; |
||||
} else { |
||||
handles_to_wait_on[nb_handles_to_wait_on] = poll_fd[_index].overlapped->hEvent; |
||||
handle_to_index[nb_handles_to_wait_on] = i; |
||||
nb_handles_to_wait_on++; |
||||
} |
||||
LeaveCriticalSection(&_poll_fd[_index].mutex); |
||||
} |
||||
|
||||
// If nothing was triggered, wait on all fds that require it
|
||||
if ((timeout != 0) && (triggered == 0) && (nb_handles_to_wait_on != 0)) { |
||||
if (timeout < 0) { |
||||
poll_dbg("starting infinite wait for %u handles...", (unsigned int)nb_handles_to_wait_on); |
||||
} else { |
||||
poll_dbg("starting %d ms wait for %u handles...", timeout, (unsigned int)nb_handles_to_wait_on); |
||||
} |
||||
ret = WaitForMultipleObjects(nb_handles_to_wait_on, handles_to_wait_on, |
||||
FALSE, (timeout<0)?INFINITE:(DWORD)timeout); |
||||
object_index = ret-WAIT_OBJECT_0; |
||||
if ((object_index >= 0) && ((DWORD)object_index < nb_handles_to_wait_on)) { |
||||
poll_dbg(" completed after wait"); |
||||
i = handle_to_index[object_index]; |
||||
_index = _fd_to_index_and_lock(fds[i].fd); |
||||
fds[i].revents = fds[i].events; |
||||
triggered++; |
||||
if (_index >= 0) { |
||||
LeaveCriticalSection(&_poll_fd[_index].mutex); |
||||
} |
||||
} else if (ret == WAIT_TIMEOUT) { |
||||
poll_dbg(" timed out"); |
||||
triggered = 0; // 0 = timeout
|
||||
} else { |
||||
errno = EIO; |
||||
triggered = -1; // error
|
||||
} |
||||
} |
||||
|
||||
poll_exit: |
||||
if (handles_to_wait_on != NULL) { |
||||
free(handles_to_wait_on); |
||||
} |
||||
if (handle_to_index != NULL) { |
||||
free(handle_to_index); |
||||
} |
||||
return triggered; |
||||
} |
||||
|
||||
/*
|
||||
* close a fake pipe fd |
||||
*/ |
||||
int usbi_close(int fd) |
||||
{ |
||||
int _index; |
||||
int r = -1; |
||||
|
||||
CHECK_INIT_POLLING; |
||||
|
||||
_index = _fd_to_index_and_lock(fd); |
||||
|
||||
if (_index < 0) { |
||||
errno = EBADF; |
||||
} else { |
||||
free_overlapped(poll_fd[_index].overlapped); |
||||
poll_fd[_index] = INVALID_WINFD; |
||||
LeaveCriticalSection(&_poll_fd[_index].mutex); |
||||
} |
||||
return r; |
||||
} |
||||
|
||||
/*
|
||||
* synchronous write for fake "pipe" signaling |
||||
*/ |
||||
ssize_t usbi_write(int fd, const void *buf, size_t count) |
||||
{ |
||||
int _index; |
||||
UNUSED(buf); |
||||
|
||||
CHECK_INIT_POLLING; |
||||
|
||||
if (count != sizeof(unsigned char)) { |
||||
usbi_err(NULL, "this function should only used for signaling"); |
||||
return -1; |
||||
} |
||||
|
||||
_index = _fd_to_index_and_lock(fd); |
||||
|
||||
if ( (_index < 0) || (poll_fd[_index].overlapped == NULL) ) { |
||||
errno = EBADF; |
||||
if (_index >= 0) { |
||||
LeaveCriticalSection(&_poll_fd[_index].mutex); |
||||
} |
||||
return -1; |
||||
} |
||||
|
||||
poll_dbg("set pipe event (fd = %d, thread = %08X)", _index, (unsigned int)GetCurrentThreadId()); |
||||
SetEvent(poll_fd[_index].overlapped->hEvent); |
||||
poll_fd[_index].overlapped->Internal = STATUS_WAIT_0; |
||||
// If two threads write on the pipe at the same time, we need to
|
||||
// process two separate reads => use the overlapped as a counter
|
||||
poll_fd[_index].overlapped->InternalHigh++; |
||||
|
||||
LeaveCriticalSection(&_poll_fd[_index].mutex); |
||||
return sizeof(unsigned char); |
||||
} |
||||
|
||||
/*
|
||||
* synchronous read for fake "pipe" signaling |
||||
*/ |
||||
ssize_t usbi_read(int fd, void *buf, size_t count) |
||||
{ |
||||
int _index; |
||||
ssize_t r = -1; |
||||
UNUSED(buf); |
||||
|
||||
CHECK_INIT_POLLING; |
||||
|
||||
if (count != sizeof(unsigned char)) { |
||||
usbi_err(NULL, "this function should only used for signaling"); |
||||
return -1; |
||||
} |
||||
|
||||
_index = _fd_to_index_and_lock(fd); |
||||
|
||||
if (_index < 0) { |
||||
errno = EBADF; |
||||
return -1; |
||||
} |
||||
|
||||
if (WaitForSingleObject(poll_fd[_index].overlapped->hEvent, INFINITE) != WAIT_OBJECT_0) { |
||||
usbi_warn(NULL, "waiting for event failed: %u", (unsigned int)GetLastError()); |
||||
errno = EIO; |
||||
goto out; |
||||
} |
||||
|
||||
poll_dbg("clr pipe event (fd = %d, thread = %08X)", _index, (unsigned int)GetCurrentThreadId()); |
||||
poll_fd[_index].overlapped->InternalHigh--; |
||||
// Don't reset unless we don't have any more events to process
|
||||
if (poll_fd[_index].overlapped->InternalHigh <= 0) { |
||||
ResetEvent(poll_fd[_index].overlapped->hEvent); |
||||
poll_fd[_index].overlapped->Internal = STATUS_PENDING; |
||||
} |
||||
|
||||
r = sizeof(unsigned char); |
||||
|
||||
out: |
||||
LeaveCriticalSection(&_poll_fd[_index].mutex); |
||||
return r; |
||||
} |
@ -0,0 +1,131 @@ |
||||
/*
|
||||
* Windows compat: POSIX compatibility wrapper |
||||
* Copyright © 2012-2013 RealVNC Ltd. |
||||
* Copyright © 2009-2010 Pete Batard <pete@akeo.ie> |
||||
* With contributions from Michael Plante, Orin Eman et al. |
||||
* Parts of poll implementation from libusb-win32, by Stephan Meyer et al. |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU Lesser General Public |
||||
* License as published by the Free Software Foundation; either |
||||
* version 2.1 of the License, or (at your option) any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
* Lesser General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Lesser General Public |
||||
* License along with this library; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
* |
||||
*/ |
||||
#pragma once |
||||
|
||||
#if defined(_MSC_VER) |
||||
// disable /W4 MSVC warnings that are benign
|
||||
#pragma warning(disable:4127) // conditional expression is constant
|
||||
#endif |
||||
|
||||
// Handle synchronous completion through the overlapped structure
|
||||
#if !defined(STATUS_REPARSE) // reuse the REPARSE status code
|
||||
#define STATUS_REPARSE ((LONG)0x00000104L) |
||||
#endif |
||||
#define STATUS_COMPLETED_SYNCHRONOUSLY STATUS_REPARSE |
||||
#if defined(_WIN32_WCE) |
||||
// WinCE doesn't have a HasOverlappedIoCompleted() macro, so attempt to emulate it
|
||||
#define HasOverlappedIoCompleted(lpOverlapped) (((DWORD)(lpOverlapped)->Internal) != STATUS_PENDING) |
||||
#endif |
||||
#define HasOverlappedIoCompletedSync(lpOverlapped) (((DWORD)(lpOverlapped)->Internal) == STATUS_COMPLETED_SYNCHRONOUSLY) |
||||
|
||||
#define DUMMY_HANDLE ((HANDLE)(LONG_PTR)-2) |
||||
|
||||
/* Windows versions */ |
||||
enum windows_version { |
||||
WINDOWS_CE = -2, |
||||
WINDOWS_UNDEFINED = -1, |
||||
WINDOWS_UNSUPPORTED = 0, |
||||
WINDOWS_XP = 0x51, |
||||
WINDOWS_2003 = 0x52, // Also XP x64
|
||||
WINDOWS_VISTA = 0x60, |
||||
WINDOWS_7 = 0x61, |
||||
WINDOWS_8 = 0x62, |
||||
WINDOWS_8_1_OR_LATER = 0x63, |
||||
WINDOWS_MAX |
||||
}; |
||||
extern int windows_version; |
||||
|
||||
#define MAX_FDS 256 |
||||
|
||||
#define POLLIN 0x0001 /* There is data to read */ |
||||
#define POLLPRI 0x0002 /* There is urgent data to read */ |
||||
#define POLLOUT 0x0004 /* Writing now will not block */ |
||||
#define POLLERR 0x0008 /* Error condition */ |
||||
#define POLLHUP 0x0010 /* Hung up */ |
||||
#define POLLNVAL 0x0020 /* Invalid request: fd not open */ |
||||
|
||||
struct pollfd { |
||||
int fd; /* file descriptor */ |
||||
short events; /* requested events */ |
||||
short revents; /* returned events */ |
||||
}; |
||||
|
||||
// access modes
|
||||
enum rw_type { |
||||
RW_NONE, |
||||
RW_READ, |
||||
RW_WRITE, |
||||
}; |
||||
|
||||
// fd struct that can be used for polling on Windows
|
||||
typedef int cancel_transfer(struct usbi_transfer *itransfer); |
||||
|
||||
struct winfd { |
||||
int fd; // what's exposed to libusb core
|
||||
HANDLE handle; // what we need to attach overlapped to the I/O op, so we can poll it
|
||||
OVERLAPPED* overlapped; // what will report our I/O status
|
||||
struct usbi_transfer *itransfer; // Associated transfer, or NULL if completed
|
||||
cancel_transfer *cancel_fn; // Function pointer to cancel transfer API
|
||||
enum rw_type rw; // I/O transfer direction: read *XOR* write (NOT BOTH)
|
||||
}; |
||||
extern const struct winfd INVALID_WINFD; |
||||
|
||||
int usbi_pipe(int pipefd[2]); |
||||
int usbi_poll(struct pollfd *fds, unsigned int nfds, int timeout); |
||||
ssize_t usbi_write(int fd, const void *buf, size_t count); |
||||
ssize_t usbi_read(int fd, void *buf, size_t count); |
||||
int usbi_close(int fd); |
||||
|
||||
void init_polling(void); |
||||
void exit_polling(void); |
||||
struct winfd usbi_create_fd(HANDLE handle, int access_mode,
|
||||
struct usbi_transfer *transfer, cancel_transfer *cancel_fn); |
||||
void usbi_free_fd(struct winfd* winfd); |
||||
struct winfd fd_to_winfd(int fd); |
||||
struct winfd handle_to_winfd(HANDLE handle); |
||||
struct winfd overlapped_to_winfd(OVERLAPPED* overlapped); |
||||
|
||||
/*
|
||||
* Timeval operations |
||||
*/ |
||||
#if defined(DDKBUILD) |
||||
#include <winsock.h> // defines timeval functions on DDK |
||||
#endif |
||||
|
||||
#if !defined(TIMESPEC_TO_TIMEVAL) |
||||
#define TIMESPEC_TO_TIMEVAL(tv, ts) { \ |
||||
(tv)->tv_sec = (long)(ts)->tv_sec; \
|
||||
(tv)->tv_usec = (long)(ts)->tv_nsec / 1000; \
|
||||
} |
||||
#endif |
||||
#if !defined(timersub) |
||||
#define timersub(a, b, result) \ |
||||
do { \
|
||||
(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
|
||||
(result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
|
||||
if ((result)->tv_usec < 0) { \
|
||||
--(result)->tv_sec; \
|
||||
(result)->tv_usec += 1000000; \
|
||||
} \
|
||||
} while (0) |
||||
#endif |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,74 @@ |
||||
/*
|
||||
* |
||||
* Copyright (c) 2016, Oracle and/or its affiliates. |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU Lesser General Public |
||||
* License as published by the Free Software Foundation; either |
||||
* version 2.1 of the License, or (at your option) any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
* Lesser General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Lesser General Public |
||||
* License along with this library; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
*/ |
||||
|
||||
#ifndef LIBUSB_SUNOS_H |
||||
#define LIBUSB_SUNOS_H |
||||
|
||||
#include <libdevinfo.h> |
||||
#include <pthread.h> |
||||
#include "libusbi.h" |
||||
|
||||
#define READ 0 |
||||
#define WRITE 1 |
||||
|
||||
typedef struct sunos_device_priv { |
||||
uint8_t cfgvalue; /* active config value */ |
||||
uint8_t *raw_cfgdescr; /* active config descriptor */ |
||||
struct libusb_device_descriptor dev_descr; /* usb device descriptor */ |
||||
char *ugenpath; /* name of the ugen(4) node */ |
||||
char *phypath; /* physical path */ |
||||
} sunos_dev_priv_t; |
||||
|
||||
typedef struct endpoint { |
||||
int datafd; /* data file */ |
||||
int statfd; /* state file */ |
||||
} sunos_ep_priv_t; |
||||
|
||||
typedef struct sunos_device_handle_priv { |
||||
uint8_t altsetting[USB_MAXINTERFACES]; /* a interface's alt */ |
||||
uint8_t config_index; |
||||
sunos_ep_priv_t eps[USB_MAXENDPOINTS]; |
||||
sunos_dev_priv_t *dpriv; /* device private */ |
||||
} sunos_dev_handle_priv_t; |
||||
|
||||
typedef struct sunos_transfer_priv { |
||||
struct aiocb aiocb; |
||||
struct libusb_transfer *transfer; |
||||
} sunos_xfer_priv_t; |
||||
|
||||
struct node_args { |
||||
struct libusb_context *ctx; |
||||
struct discovered_devs **discdevs; |
||||
const char *last_ugenpath; |
||||
di_devlink_handle_t dlink_hdl; |
||||
}; |
||||
|
||||
struct devlink_cbarg { |
||||
struct node_args *nargs; /* di node walk arguments */ |
||||
di_node_t myself; /* the di node */ |
||||
di_minor_t minor; |
||||
}; |
||||
|
||||
/* AIO callback args */ |
||||
struct aio_callback_args{ |
||||
struct libusb_transfer *transfer; |
||||
struct aiocb aiocb; |
||||
}; |
||||
|
||||
#endif /* LIBUSB_SUNOS_H */ |
@ -0,0 +1,79 @@ |
||||
/*
|
||||
* libusb synchronization using POSIX Threads |
||||
* |
||||
* Copyright © 2011 Vitali Lovich <vlovich@aliph.com> |
||||
* Copyright © 2011 Peter Stuge <peter@stuge.se> |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU Lesser General Public |
||||
* License as published by the Free Software Foundation; either |
||||
* version 2.1 of the License, or (at your option) any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
* Lesser General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Lesser General Public |
||||
* License along with this library; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
*/ |
||||
|
||||
#include <config.h> |
||||
|
||||
#include <time.h> |
||||
#if defined(__linux__) || defined(__OpenBSD__) |
||||
# if defined(__OpenBSD__) |
||||
# define _BSD_SOURCE |
||||
# endif |
||||
# include <unistd.h> |
||||
# include <sys/syscall.h> |
||||
#elif defined(__APPLE__) |
||||
# include <mach/mach.h> |
||||
#elif defined(__CYGWIN__) |
||||
# include <windows.h> |
||||
#endif |
||||
|
||||
#include "threads_posix.h" |
||||
#include "libusbi.h" |
||||
|
||||
int usbi_cond_timedwait(pthread_cond_t *cond, |
||||
pthread_mutex_t *mutex, const struct timeval *tv) |
||||
{ |
||||
struct timespec timeout; |
||||
int r; |
||||
|
||||
r = usbi_backend->clock_gettime(USBI_CLOCK_REALTIME, &timeout); |
||||
if (r < 0) |
||||
return r; |
||||
|
||||
timeout.tv_sec += tv->tv_sec; |
||||
timeout.tv_nsec += tv->tv_usec * 1000; |
||||
while (timeout.tv_nsec >= 1000000000L) { |
||||
timeout.tv_nsec -= 1000000000L; |
||||
timeout.tv_sec++; |
||||
} |
||||
|
||||
return pthread_cond_timedwait(cond, mutex, &timeout); |
||||
} |
||||
|
||||
int usbi_get_tid(void) |
||||
{ |
||||
int ret = -1; |
||||
#if defined(__ANDROID__) |
||||
ret = gettid(); |
||||
#elif defined(__linux__) |
||||
ret = syscall(SYS_gettid); |
||||
#elif defined(__OpenBSD__) |
||||
/* The following only works with OpenBSD > 5.1 as it requires
|
||||
real thread support. For 5.1 and earlier, -1 is returned. */ |
||||
ret = syscall(SYS_getthrid); |
||||
#elif defined(__APPLE__) |
||||
ret = mach_thread_self(); |
||||
mach_port_deallocate(mach_task_self(), ret); |
||||
#elif defined(__CYGWIN__) |
||||
ret = GetCurrentThreadId(); |
||||
#endif |
||||
/* TODO: NetBSD thread ID support */ |
||||
return ret; |
||||
} |
@ -0,0 +1,55 @@ |
||||
/*
|
||||
* libusb synchronization using POSIX Threads |
||||
* |
||||
* Copyright © 2010 Peter Stuge <peter@stuge.se> |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU Lesser General Public |
||||
* License as published by the Free Software Foundation; either |
||||
* version 2.1 of the License, or (at your option) any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
* Lesser General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Lesser General Public |
||||
* License along with this library; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
*/ |
||||
|
||||
#ifndef LIBUSB_THREADS_POSIX_H |
||||
#define LIBUSB_THREADS_POSIX_H |
||||
|
||||
#include <pthread.h> |
||||
#ifdef HAVE_SYS_TIME_H |
||||
#include <sys/time.h> |
||||
#endif |
||||
|
||||
#define usbi_mutex_static_t pthread_mutex_t |
||||
#define USBI_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER |
||||
#define usbi_mutex_static_lock pthread_mutex_lock |
||||
#define usbi_mutex_static_unlock pthread_mutex_unlock |
||||
|
||||
#define usbi_mutex_t pthread_mutex_t |
||||
#define usbi_mutex_init(mutex) pthread_mutex_init((mutex), NULL) |
||||
#define usbi_mutex_lock pthread_mutex_lock |
||||
#define usbi_mutex_unlock pthread_mutex_unlock |
||||
#define usbi_mutex_trylock pthread_mutex_trylock |
||||
#define usbi_mutex_destroy pthread_mutex_destroy |
||||
|
||||
#define usbi_cond_t pthread_cond_t |
||||
#define usbi_cond_init(cond) pthread_cond_init((cond), NULL) |
||||
#define usbi_cond_wait pthread_cond_wait |
||||
#define usbi_cond_broadcast pthread_cond_broadcast |
||||
#define usbi_cond_destroy pthread_cond_destroy |
||||
|
||||
#define usbi_tls_key_t pthread_key_t |
||||
#define usbi_tls_key_create(key) pthread_key_create((key), NULL) |
||||
#define usbi_tls_key_get pthread_getspecific |
||||
#define usbi_tls_key_set pthread_setspecific |
||||
#define usbi_tls_key_delete pthread_key_delete |
||||
|
||||
int usbi_get_tid(void); |
||||
|
||||
#endif /* LIBUSB_THREADS_POSIX_H */ |
@ -0,0 +1,259 @@ |
||||
/*
|
||||
* libusb synchronization on Microsoft Windows |
||||
* |
||||
* Copyright © 2010 Michael Plante <michael.plante@gmail.com> |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU Lesser General Public |
||||
* License as published by the Free Software Foundation; either |
||||
* version 2.1 of the License, or (at your option) any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
* Lesser General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Lesser General Public |
||||
* License along with this library; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
*/ |
||||
|
||||
#include <config.h> |
||||
|
||||
#include <objbase.h> |
||||
#include <errno.h> |
||||
|
||||
#include "libusbi.h" |
||||
|
||||
struct usbi_cond_perthread { |
||||
struct list_head list; |
||||
DWORD tid; |
||||
HANDLE event; |
||||
}; |
||||
|
||||
int usbi_mutex_static_lock(usbi_mutex_static_t *mutex) |
||||
{ |
||||
if (!mutex) |
||||
return EINVAL; |
||||
while (InterlockedExchange(mutex, 1) == 1) |
||||
SleepEx(0, TRUE); |
||||
return 0; |
||||
} |
||||
|
||||
int usbi_mutex_static_unlock(usbi_mutex_static_t *mutex) |
||||
{ |
||||
if (!mutex) |
||||
return EINVAL; |
||||
InterlockedExchange(mutex, 0); |
||||
return 0; |
||||
} |
||||
|
||||
int usbi_mutex_init(usbi_mutex_t *mutex) |
||||
{ |
||||
if (!mutex) |
||||
return EINVAL; |
||||
*mutex = CreateMutex(NULL, FALSE, NULL); |
||||
if (!*mutex) |
||||
return ENOMEM; |
||||
return 0; |
||||
} |
||||
|
||||
int usbi_mutex_lock(usbi_mutex_t *mutex) |
||||
{ |
||||
DWORD result; |
||||
|
||||
if (!mutex) |
||||
return EINVAL; |
||||
result = WaitForSingleObject(*mutex, INFINITE); |
||||
if (result == WAIT_OBJECT_0 || result == WAIT_ABANDONED) |
||||
return 0; // acquired (ToDo: check that abandoned is ok)
|
||||
else |
||||
return EINVAL; // don't know how this would happen
|
||||
// so don't know proper errno
|
||||
} |
||||
|
||||
int usbi_mutex_unlock(usbi_mutex_t *mutex) |
||||
{ |
||||
if (!mutex) |
||||
return EINVAL; |
||||
if (ReleaseMutex(*mutex)) |
||||
return 0; |
||||
else |
||||
return EPERM; |
||||
} |
||||
|
||||
int usbi_mutex_trylock(usbi_mutex_t *mutex) |
||||
{ |
||||
DWORD result; |
||||
|
||||
if (!mutex) |
||||
return EINVAL; |
||||
result = WaitForSingleObject(*mutex, 0); |
||||
if (result == WAIT_OBJECT_0 || result == WAIT_ABANDONED) |
||||
return 0; // acquired (ToDo: check that abandoned is ok)
|
||||
else if (result == WAIT_TIMEOUT) |
||||
return EBUSY; |
||||
else |
||||
return EINVAL; // don't know how this would happen
|
||||
// so don't know proper error
|
||||
} |
||||
|
||||
int usbi_mutex_destroy(usbi_mutex_t *mutex) |
||||
{ |
||||
// It is not clear if CloseHandle failure is due to failure to unlock.
|
||||
// If so, this should be errno=EBUSY.
|
||||
if (!mutex || !CloseHandle(*mutex)) |
||||
return EINVAL; |
||||
*mutex = NULL; |
||||
return 0; |
||||
} |
||||
|
||||
int usbi_cond_init(usbi_cond_t *cond) |
||||
{ |
||||
if (!cond) |
||||
return EINVAL; |
||||
list_init(&cond->waiters); |
||||
list_init(&cond->not_waiting); |
||||
return 0; |
||||
} |
||||
|
||||
int usbi_cond_destroy(usbi_cond_t *cond) |
||||
{ |
||||
// This assumes no one is using this anymore. The check MAY NOT BE safe.
|
||||
struct usbi_cond_perthread *pos, *next_pos; |
||||
|
||||
if(!cond) |
||||
return EINVAL; |
||||
if (!list_empty(&cond->waiters)) |
||||
return EBUSY; // (!see above!)
|
||||
list_for_each_entry_safe(pos, next_pos, &cond->not_waiting, list, struct usbi_cond_perthread) { |
||||
CloseHandle(pos->event); |
||||
list_del(&pos->list); |
||||
free(pos); |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
int usbi_cond_broadcast(usbi_cond_t *cond) |
||||
{ |
||||
// Assumes mutex is locked; this is not in keeping with POSIX spec, but
|
||||
// libusb does this anyway, so we simplify by not adding more sync
|
||||
// primitives to the CV definition!
|
||||
int fail = 0; |
||||
struct usbi_cond_perthread *pos; |
||||
|
||||
if (!cond) |
||||
return EINVAL; |
||||
list_for_each_entry(pos, &cond->waiters, list, struct usbi_cond_perthread) { |
||||
if (!SetEvent(pos->event)) |
||||
fail = 1; |
||||
} |
||||
// The wait function will remove its respective item from the list.
|
||||
return fail ? EINVAL : 0; |
||||
} |
||||
|
||||
__inline static int usbi_cond_intwait(usbi_cond_t *cond, |
||||
usbi_mutex_t *mutex, DWORD timeout_ms) |
||||
{ |
||||
struct usbi_cond_perthread *pos; |
||||
int r, found = 0; |
||||
DWORD r2, tid = GetCurrentThreadId(); |
||||
|
||||
if (!cond || !mutex) |
||||
return EINVAL; |
||||
list_for_each_entry(pos, &cond->not_waiting, list, struct usbi_cond_perthread) { |
||||
if(tid == pos->tid) { |
||||
found = 1; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (!found) { |
||||
pos = calloc(1, sizeof(struct usbi_cond_perthread)); |
||||
if (!pos) |
||||
return ENOMEM; // This errno is not POSIX-allowed.
|
||||
pos->tid = tid; |
||||
pos->event = CreateEvent(NULL, FALSE, FALSE, NULL); // auto-reset.
|
||||
if (!pos->event) { |
||||
free(pos); |
||||
return ENOMEM; |
||||
} |
||||
list_add(&pos->list, &cond->not_waiting); |
||||
} |
||||
|
||||
list_del(&pos->list); // remove from not_waiting list.
|
||||
list_add(&pos->list, &cond->waiters); |
||||
|
||||
r = usbi_mutex_unlock(mutex); |
||||
if (r) |
||||
return r; |
||||
|
||||
r2 = WaitForSingleObject(pos->event, timeout_ms); |
||||
r = usbi_mutex_lock(mutex); |
||||
if (r) |
||||
return r; |
||||
|
||||
list_del(&pos->list); |
||||
list_add(&pos->list, &cond->not_waiting); |
||||
|
||||
if (r2 == WAIT_OBJECT_0) |
||||
return 0; |
||||
else if (r2 == WAIT_TIMEOUT) |
||||
return ETIMEDOUT; |
||||
else |
||||
return EINVAL; |
||||
} |
||||
// N.B.: usbi_cond_*wait() can also return ENOMEM, even though pthread_cond_*wait cannot!
|
||||
int usbi_cond_wait(usbi_cond_t *cond, usbi_mutex_t *mutex) |
||||
{ |
||||
return usbi_cond_intwait(cond, mutex, INFINITE); |
||||
} |
||||
|
||||
int usbi_cond_timedwait(usbi_cond_t *cond, |
||||
usbi_mutex_t *mutex, const struct timeval *tv) |
||||
{ |
||||
DWORD millis; |
||||
|
||||
millis = (DWORD)(tv->tv_sec * 1000) + (tv->tv_usec / 1000); |
||||
/* round up to next millisecond */ |
||||
if (tv->tv_usec % 1000) |
||||
millis++; |
||||
return usbi_cond_intwait(cond, mutex, millis); |
||||
} |
||||
|
||||
int usbi_tls_key_create(usbi_tls_key_t *key) |
||||
{ |
||||
if (!key) |
||||
return EINVAL; |
||||
*key = TlsAlloc(); |
||||
if (*key == TLS_OUT_OF_INDEXES) |
||||
return ENOMEM; |
||||
else |
||||
return 0; |
||||
} |
||||
|
||||
void *usbi_tls_key_get(usbi_tls_key_t key) |
||||
{ |
||||
return TlsGetValue(key); |
||||
} |
||||
|
||||
int usbi_tls_key_set(usbi_tls_key_t key, void *value) |
||||
{ |
||||
if (TlsSetValue(key, value)) |
||||
return 0; |
||||
else |
||||
return EINVAL; |
||||
} |
||||
|
||||
int usbi_tls_key_delete(usbi_tls_key_t key) |
||||
{ |
||||
if (TlsFree(key)) |
||||
return 0; |
||||
else |
||||
return EINVAL; |
||||
} |
||||
|
||||
int usbi_get_tid(void) |
||||
{ |
||||
return (int)GetCurrentThreadId(); |
||||
} |
@ -0,0 +1,76 @@ |
||||
/*
|
||||
* libusb synchronization on Microsoft Windows |
||||
* |
||||
* Copyright © 2010 Michael Plante <michael.plante@gmail.com> |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU Lesser General Public |
||||
* License as published by the Free Software Foundation; either |
||||
* version 2.1 of the License, or (at your option) any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
* Lesser General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Lesser General Public |
||||
* License along with this library; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
*/ |
||||
|
||||
#ifndef LIBUSB_THREADS_WINDOWS_H |
||||
#define LIBUSB_THREADS_WINDOWS_H |
||||
|
||||
#define usbi_mutex_static_t volatile LONG |
||||
#define USBI_MUTEX_INITIALIZER 0 |
||||
|
||||
#define usbi_mutex_t HANDLE |
||||
|
||||
typedef struct usbi_cond { |
||||
// Every time a thread touches the CV, it winds up in one of these lists.
|
||||
// It stays there until the CV is destroyed, even if the thread terminates.
|
||||
struct list_head waiters; |
||||
struct list_head not_waiting; |
||||
} usbi_cond_t; |
||||
|
||||
// We *were* getting timespec from pthread.h:
|
||||
#if (!defined(HAVE_STRUCT_TIMESPEC) && !defined(_TIMESPEC_DEFINED)) |
||||
#define HAVE_STRUCT_TIMESPEC 1 |
||||
#define _TIMESPEC_DEFINED 1 |
||||
struct timespec { |
||||
long tv_sec; |
||||
long tv_nsec; |
||||
}; |
||||
#endif /* HAVE_STRUCT_TIMESPEC | _TIMESPEC_DEFINED */ |
||||
|
||||
// We *were* getting ETIMEDOUT from pthread.h:
|
||||
#ifndef ETIMEDOUT |
||||
# define ETIMEDOUT 10060 /* This is the value in winsock.h. */ |
||||
#endif |
||||
|
||||
#define usbi_tls_key_t DWORD |
||||
|
||||
int usbi_mutex_static_lock(usbi_mutex_static_t *mutex); |
||||
int usbi_mutex_static_unlock(usbi_mutex_static_t *mutex); |
||||
|
||||
int usbi_mutex_init(usbi_mutex_t *mutex); |
||||
int usbi_mutex_lock(usbi_mutex_t *mutex); |
||||
int usbi_mutex_unlock(usbi_mutex_t *mutex); |
||||
int usbi_mutex_trylock(usbi_mutex_t *mutex); |
||||
int usbi_mutex_destroy(usbi_mutex_t *mutex); |
||||
|
||||
int usbi_cond_init(usbi_cond_t *cond); |
||||
int usbi_cond_wait(usbi_cond_t *cond, usbi_mutex_t *mutex); |
||||
int usbi_cond_timedwait(usbi_cond_t *cond, |
||||
usbi_mutex_t *mutex, const struct timeval *tv); |
||||
int usbi_cond_broadcast(usbi_cond_t *cond); |
||||
int usbi_cond_destroy(usbi_cond_t *cond); |
||||
|
||||
int usbi_tls_key_create(usbi_tls_key_t *key); |
||||
void *usbi_tls_key_get(usbi_tls_key_t key); |
||||
int usbi_tls_key_set(usbi_tls_key_t key, void *value); |
||||
int usbi_tls_key_delete(usbi_tls_key_t key); |
||||
|
||||
int usbi_get_tid(void); |
||||
|
||||
#endif /* LIBUSB_THREADS_WINDOWS_H */ |
@ -0,0 +1,899 @@ |
||||
/*
|
||||
* Windows CE backend for libusb 1.0 |
||||
* Copyright © 2011-2013 RealVNC Ltd. |
||||
* Large portions taken from Windows backend, which is |
||||
* Copyright © 2009-2010 Pete Batard <pbatard@gmail.com> |
||||
* With contributions from Michael Plante, Orin Eman et al. |
||||
* Parts of this code adapted from libusb-win32-v1 by Stephan Meyer |
||||
* Major code testing contribution by Xiaofan Chen |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU Lesser General Public |
||||
* License as published by the Free Software Foundation; either |
||||
* version 2.1 of the License, or (at your option) any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
* Lesser General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Lesser General Public |
||||
* License along with this library; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
*/ |
||||
|
||||
#include <config.h> |
||||
|
||||
#include <stdint.h> |
||||
#include <inttypes.h> |
||||
|
||||
#include "libusbi.h" |
||||
#include "wince_usb.h" |
||||
|
||||
// Global variables
|
||||
int windows_version = WINDOWS_CE; |
||||
static uint64_t hires_frequency, hires_ticks_to_ps; |
||||
static HANDLE driver_handle = INVALID_HANDLE_VALUE; |
||||
static int concurrent_usage = -1; |
||||
|
||||
/*
|
||||
* Converts a windows error to human readable string |
||||
* uses retval as errorcode, or, if 0, use GetLastError() |
||||
*/ |
||||
#if defined(ENABLE_LOGGING) |
||||
static const char *windows_error_str(DWORD error_code) |
||||
{ |
||||
static TCHAR wErr_string[ERR_BUFFER_SIZE]; |
||||
static char err_string[ERR_BUFFER_SIZE]; |
||||
|
||||
DWORD size; |
||||
int len; |
||||
|
||||
if (error_code == 0) |
||||
error_code = GetLastError(); |
||||
|
||||
len = sprintf(err_string, "[%u] ", (unsigned int)error_code); |
||||
|
||||
size = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, |
||||
NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
||||
wErr_string, ERR_BUFFER_SIZE, NULL); |
||||
if (size == 0) { |
||||
DWORD format_error = GetLastError(); |
||||
if (format_error) |
||||
snprintf(err_string, ERR_BUFFER_SIZE, |
||||
"Windows error code %u (FormatMessage error code %u)", |
||||
(unsigned int)error_code, (unsigned int)format_error); |
||||
else |
||||
snprintf(err_string, ERR_BUFFER_SIZE, "Unknown error code %u", (unsigned int)error_code); |
||||
} else { |
||||
// Remove CR/LF terminators, if present
|
||||
size_t pos = size - 2; |
||||
if (wErr_string[pos] == 0x0D) |
||||
wErr_string[pos] = 0; |
||||
|
||||
if (!WideCharToMultiByte(CP_ACP, 0, wErr_string, -1, &err_string[len], ERR_BUFFER_SIZE - len, NULL, NULL)) |
||||
strcpy(err_string, "Unable to convert error string"); |
||||
} |
||||
|
||||
return err_string; |
||||
} |
||||
#endif |
||||
|
||||
static struct wince_device_priv *_device_priv(struct libusb_device *dev) |
||||
{ |
||||
return (struct wince_device_priv *)dev->os_priv; |
||||
} |
||||
|
||||
// ceusbkwrapper to libusb error code mapping
|
||||
static int translate_driver_error(DWORD error) |
||||
{ |
||||
switch (error) { |
||||
case ERROR_INVALID_PARAMETER: |
||||
return LIBUSB_ERROR_INVALID_PARAM; |
||||
case ERROR_CALL_NOT_IMPLEMENTED: |
||||
case ERROR_NOT_SUPPORTED: |
||||
return LIBUSB_ERROR_NOT_SUPPORTED; |
||||
case ERROR_NOT_ENOUGH_MEMORY: |
||||
return LIBUSB_ERROR_NO_MEM; |
||||
case ERROR_INVALID_HANDLE: |
||||
return LIBUSB_ERROR_NO_DEVICE; |
||||
case ERROR_BUSY: |
||||
return LIBUSB_ERROR_BUSY; |
||||
|
||||
// Error codes that are either unexpected, or have
|
||||
// no suitable LIBUSB_ERROR equivalent.
|
||||
case ERROR_CANCELLED: |
||||
case ERROR_INTERNAL_ERROR: |
||||
default: |
||||
return LIBUSB_ERROR_OTHER; |
||||
} |
||||
} |
||||
|
||||
static int init_dllimports(void) |
||||
{ |
||||
DLL_GET_HANDLE(ceusbkwrapper); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwOpenDriver, TRUE); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwGetDeviceList, TRUE); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwReleaseDeviceList, TRUE); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwGetDeviceAddress, TRUE); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwGetDeviceDescriptor, TRUE); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwGetConfigDescriptor, TRUE); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwCloseDriver, TRUE); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwCancelTransfer, TRUE); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwIssueControlTransfer, TRUE); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwClaimInterface, TRUE); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwReleaseInterface, TRUE); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwSetInterfaceAlternateSetting, TRUE); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwClearHaltHost, TRUE); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwClearHaltDevice, TRUE); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwGetConfig, TRUE); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwSetConfig, TRUE); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwResetDevice, TRUE); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwKernelDriverActive, TRUE); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwAttachKernelDriver, TRUE); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwDetachKernelDriver, TRUE); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwIssueBulkTransfer, TRUE); |
||||
DLL_LOAD_FUNC(ceusbkwrapper, UkwIsPipeHalted, TRUE); |
||||
|
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
static void exit_dllimports(void) |
||||
{ |
||||
DLL_FREE_HANDLE(ceusbkwrapper); |
||||
} |
||||
|
||||
static int init_device( |
||||
struct libusb_device *dev, UKW_DEVICE drv_dev, |
||||
unsigned char bus_addr, unsigned char dev_addr) |
||||
{ |
||||
struct wince_device_priv *priv = _device_priv(dev); |
||||
int r = LIBUSB_SUCCESS; |
||||
|
||||
dev->bus_number = bus_addr; |
||||
dev->device_address = dev_addr; |
||||
priv->dev = drv_dev; |
||||
|
||||
if (!UkwGetDeviceDescriptor(priv->dev, &(priv->desc))) |
||||
r = translate_driver_error(GetLastError()); |
||||
|
||||
return r; |
||||
} |
||||
|
||||
// Internal API functions
|
||||
static int wince_init(struct libusb_context *ctx) |
||||
{ |
||||
int r = LIBUSB_ERROR_OTHER; |
||||
HANDLE semaphore; |
||||
LARGE_INTEGER li_frequency; |
||||
TCHAR sem_name[11 + 8 + 1]; // strlen("libusb_init") + (32-bit hex PID) + '\0'
|
||||
|
||||
_stprintf(sem_name, _T("libusb_init%08X"), (unsigned int)(GetCurrentProcessId() & 0xFFFFFFFF)); |
||||
semaphore = CreateSemaphore(NULL, 1, 1, sem_name); |
||||
if (semaphore == NULL) { |
||||
usbi_err(ctx, "could not create semaphore: %s", windows_error_str(0)); |
||||
return LIBUSB_ERROR_NO_MEM; |
||||
} |
||||
|
||||
// A successful wait brings our semaphore count to 0 (unsignaled)
|
||||
// => any concurent wait stalls until the semaphore's release
|
||||
if (WaitForSingleObject(semaphore, INFINITE) != WAIT_OBJECT_0) { |
||||
usbi_err(ctx, "failure to access semaphore: %s", windows_error_str(0)); |
||||
CloseHandle(semaphore); |
||||
return LIBUSB_ERROR_NO_MEM; |
||||
} |
||||
|
||||
// NB: concurrent usage supposes that init calls are equally balanced with
|
||||
// exit calls. If init is called more than exit, we will not exit properly
|
||||
if ( ++concurrent_usage == 0 ) { // First init?
|
||||
// Initialize pollable file descriptors
|
||||
init_polling(); |
||||
|
||||
// Load DLL imports
|
||||
if (init_dllimports() != LIBUSB_SUCCESS) { |
||||
usbi_err(ctx, "could not resolve DLL functions"); |
||||
r = LIBUSB_ERROR_NOT_SUPPORTED; |
||||
goto init_exit; |
||||
} |
||||
|
||||
// try to open a handle to the driver
|
||||
driver_handle = UkwOpenDriver(); |
||||
if (driver_handle == INVALID_HANDLE_VALUE) { |
||||
usbi_err(ctx, "could not connect to driver"); |
||||
r = LIBUSB_ERROR_NOT_SUPPORTED; |
||||
goto init_exit; |
||||
} |
||||
|
||||
// find out if we have access to a monotonic (hires) timer
|
||||
if (QueryPerformanceFrequency(&li_frequency)) { |
||||
hires_frequency = li_frequency.QuadPart; |
||||
// The hires frequency can go as high as 4 GHz, so we'll use a conversion
|
||||
// to picoseconds to compute the tv_nsecs part in clock_gettime
|
||||
hires_ticks_to_ps = UINT64_C(1000000000000) / hires_frequency; |
||||
usbi_dbg("hires timer available (Frequency: %"PRIu64" Hz)", hires_frequency); |
||||
} else { |
||||
usbi_dbg("no hires timer available on this platform"); |
||||
hires_frequency = 0; |
||||
hires_ticks_to_ps = UINT64_C(0); |
||||
} |
||||
} |
||||
// At this stage, either we went through full init successfully, or didn't need to
|
||||
r = LIBUSB_SUCCESS; |
||||
|
||||
init_exit: // Holds semaphore here.
|
||||
if (!concurrent_usage && r != LIBUSB_SUCCESS) { // First init failed?
|
||||
exit_dllimports(); |
||||
exit_polling(); |
||||
|
||||
if (driver_handle != INVALID_HANDLE_VALUE) { |
||||
UkwCloseDriver(driver_handle); |
||||
driver_handle = INVALID_HANDLE_VALUE; |
||||
} |
||||
} |
||||
|
||||
if (r != LIBUSB_SUCCESS) |
||||
--concurrent_usage; // Not expected to call libusb_exit if we failed.
|
||||
|
||||
ReleaseSemaphore(semaphore, 1, NULL); // increase count back to 1
|
||||
CloseHandle(semaphore); |
||||
return r; |
||||
} |
||||
|
||||
static void wince_exit(void) |
||||
{ |
||||
HANDLE semaphore; |
||||
TCHAR sem_name[11 + 8 + 1]; // strlen("libusb_init") + (32-bit hex PID) + '\0'
|
||||
|
||||
_stprintf(sem_name, _T("libusb_init%08X"), (unsigned int)(GetCurrentProcessId() & 0xFFFFFFFF)); |
||||
semaphore = CreateSemaphore(NULL, 1, 1, sem_name); |
||||
if (semaphore == NULL) |
||||
return; |
||||
|
||||
// A successful wait brings our semaphore count to 0 (unsignaled)
|
||||
// => any concurent wait stalls until the semaphore release
|
||||
if (WaitForSingleObject(semaphore, INFINITE) != WAIT_OBJECT_0) { |
||||
CloseHandle(semaphore); |
||||
return; |
||||
} |
||||
|
||||
// Only works if exits and inits are balanced exactly
|
||||
if (--concurrent_usage < 0) { // Last exit
|
||||
exit_dllimports(); |
||||
exit_polling(); |
||||
|
||||
if (driver_handle != INVALID_HANDLE_VALUE) { |
||||
UkwCloseDriver(driver_handle); |
||||
driver_handle = INVALID_HANDLE_VALUE; |
||||
} |
||||
} |
||||
|
||||
ReleaseSemaphore(semaphore, 1, NULL); // increase count back to 1
|
||||
CloseHandle(semaphore); |
||||
} |
||||
|
||||
static int wince_get_device_list( |
||||
struct libusb_context *ctx, |
||||
struct discovered_devs **discdevs) |
||||
{ |
||||
UKW_DEVICE devices[MAX_DEVICE_COUNT]; |
||||
struct discovered_devs *new_devices = *discdevs; |
||||
DWORD count = 0, i; |
||||
struct libusb_device *dev = NULL; |
||||
unsigned char bus_addr, dev_addr; |
||||
unsigned long session_id; |
||||
BOOL success; |
||||
DWORD release_list_offset = 0; |
||||
int r = LIBUSB_SUCCESS; |
||||
|
||||
success = UkwGetDeviceList(driver_handle, devices, MAX_DEVICE_COUNT, &count); |
||||
if (!success) { |
||||
int libusbErr = translate_driver_error(GetLastError()); |
||||
usbi_err(ctx, "could not get devices: %s", windows_error_str(0)); |
||||
return libusbErr; |
||||
} |
||||
|
||||
for (i = 0; i < count; ++i) { |
||||
release_list_offset = i; |
||||
success = UkwGetDeviceAddress(devices[i], &bus_addr, &dev_addr, &session_id); |
||||
if (!success) { |
||||
r = translate_driver_error(GetLastError()); |
||||
usbi_err(ctx, "could not get device address for %u: %s", (unsigned int)i, windows_error_str(0)); |
||||
goto err_out; |
||||
} |
||||
|
||||
dev = usbi_get_device_by_session_id(ctx, session_id); |
||||
if (dev) { |
||||
usbi_dbg("using existing device for %u/%u (session %lu)", |
||||
bus_addr, dev_addr, session_id); |
||||
// Release just this element in the device list (as we already hold a
|
||||
// reference to it).
|
||||
UkwReleaseDeviceList(driver_handle, &devices[i], 1); |
||||
release_list_offset++; |
||||
} else { |
||||
usbi_dbg("allocating new device for %u/%u (session %lu)", |
||||
bus_addr, dev_addr, session_id); |
||||
dev = usbi_alloc_device(ctx, session_id); |
||||
if (!dev) { |
||||
r = LIBUSB_ERROR_NO_MEM; |
||||
goto err_out; |
||||
} |
||||
|
||||
r = init_device(dev, devices[i], bus_addr, dev_addr); |
||||
if (r < 0) |
||||
goto err_out; |
||||
|
||||
r = usbi_sanitize_device(dev); |
||||
if (r < 0) |
||||
goto err_out; |
||||
} |
||||
|
||||
new_devices = discovered_devs_append(new_devices, dev); |
||||
if (!discdevs) { |
||||
r = LIBUSB_ERROR_NO_MEM; |
||||
goto err_out; |
||||
} |
||||
|
||||
libusb_unref_device(dev); |
||||
} |
||||
|
||||
*discdevs = new_devices; |
||||
return r; |
||||
err_out: |
||||
*discdevs = new_devices; |
||||
libusb_unref_device(dev); |
||||
// Release the remainder of the unprocessed device list.
|
||||
// The devices added to new_devices already will still be passed up to libusb,
|
||||
// which can dispose of them at its leisure.
|
||||
UkwReleaseDeviceList(driver_handle, &devices[release_list_offset], count - release_list_offset); |
||||
return r; |
||||
} |
||||
|
||||
static int wince_open(struct libusb_device_handle *handle) |
||||
{ |
||||
// Nothing to do to open devices as a handle to it has
|
||||
// been retrieved by wince_get_device_list
|
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
static void wince_close(struct libusb_device_handle *handle) |
||||
{ |
||||
// Nothing to do as wince_open does nothing.
|
||||
} |
||||
|
||||
static int wince_get_device_descriptor( |
||||
struct libusb_device *device, |
||||
unsigned char *buffer, int *host_endian) |
||||
{ |
||||
struct wince_device_priv *priv = _device_priv(device); |
||||
|
||||
*host_endian = 1; |
||||
memcpy(buffer, &priv->desc, DEVICE_DESC_LENGTH); |
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
static int wince_get_active_config_descriptor( |
||||
struct libusb_device *device, |
||||
unsigned char *buffer, size_t len, int *host_endian) |
||||
{ |
||||
struct wince_device_priv *priv = _device_priv(device); |
||||
DWORD actualSize = len; |
||||
|
||||
*host_endian = 0; |
||||
if (!UkwGetConfigDescriptor(priv->dev, UKW_ACTIVE_CONFIGURATION, buffer, len, &actualSize)) |
||||
return translate_driver_error(GetLastError()); |
||||
|
||||
return actualSize; |
||||
} |
||||
|
||||
static int wince_get_config_descriptor( |
||||
struct libusb_device *device, |
||||
uint8_t config_index, |
||||
unsigned char *buffer, size_t len, int *host_endian) |
||||
{ |
||||
struct wince_device_priv *priv = _device_priv(device); |
||||
DWORD actualSize = len; |
||||
|
||||
*host_endian = 0; |
||||
if (!UkwGetConfigDescriptor(priv->dev, config_index, buffer, len, &actualSize)) |
||||
return translate_driver_error(GetLastError()); |
||||
|
||||
return actualSize; |
||||
} |
||||
|
||||
static int wince_get_configuration( |
||||
struct libusb_device_handle *handle, |
||||
int *config) |
||||
{ |
||||
struct wince_device_priv *priv = _device_priv(handle->dev); |
||||
UCHAR cv = 0; |
||||
|
||||
if (!UkwGetConfig(priv->dev, &cv)) |
||||
return translate_driver_error(GetLastError()); |
||||
|
||||
(*config) = cv; |
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
static int wince_set_configuration( |
||||
struct libusb_device_handle *handle, |
||||
int config) |
||||
{ |
||||
struct wince_device_priv *priv = _device_priv(handle->dev); |
||||
// Setting configuration 0 places the device in Address state.
|
||||
// This should correspond to the "unconfigured state" required by
|
||||
// libusb when the specified configuration is -1.
|
||||
UCHAR cv = (config < 0) ? 0 : config; |
||||
if (!UkwSetConfig(priv->dev, cv)) |
||||
return translate_driver_error(GetLastError()); |
||||
|
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
static int wince_claim_interface( |
||||
struct libusb_device_handle *handle, |
||||
int interface_number) |
||||
{ |
||||
struct wince_device_priv *priv = _device_priv(handle->dev); |
||||
|
||||
if (!UkwClaimInterface(priv->dev, interface_number)) |
||||
return translate_driver_error(GetLastError()); |
||||
|
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
static int wince_release_interface( |
||||
struct libusb_device_handle *handle, |
||||
int interface_number) |
||||
{ |
||||
struct wince_device_priv *priv = _device_priv(handle->dev); |
||||
|
||||
if (!UkwSetInterfaceAlternateSetting(priv->dev, interface_number, 0)) |
||||
return translate_driver_error(GetLastError()); |
||||
|
||||
if (!UkwReleaseInterface(priv->dev, interface_number)) |
||||
return translate_driver_error(GetLastError()); |
||||
|
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
static int wince_set_interface_altsetting( |
||||
struct libusb_device_handle *handle, |
||||
int interface_number, int altsetting) |
||||
{ |
||||
struct wince_device_priv *priv = _device_priv(handle->dev); |
||||
|
||||
if (!UkwSetInterfaceAlternateSetting(priv->dev, interface_number, altsetting)) |
||||
return translate_driver_error(GetLastError()); |
||||
|
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
static int wince_clear_halt( |
||||
struct libusb_device_handle *handle, |
||||
unsigned char endpoint) |
||||
{ |
||||
struct wince_device_priv *priv = _device_priv(handle->dev); |
||||
|
||||
if (!UkwClearHaltHost(priv->dev, endpoint)) |
||||
return translate_driver_error(GetLastError()); |
||||
|
||||
if (!UkwClearHaltDevice(priv->dev, endpoint)) |
||||
return translate_driver_error(GetLastError()); |
||||
|
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
static int wince_reset_device( |
||||
struct libusb_device_handle *handle) |
||||
{ |
||||
struct wince_device_priv *priv = _device_priv(handle->dev); |
||||
|
||||
if (!UkwResetDevice(priv->dev)) |
||||
return translate_driver_error(GetLastError()); |
||||
|
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
static int wince_kernel_driver_active( |
||||
struct libusb_device_handle *handle, |
||||
int interface_number) |
||||
{ |
||||
struct wince_device_priv *priv = _device_priv(handle->dev); |
||||
BOOL result = FALSE; |
||||
|
||||
if (!UkwKernelDriverActive(priv->dev, interface_number, &result)) |
||||
return translate_driver_error(GetLastError()); |
||||
|
||||
return result ? 1 : 0; |
||||
} |
||||
|
||||
static int wince_detach_kernel_driver( |
||||
struct libusb_device_handle *handle, |
||||
int interface_number) |
||||
{ |
||||
struct wince_device_priv *priv = _device_priv(handle->dev); |
||||
|
||||
if (!UkwDetachKernelDriver(priv->dev, interface_number)) |
||||
return translate_driver_error(GetLastError()); |
||||
|
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
static int wince_attach_kernel_driver( |
||||
struct libusb_device_handle *handle, |
||||
int interface_number) |
||||
{ |
||||
struct wince_device_priv *priv = _device_priv(handle->dev); |
||||
|
||||
if (!UkwAttachKernelDriver(priv->dev, interface_number)) |
||||
return translate_driver_error(GetLastError()); |
||||
|
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
static void wince_destroy_device(struct libusb_device *dev) |
||||
{ |
||||
struct wince_device_priv *priv = _device_priv(dev); |
||||
|
||||
UkwReleaseDeviceList(driver_handle, &priv->dev, 1); |
||||
} |
||||
|
||||
static void wince_clear_transfer_priv(struct usbi_transfer *itransfer) |
||||
{ |
||||
struct wince_transfer_priv *transfer_priv = usbi_transfer_get_os_priv(itransfer); |
||||
struct winfd wfd = fd_to_winfd(transfer_priv->pollable_fd.fd); |
||||
|
||||
// No need to cancel transfer as it is either complete or abandoned
|
||||
wfd.itransfer = NULL; |
||||
CloseHandle(wfd.handle); |
||||
usbi_free_fd(&transfer_priv->pollable_fd); |
||||
} |
||||
|
||||
static int wince_cancel_transfer(struct usbi_transfer *itransfer) |
||||
{ |
||||
struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); |
||||
struct wince_device_priv *priv = _device_priv(transfer->dev_handle->dev); |
||||
struct wince_transfer_priv *transfer_priv = usbi_transfer_get_os_priv(itransfer); |
||||
|
||||
if (!UkwCancelTransfer(priv->dev, transfer_priv->pollable_fd.overlapped, UKW_TF_NO_WAIT)) |
||||
return translate_driver_error(GetLastError()); |
||||
|
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
static int wince_submit_control_or_bulk_transfer(struct usbi_transfer *itransfer) |
||||
{ |
||||
struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); |
||||
struct libusb_context *ctx = DEVICE_CTX(transfer->dev_handle->dev); |
||||
struct wince_transfer_priv *transfer_priv = usbi_transfer_get_os_priv(itransfer); |
||||
struct wince_device_priv *priv = _device_priv(transfer->dev_handle->dev); |
||||
BOOL direction_in, ret; |
||||
struct winfd wfd; |
||||
DWORD flags; |
||||
HANDLE eventHandle; |
||||
PUKW_CONTROL_HEADER setup = NULL; |
||||
const BOOL control_transfer = transfer->type == LIBUSB_TRANSFER_TYPE_CONTROL; |
||||
|
||||
transfer_priv->pollable_fd = INVALID_WINFD; |
||||
if (control_transfer) { |
||||
setup = (PUKW_CONTROL_HEADER) transfer->buffer; |
||||
direction_in = setup->bmRequestType & LIBUSB_ENDPOINT_IN; |
||||
} else { |
||||
direction_in = transfer->endpoint & LIBUSB_ENDPOINT_IN; |
||||
} |
||||
flags = direction_in ? UKW_TF_IN_TRANSFER : UKW_TF_OUT_TRANSFER; |
||||
flags |= UKW_TF_SHORT_TRANSFER_OK; |
||||
|
||||
eventHandle = CreateEvent(NULL, FALSE, FALSE, NULL); |
||||
if (eventHandle == NULL) { |
||||
usbi_err(ctx, "Failed to create event for async transfer"); |
||||
return LIBUSB_ERROR_NO_MEM; |
||||
} |
||||
|
||||
wfd = usbi_create_fd(eventHandle, direction_in ? RW_READ : RW_WRITE, itransfer, &wince_cancel_transfer); |
||||
if (wfd.fd < 0) { |
||||
CloseHandle(eventHandle); |
||||
return LIBUSB_ERROR_NO_MEM; |
||||
} |
||||
|
||||
transfer_priv->pollable_fd = wfd; |
||||
if (control_transfer) { |
||||
// Split out control setup header and data buffer
|
||||
DWORD bufLen = transfer->length - sizeof(UKW_CONTROL_HEADER); |
||||
PVOID buf = (PVOID) &transfer->buffer[sizeof(UKW_CONTROL_HEADER)]; |
||||
|
||||
ret = UkwIssueControlTransfer(priv->dev, flags, setup, buf, bufLen, &transfer->actual_length, wfd.overlapped); |
||||
} else { |
||||
ret = UkwIssueBulkTransfer(priv->dev, flags, transfer->endpoint, transfer->buffer, |
||||
transfer->length, &transfer->actual_length, wfd.overlapped); |
||||
} |
||||
|
||||
if (!ret) { |
||||
int libusbErr = translate_driver_error(GetLastError()); |
||||
usbi_err(ctx, "UkwIssue%sTransfer failed: error %u", |
||||
control_transfer ? "Control" : "Bulk", (unsigned int)GetLastError()); |
||||
wince_clear_transfer_priv(itransfer); |
||||
return libusbErr; |
||||
} |
||||
usbi_add_pollfd(ctx, transfer_priv->pollable_fd.fd, direction_in ? POLLIN : POLLOUT); |
||||
|
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
|
||||
static int wince_submit_iso_transfer(struct usbi_transfer *itransfer) |
||||
{ |
||||
return LIBUSB_ERROR_NOT_SUPPORTED; |
||||
} |
||||
|
||||
static int wince_submit_transfer(struct usbi_transfer *itransfer) |
||||
{ |
||||
struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); |
||||
|
||||
switch (transfer->type) { |
||||
case LIBUSB_TRANSFER_TYPE_CONTROL: |
||||
case LIBUSB_TRANSFER_TYPE_BULK: |
||||
case LIBUSB_TRANSFER_TYPE_INTERRUPT: |
||||
return wince_submit_control_or_bulk_transfer(itransfer); |
||||
case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: |
||||
return wince_submit_iso_transfer(itransfer); |
||||
case LIBUSB_TRANSFER_TYPE_BULK_STREAM: |
||||
return LIBUSB_ERROR_NOT_SUPPORTED; |
||||
default: |
||||
usbi_err(TRANSFER_CTX(transfer), "unknown endpoint type %d", transfer->type); |
||||
return LIBUSB_ERROR_INVALID_PARAM; |
||||
} |
||||
} |
||||
|
||||
static void wince_transfer_callback( |
||||
struct usbi_transfer *itransfer, |
||||
uint32_t io_result, uint32_t io_size) |
||||
{ |
||||
struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); |
||||
struct wince_transfer_priv *transfer_priv = (struct wince_transfer_priv*)usbi_transfer_get_os_priv(itransfer); |
||||
struct wince_device_priv *priv = _device_priv(transfer->dev_handle->dev); |
||||
int status; |
||||
|
||||
usbi_dbg("handling I/O completion with errcode %u", io_result); |
||||
|
||||
if (io_result == ERROR_NOT_SUPPORTED && |
||||
transfer->type != LIBUSB_TRANSFER_TYPE_CONTROL) { |
||||
/* For functional stalls, the WinCE USB layer (and therefore the USB Kernel Wrapper
|
||||
* Driver) will report USB_ERROR_STALL/ERROR_NOT_SUPPORTED in situations where the |
||||
* endpoint isn't actually stalled. |
||||
* |
||||
* One example of this is that some devices will occasionally fail to reply to an IN |
||||
* token. The WinCE USB layer carries on with the transaction until it is completed |
||||
* (or cancelled) but then completes it with USB_ERROR_STALL. |
||||
* |
||||
* This code therefore needs to confirm that there really is a stall error, by both |
||||
* checking the pipe status and requesting the endpoint status from the device. |
||||
*/ |
||||
BOOL halted = FALSE; |
||||
usbi_dbg("checking I/O completion with errcode ERROR_NOT_SUPPORTED is really a stall"); |
||||
if (UkwIsPipeHalted(priv->dev, transfer->endpoint, &halted)) { |
||||
/* Pipe status retrieved, so now request endpoint status by sending a GET_STATUS
|
||||
* control request to the device. This is done synchronously, which is a bit |
||||
* naughty, but this is a special corner case. |
||||
*/ |
||||
WORD wStatus = 0; |
||||
DWORD written = 0; |
||||
UKW_CONTROL_HEADER ctrlHeader; |
||||
ctrlHeader.bmRequestType = LIBUSB_REQUEST_TYPE_STANDARD | |
||||
LIBUSB_ENDPOINT_IN | LIBUSB_RECIPIENT_ENDPOINT; |
||||
ctrlHeader.bRequest = LIBUSB_REQUEST_GET_STATUS; |
||||
ctrlHeader.wValue = 0; |
||||
ctrlHeader.wIndex = transfer->endpoint; |
||||
ctrlHeader.wLength = sizeof(wStatus); |
||||
if (UkwIssueControlTransfer(priv->dev, |
||||
UKW_TF_IN_TRANSFER | UKW_TF_SEND_TO_ENDPOINT, |
||||
&ctrlHeader, &wStatus, sizeof(wStatus), &written, NULL)) { |
||||
if (written == sizeof(wStatus) && |
||||
(wStatus & STATUS_HALT_FLAG) == 0) { |
||||
if (!halted || UkwClearHaltHost(priv->dev, transfer->endpoint)) { |
||||
usbi_dbg("Endpoint doesn't appear to be stalled, overriding error with success"); |
||||
io_result = ERROR_SUCCESS; |
||||
} else { |
||||
usbi_dbg("Endpoint doesn't appear to be stalled, but the host is halted, changing error"); |
||||
io_result = ERROR_IO_DEVICE; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
switch(io_result) { |
||||
case ERROR_SUCCESS: |
||||
itransfer->transferred += io_size; |
||||
status = LIBUSB_TRANSFER_COMPLETED; |
||||
break; |
||||
case ERROR_CANCELLED: |
||||
usbi_dbg("detected transfer cancel"); |
||||
status = LIBUSB_TRANSFER_CANCELLED; |
||||
break; |
||||
case ERROR_NOT_SUPPORTED: |
||||
case ERROR_GEN_FAILURE: |
||||
usbi_dbg("detected endpoint stall"); |
||||
status = LIBUSB_TRANSFER_STALL; |
||||
break; |
||||
case ERROR_SEM_TIMEOUT: |
||||
usbi_dbg("detected semaphore timeout"); |
||||
status = LIBUSB_TRANSFER_TIMED_OUT; |
||||
break; |
||||
case ERROR_OPERATION_ABORTED: |
||||
usbi_dbg("detected operation aborted"); |
||||
status = LIBUSB_TRANSFER_CANCELLED; |
||||
break; |
||||
default: |
||||
usbi_err(ITRANSFER_CTX(itransfer), "detected I/O error: %s", windows_error_str(io_result)); |
||||
status = LIBUSB_TRANSFER_ERROR; |
||||
break; |
||||
} |
||||
|
||||
wince_clear_transfer_priv(itransfer); |
||||
if (status == LIBUSB_TRANSFER_CANCELLED) |
||||
usbi_handle_transfer_cancellation(itransfer); |
||||
else |
||||
usbi_handle_transfer_completion(itransfer, (enum libusb_transfer_status)status); |
||||
} |
||||
|
||||
static void wince_handle_callback( |
||||
struct usbi_transfer *itransfer, |
||||
uint32_t io_result, uint32_t io_size) |
||||
{ |
||||
struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); |
||||
|
||||
switch (transfer->type) { |
||||
case LIBUSB_TRANSFER_TYPE_CONTROL: |
||||
case LIBUSB_TRANSFER_TYPE_BULK: |
||||
case LIBUSB_TRANSFER_TYPE_INTERRUPT: |
||||
case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: |
||||
wince_transfer_callback (itransfer, io_result, io_size); |
||||
break; |
||||
case LIBUSB_TRANSFER_TYPE_BULK_STREAM: |
||||
break; |
||||
default: |
||||
usbi_err(ITRANSFER_CTX(itransfer), "unknown endpoint type %d", transfer->type); |
||||
} |
||||
} |
||||
|
||||
static int wince_handle_events( |
||||
struct libusb_context *ctx, |
||||
struct pollfd *fds, POLL_NFDS_TYPE nfds, int num_ready) |
||||
{ |
||||
struct wince_transfer_priv* transfer_priv = NULL; |
||||
POLL_NFDS_TYPE i = 0; |
||||
BOOL found = FALSE; |
||||
struct usbi_transfer *transfer; |
||||
DWORD io_size, io_result; |
||||
int r = LIBUSB_SUCCESS; |
||||
|
||||
usbi_mutex_lock(&ctx->open_devs_lock); |
||||
for (i = 0; i < nfds && num_ready > 0; i++) { |
||||
|
||||
usbi_dbg("checking fd %d with revents = %04x", fds[i].fd, fds[i].revents); |
||||
|
||||
if (!fds[i].revents) |
||||
continue; |
||||
|
||||
num_ready--; |
||||
|
||||
// Because a Windows OVERLAPPED is used for poll emulation,
|
||||
// a pollable fd is created and stored with each transfer
|
||||
usbi_mutex_lock(&ctx->flying_transfers_lock); |
||||
list_for_each_entry(transfer, &ctx->flying_transfers, list, struct usbi_transfer) { |
||||
transfer_priv = usbi_transfer_get_os_priv(transfer); |
||||
if (transfer_priv->pollable_fd.fd == fds[i].fd) { |
||||
found = TRUE; |
||||
break; |
||||
} |
||||
} |
||||
usbi_mutex_unlock(&ctx->flying_transfers_lock); |
||||
|
||||
if (found && HasOverlappedIoCompleted(transfer_priv->pollable_fd.overlapped)) { |
||||
io_result = (DWORD)transfer_priv->pollable_fd.overlapped->Internal; |
||||
io_size = (DWORD)transfer_priv->pollable_fd.overlapped->InternalHigh; |
||||
usbi_remove_pollfd(ctx, transfer_priv->pollable_fd.fd); |
||||
// let handle_callback free the event using the transfer wfd
|
||||
// If you don't use the transfer wfd, you run a risk of trying to free a
|
||||
// newly allocated wfd that took the place of the one from the transfer.
|
||||
wince_handle_callback(transfer, io_result, io_size); |
||||
} else if (found) { |
||||
usbi_err(ctx, "matching transfer for fd %d has not completed", fds[i]); |
||||
r = LIBUSB_ERROR_OTHER; |
||||
break; |
||||
} else { |
||||
usbi_err(ctx, "could not find a matching transfer for fd %d", fds[i]); |
||||
r = LIBUSB_ERROR_NOT_FOUND; |
||||
break; |
||||
} |
||||
} |
||||
usbi_mutex_unlock(&ctx->open_devs_lock); |
||||
|
||||
return r; |
||||
} |
||||
|
||||
/*
|
||||
* Monotonic and real time functions |
||||
*/ |
||||
static int wince_clock_gettime(int clk_id, struct timespec *tp) |
||||
{ |
||||
LARGE_INTEGER hires_counter; |
||||
ULARGE_INTEGER rtime; |
||||
FILETIME filetime; |
||||
SYSTEMTIME st; |
||||
|
||||
switch(clk_id) { |
||||
case USBI_CLOCK_MONOTONIC: |
||||
if (hires_frequency != 0 && QueryPerformanceCounter(&hires_counter)) { |
||||
tp->tv_sec = (long)(hires_counter.QuadPart / hires_frequency); |
||||
tp->tv_nsec = (long)(((hires_counter.QuadPart % hires_frequency) / 1000) * hires_ticks_to_ps); |
||||
return LIBUSB_SUCCESS; |
||||
} |
||||
// Fall through and return real-time if monotonic read failed or was not detected @ init
|
||||
case USBI_CLOCK_REALTIME: |
||||
// We follow http://msdn.microsoft.com/en-us/library/ms724928%28VS.85%29.aspx
|
||||
// with a predef epoch time to have an epoch that starts at 1970.01.01 00:00
|
||||
// Note however that our resolution is bounded by the Windows system time
|
||||
// functions and is at best of the order of 1 ms (or, usually, worse)
|
||||
GetSystemTime(&st); |
||||
SystemTimeToFileTime(&st, &filetime); |
||||
rtime.LowPart = filetime.dwLowDateTime; |
||||
rtime.HighPart = filetime.dwHighDateTime; |
||||
rtime.QuadPart -= EPOCH_TIME; |
||||
tp->tv_sec = (long)(rtime.QuadPart / 10000000); |
||||
tp->tv_nsec = (long)((rtime.QuadPart % 10000000)*100); |
||||
return LIBUSB_SUCCESS; |
||||
default: |
||||
return LIBUSB_ERROR_INVALID_PARAM; |
||||
} |
||||
} |
||||
|
||||
const struct usbi_os_backend wince_backend = { |
||||
"Windows CE", |
||||
0, |
||||
wince_init, |
||||
wince_exit, |
||||
|
||||
wince_get_device_list, |
||||
NULL, /* hotplug_poll */ |
||||
wince_open, |
||||
wince_close, |
||||
|
||||
wince_get_device_descriptor, |
||||
wince_get_active_config_descriptor, |
||||
wince_get_config_descriptor, |
||||
NULL, /* get_config_descriptor_by_value() */ |
||||
|
||||
wince_get_configuration, |
||||
wince_set_configuration, |
||||
wince_claim_interface, |
||||
wince_release_interface, |
||||
|
||||
wince_set_interface_altsetting, |
||||
wince_clear_halt, |
||||
wince_reset_device, |
||||
|
||||
NULL, /* alloc_streams */ |
||||
NULL, /* free_streams */ |
||||
|
||||
NULL, /* dev_mem_alloc() */ |
||||
NULL, /* dev_mem_free() */ |
||||
|
||||
wince_kernel_driver_active, |
||||
wince_detach_kernel_driver, |
||||
wince_attach_kernel_driver, |
||||
|
||||
wince_destroy_device, |
||||
|
||||
wince_submit_transfer, |
||||
wince_cancel_transfer, |
||||
wince_clear_transfer_priv, |
||||
|
||||
wince_handle_events, |
||||
NULL, /* handle_transfer_completion() */ |
||||
|
||||
wince_clock_gettime, |
||||
sizeof(struct wince_device_priv), |
||||
0, |
||||
sizeof(struct wince_transfer_priv), |
||||
}; |
@ -0,0 +1,126 @@ |
||||
/*
|
||||
* Windows CE backend for libusb 1.0 |
||||
* Copyright © 2011-2013 RealVNC Ltd. |
||||
* Portions taken from Windows backend, which is |
||||
* Copyright © 2009-2010 Pete Batard <pbatard@gmail.com> |
||||
* With contributions from Michael Plante, Orin Eman et al. |
||||
* Parts of this code adapted from libusb-win32-v1 by Stephan Meyer |
||||
* Major code testing contribution by Xiaofan Chen |
||||
* |
||||
* This library is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU Lesser General Public |
||||
* License as published by the Free Software Foundation; either |
||||
* version 2.1 of the License, or (at your option) any later version. |
||||
* |
||||
* This library is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||||
* Lesser General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Lesser General Public |
||||
* License along with this library; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||||
*/ |
||||
#pragma once |
||||
|
||||
#include "windows_common.h" |
||||
|
||||
#include <windows.h> |
||||
#include "poll_windows.h" |
||||
|
||||
#define MAX_DEVICE_COUNT 256 |
||||
|
||||
// This is a modified dump of the types in the ceusbkwrapper.h library header
|
||||
// with functions transformed into extern pointers.
|
||||
//
|
||||
// This backend dynamically loads ceusbkwrapper.dll and doesn't include
|
||||
// ceusbkwrapper.h directly to simplify the build process. The kernel
|
||||
// side wrapper driver is built using the platform image build tools,
|
||||
// which makes it difficult to reference directly from the libusb build
|
||||
// system.
|
||||
struct UKW_DEVICE_PRIV; |
||||
typedef struct UKW_DEVICE_PRIV *UKW_DEVICE; |
||||
typedef UKW_DEVICE *PUKW_DEVICE, *LPUKW_DEVICE; |
||||
|
||||
typedef struct { |
||||
UINT8 bLength; |
||||
UINT8 bDescriptorType; |
||||
UINT16 bcdUSB; |
||||
UINT8 bDeviceClass; |
||||
UINT8 bDeviceSubClass; |
||||
UINT8 bDeviceProtocol; |
||||
UINT8 bMaxPacketSize0; |
||||
UINT16 idVendor; |
||||
UINT16 idProduct; |
||||
UINT16 bcdDevice; |
||||
UINT8 iManufacturer; |
||||
UINT8 iProduct; |
||||
UINT8 iSerialNumber; |
||||
UINT8 bNumConfigurations; |
||||
} UKW_DEVICE_DESCRIPTOR, *PUKW_DEVICE_DESCRIPTOR, *LPUKW_DEVICE_DESCRIPTOR; |
||||
|
||||
typedef struct { |
||||
UINT8 bmRequestType; |
||||
UINT8 bRequest; |
||||
UINT16 wValue; |
||||
UINT16 wIndex; |
||||
UINT16 wLength; |
||||
} UKW_CONTROL_HEADER, *PUKW_CONTROL_HEADER, *LPUKW_CONTROL_HEADER; |
||||
|
||||
// Collection of flags which can be used when issuing transfer requests
|
||||
/* Indicates that the transfer direction is 'in' */ |
||||
#define UKW_TF_IN_TRANSFER 0x00000001 |
||||
/* Indicates that the transfer direction is 'out' */ |
||||
#define UKW_TF_OUT_TRANSFER 0x00000000 |
||||
/* Specifies that the transfer should complete as soon as possible,
|
||||
* even if no OVERLAPPED structure has been provided. */ |
||||
#define UKW_TF_NO_WAIT 0x00000100 |
||||
/* Indicates that transfers shorter than the buffer are ok */ |
||||
#define UKW_TF_SHORT_TRANSFER_OK 0x00000200 |
||||
#define UKW_TF_SEND_TO_DEVICE 0x00010000 |
||||
#define UKW_TF_SEND_TO_INTERFACE 0x00020000 |
||||
#define UKW_TF_SEND_TO_ENDPOINT 0x00040000 |
||||
/* Don't block when waiting for memory allocations */ |
||||
#define UKW_TF_DONT_BLOCK_FOR_MEM 0x00080000 |
||||
|
||||
/* Value to use when dealing with configuration values, such as UkwGetConfigDescriptor,
|
||||
* to specify the currently active configuration for the device. */ |
||||
#define UKW_ACTIVE_CONFIGURATION -1 |
||||
|
||||
DLL_DECLARE_HANDLE(ceusbkwrapper); |
||||
DLL_DECLARE_FUNC(WINAPI, HANDLE, UkwOpenDriver, ()); |
||||
DLL_DECLARE_FUNC(WINAPI, BOOL, UkwGetDeviceList, (HANDLE, LPUKW_DEVICE, DWORD, LPDWORD)); |
||||
DLL_DECLARE_FUNC(WINAPI, void, UkwReleaseDeviceList, (HANDLE, LPUKW_DEVICE, DWORD)); |
||||
DLL_DECLARE_FUNC(WINAPI, BOOL, UkwGetDeviceAddress, (UKW_DEVICE, unsigned char*, unsigned char*, unsigned long*)); |
||||
DLL_DECLARE_FUNC(WINAPI, BOOL, UkwGetDeviceDescriptor, (UKW_DEVICE, LPUKW_DEVICE_DESCRIPTOR)); |
||||
DLL_DECLARE_FUNC(WINAPI, BOOL, UkwGetConfigDescriptor, (UKW_DEVICE, DWORD, LPVOID, DWORD, LPDWORD)); |
||||
DLL_DECLARE_FUNC(WINAPI, void, UkwCloseDriver, (HANDLE)); |
||||
DLL_DECLARE_FUNC(WINAPI, BOOL, UkwCancelTransfer, (UKW_DEVICE, LPOVERLAPPED, DWORD)); |
||||
DLL_DECLARE_FUNC(WINAPI, BOOL, UkwIssueControlTransfer, (UKW_DEVICE, DWORD, LPUKW_CONTROL_HEADER, LPVOID, DWORD, LPDWORD, LPOVERLAPPED)); |
||||
DLL_DECLARE_FUNC(WINAPI, BOOL, UkwClaimInterface, (UKW_DEVICE, DWORD)); |
||||
DLL_DECLARE_FUNC(WINAPI, BOOL, UkwReleaseInterface, (UKW_DEVICE, DWORD)); |
||||
DLL_DECLARE_FUNC(WINAPI, BOOL, UkwSetInterfaceAlternateSetting, (UKW_DEVICE, DWORD, DWORD)); |
||||
DLL_DECLARE_FUNC(WINAPI, BOOL, UkwClearHaltHost, (UKW_DEVICE, UCHAR)); |
||||
DLL_DECLARE_FUNC(WINAPI, BOOL, UkwClearHaltDevice, (UKW_DEVICE, UCHAR)); |
||||
DLL_DECLARE_FUNC(WINAPI, BOOL, UkwGetConfig, (UKW_DEVICE, PUCHAR)); |
||||
DLL_DECLARE_FUNC(WINAPI, BOOL, UkwSetConfig, (UKW_DEVICE, UCHAR)); |
||||
DLL_DECLARE_FUNC(WINAPI, BOOL, UkwResetDevice, (UKW_DEVICE)); |
||||
DLL_DECLARE_FUNC(WINAPI, BOOL, UkwKernelDriverActive, (UKW_DEVICE, DWORD, PBOOL)); |
||||
DLL_DECLARE_FUNC(WINAPI, BOOL, UkwAttachKernelDriver, (UKW_DEVICE, DWORD)); |
||||
DLL_DECLARE_FUNC(WINAPI, BOOL, UkwDetachKernelDriver, (UKW_DEVICE, DWORD)); |
||||
DLL_DECLARE_FUNC(WINAPI, BOOL, UkwIssueBulkTransfer, (UKW_DEVICE, DWORD, UCHAR, LPVOID, DWORD, LPDWORD, LPOVERLAPPED)); |
||||
DLL_DECLARE_FUNC(WINAPI, BOOL, UkwIsPipeHalted, (UKW_DEVICE, UCHAR, LPBOOL)); |
||||
|
||||
// Used to determine if an endpoint status really is halted on a failed transfer.
|
||||
#define STATUS_HALT_FLAG 0x1 |
||||
|
||||
struct wince_device_priv { |
||||
UKW_DEVICE dev; |
||||
UKW_DEVICE_DESCRIPTOR desc; |
||||
}; |
||||
|
||||
struct wince_transfer_priv { |
||||
struct winfd pollable_fd; |
||||
uint8_t interface_number; |
||||
}; |
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue