blob: 47e7a84cac2ec45f0dc4beb3419f117d175f92a3 [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Package uhid supports creating, handling and destroying devices created
// via /dev/uhid.
package uhid
import (
"bytes"
"context"
"encoding/binary"
"fmt"
"os"
"time"
"github.com/google/uuid"
"chromiumos/tast/errors"
)
const (
// hidMaxDescriptorSize represents the maximum length of a
// descriptor or an event injected. It matches UHID_DATA_MAX
// from uhid.h.
hidMaxDescriptorSize = 4096
// uhidEventSize refers to the size of struct uhid_event from
// uhid.h. This is the struct that is always written by the
// kernel to /dev/uhid.
uhidEventSize = 4380
)
// EventType is the type used to encapsulate the different request
// types that can be written by the kernel or user to /dev/uhid.
type EventType uint32
const (
// The following constants are from enum uhid_event_type in uhid.h.
// These constants are used for event handlers. If the user wishes
// to handle an event UHIDEvent for a device d using the handler
// function handlerFunction then the corresponding handler must be
// set in the EventHandlers map like so:
// d.EventHandlers[uhid.UHIDEvent] = handlerFunction
// Destroy destroys the device freeing up it's resources.
Destroy EventType = 1
// Start is written by the kernel to acknowledge the creation of
// a device.
Start = 2
// Stop is written by the kernel to acknowledge the destruction
// of a device.
Stop = 3
// Open is written by the kernel to signal that the data being
// provided by the device is being read.
Open = 4
// Close is written by the kernel to signal that no more processes
// are reading this device's data.
Close = 5
// Output is written by the kernel to signal that the HID device
// driver wants to send raw data to the I/O device on the interrupt
// channel.
Output = 6
// GetReport is written by the kernel to signal that the kernel
// driver wants to perform a GET_REPORT request on the control
// channeld as described in the HID specs.
GetReport = 9
// GetReportReply must be written by the user as a reply to a
// UHIDGetReport request.
GetReportReply = 10
// Create2 is written by the user to create a device.
Create2 = 11
// Input2 is used to inject events to the device.
Input2 = 12
// SetReport is written by the kernel to signal that the kernel
// driver wants to perform a SET_REPORT request on the control
// channeld as described in the HID specs.
SetReport = 13
// SetReportReply must be written by the user as a reply to a
// SetReport request.
SetReportReply = 14
)
// ReadStatus is returned by Dispatch to signal the multiple
// results of reading from /dev/uhid
type ReadStatus uint8
const (
// StatusOK signals that an event was read and no problem was
// encountered.
StatusOK ReadStatus = iota
// StatusNoEvent signals that no event was read.
StatusNoEvent
)
// RNumType is the type used for the rnum field in get report
// requests.
type RNumType uint8
// GetReportRequest replicates struct uhid_get_report_req in uhid.h.
// It is used to read GetReport requests written by the kernel and
// handling them afterwards if necessary.
type GetReportRequest struct {
RequestType uint32
ID uint32
RNum RNumType
RType uint8
}
// GetReportReplyRequest replicates struct uhid_get_report_reply_req
// in uhid.h. It should be written to Device.File in response to a
// GetReportRequest by the kernel.
type GetReportReplyRequest struct {
RequestType uint32
ID uint32
Err uint16
DataSize uint16
Data [hidMaxDescriptorSize]byte
}
// uhidCreate2Request replicates struct uhid_create2_req in uhid.h.
// Create requests are written into /dev/uhid in order to create a
// virtual HID device. This device will have the given name and IDs as
// well as respond to the given HID descriptor.
type uhidCreate2Request struct {
requestType uint32
name [128]byte
phys [64]byte
uniq [64]byte
descriptorSize uint16
bus uint16
vendorID uint32
productID uint32
version uint32
country uint32
descriptor [hidMaxDescriptorSize]byte
}
// DeviceData encapsulates the non-trivial data that will then be
// copied over to a create request or be used to get information from
// the device. The fixed size byte arrays are meant to replicate those
// in struct uhid_create2_req in uhid.h.
type DeviceData struct {
name [128]byte
phys [64]byte
uniq [64]byte
descriptor [hidMaxDescriptorSize]byte
bus uint16
vendorID uint32
productID uint32
}
type eventHandler func(ctx context.Context, d *Device, buf []byte) error
// Device is the main interface carrying all of the created (or soon
// to be created) kernel device's information.
type Device struct {
Data DeviceData
hidrawNodes []string
eventNodes []string
file *os.File
// EventHandlers is used on a call to Dispatch to call the
// corresponding handling function. If the user wishes to handle a
// particular event then they must assign their handler function to
// EventHandlers[UHIDEvent] where UHIDEvent is one of the UHID
// constants defined above.
EventHandlers map[uint32]eventHandler
}
// Input2Request replicates struct uhid_input2_req in uhid.h.
// An input request is used to inject events into the created device.
type Input2Request struct {
RequestType uint32
DataSize uint16
Data [hidMaxDescriptorSize]uint8
}
// NewDevice returns a device with the given name and descriptor.
func NewDevice(name, descriptor string) (*Device, error) {
if len(name) > 128 {
return nil, errors.Errorf("device name too long: got %d want %d or shorter", len(name), 128)
}
if len(descriptor) > hidMaxDescriptorSize {
return nil, errors.Errorf("device descriptor too long: got %d want %d or shorter", len(descriptor), hidMaxDescriptorSize)
}
d := Device{}
copy(d.Data.name[:], name)
copy(d.Data.descriptor[:], descriptor)
return &d, nil
}
// NewKernelDevice creates a device with the attributes specified in
// d. Only after calling this function will the device be ready for
// the other operations.
func (d *Device) NewKernelDevice(ctx context.Context) error {
if d.Data.name == [128]byte{} || d.Data.descriptor == [hidMaxDescriptorSize]byte{} {
return errors.New("device has not been initialized")
}
var err error
if d.file, err = os.OpenFile("/dev/uhid", os.O_RDWR, 0644); err != nil {
return errors.Wrap(err, "failed opening /dev/uhid file")
}
// Check if uniq is empty.
if d.Data.uniq == [64]byte{} {
uniq, _ := uuid.NewRandom()
copy(d.Data.uniq[:], uniq[:])
}
if err = d.WriteEvent(d.Data.createRequest()); err != nil {
return errors.Wrap(err, "failed writing uhid create request")
}
d.setDefaultHandlers()
var status ReadStatus
status, err = d.Dispatch(ctx)
if status != StatusOK || err != nil {
return errors.Wrap(err, "kernel failed at creating the device")
}
return nil
}
// Close destroys the device specified in d by writing a destroy
// request to /dev/uhid. The file as well as the hidraw and event nodes
// are cleared.
func (d *Device) Close() error {
if d.file != nil {
if err := d.WriteEvent(Destroy); err != nil {
return errors.Wrap(err, "failed writing uhid destroy request")
}
if err := d.file.Close(); err != nil {
return errors.Wrap(err, "failed closing file during device destruction")
}
}
return nil
}
// InjectEvent Injects an event into an existing device. The data array
// will vary from device to device.
func (d *Device) InjectEvent(data []uint8) error {
if d.file == nil {
return errors.New("device has not been initialized")
}
req := Input2Request{}
req.RequestType = Input2
req.DataSize = uint16(len(data))
copy(req.Data[:len(data)], data)
if err := d.WriteEvent(req); err != nil {
return errors.Wrap(err, "failed writing input2 request")
}
return nil
}
// HidrawNodes returns the /dev/hidraw* paths associated to this
// device.
func (d *Device) HidrawNodes(ctx context.Context) ([]string, error) {
if d.file == nil {
return nil, errors.New("device has not been initialized")
}
if d.hidrawNodes == nil {
if err := deviceNodes(ctx, d); err != nil {
return nil, err
}
}
return d.hidrawNodes, nil
}
// EventNodes returns the /dev/input/event* paths associated to this
// device.
func (d *Device) EventNodes(ctx context.Context) ([]string, error) {
if d.file == nil {
return nil, errors.New("device has not been initialized")
}
if d.eventNodes == nil {
if err := deviceNodes(ctx, d); err != nil {
return nil, err
}
}
return d.eventNodes, nil
}
// readEvent returns a buffer with information read from the given
// device's file. All events arriving to /dev/uhid will be of the
// form of struct uhid_event from uhid.h, which has a size of
// uhidEventSize.
func (d *Device) readEvent() ([]byte, error) {
if d.file == nil {
return nil, errors.New("device has not been initialized")
}
buf := make([]byte, uhidEventSize)
// Calls to file.Read block, this should be fixed.
n, err := d.file.Read(buf)
if err != nil {
return buf, err
}
if n != uhidEventSize {
return buf, errors.Errorf("unexpected number of bytes of UHID event; got %d, want %d", n, uhidEventSize)
}
return buf, nil
}
// WriteEvent will write the struct given in i into /dev/uhid and
// return an error if unsuccessful.
func (d *Device) WriteEvent(i interface{}) error {
if d.file == nil {
return errors.New("device has not been initialized")
}
buf := new(bytes.Buffer)
err := binary.Write(buf, binary.LittleEndian, i)
if err != nil {
return err
}
_, err = d.file.Write(buf.Bytes())
if err != nil {
return err
}
return nil
}
// Dispatch must be called when an event needs to be handled. Be sure
// to implement some method of checking if the event you wish to
// handle was indeed the one handled.
func (d *Device) Dispatch(ctx context.Context) (ReadStatus, error) {
if d.file == nil {
return StatusOK, errors.New("device has not been initialized")
}
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
done := make(chan struct{})
var buf []byte
var err error
go func() {
buf, err = d.readEvent()
close(done)
}()
select {
case <-done:
if err != nil {
return StatusOK, errors.Wrap(err, "failed reading uhid event")
}
reader := bytes.NewReader(buf[:4]) // We just want to read the first uint32 for now
var requestType uint32
if err = binary.Read(reader, binary.LittleEndian, &requestType); err != nil {
return StatusOK, errors.Wrap(err, "failed parsing uhid file data into request")
}
return StatusOK, d.processEvent(ctx, buf, requestType)
case <-ctx.Done():
return StatusNoEvent, nil
}
}
// processEvent selects the correct function to handle the request
// sent by the kernel and runs it. It will return an error if the
// event us unrecognized.
func (d *Device) processEvent(ctx context.Context, buf []byte, requestType uint32) error {
if f := d.EventHandlers[requestType]; f != nil {
return f(ctx, d, buf)
}
return errors.Errorf("unknown event: %d", requestType)
}
// setDefaultHandlers assigns to d's EventHandlers map handlers that
// simply ignore the given event.
func (d *Device) setDefaultHandlers() {
d.EventHandlers = map[uint32]eventHandler{
Start: defaultHandler,
Stop: defaultHandler,
Open: defaultHandler,
Close: defaultHandler,
Output: defaultHandler,
GetReport: defaultHandler,
SetReport: defaultHandler,
}
}
// Name returns the name of the device.
func (d *Device) Name() string {
return string(d.Data.name[:])
}
// Phys returns the phys of the device.
func (d *Device) Phys() string {
return string(d.Data.phys[:])
}
// Uniq returns the uniq of this device.
func (d *Device) Uniq() string {
return string(d.Data.uniq[:])
}
// SetUniq sets the uniq of this device.
func (d *Device) SetUniq(uniq string) error {
if len(uniq) > 64 {
return errors.Errorf("device name too long: got %d want %d or shorter", len(uniq), 64)
}
copy(d.Data.uniq[:], uniq)
return nil
}
// Bus returns the bus of this device.
func (d *Device) Bus() uint16 {
return d.Data.bus
}
// VendorID returns the vendor id of this device.
func (d *Device) VendorID() uint32 {
return d.Data.vendorID
}
// ProductID returns the product id of this device.
func (d *Device) ProductID() uint32 {
return d.Data.productID
}
// createRequest returns a new uhidCreate2Request based on the data
// contained in deviceData.
func (dd *DeviceData) createRequest() uhidCreate2Request {
return uhidCreate2Request{
requestType: Create2,
name: dd.name,
phys: dd.phys,
uniq: dd.uniq,
descriptorSize: uint16(len(dd.descriptor)),
bus: dd.bus,
vendorID: dd.vendorID,
productID: dd.productID,
version: 0,
country: 0,
descriptor: dd.descriptor,
}
}
// defaultHandler ignores the event that it is called to handle.
func defaultHandler(ctx context.Context, d *Device, buf []byte) error {
return nil
}
// deviceNodes assigns to device d's hidRawNodes and eventNodes fields
// their corresponding nodes. These nodes refer to the
// /dev/input/event and /dev/hidraw nodes. These are obtained from
// /sys/bus/hid/devices which contains HID Device information.
func deviceNodes(ctx context.Context, d *Device) error {
devicePath, err := devicePath(d.infoString())
if err != nil {
return err
}
if d.hidrawNodes, err = hidrawNodes(ctx, devicePath); err != nil {
return err
}
d.eventNodes, err = eventNodes(devicePath)
return err
}
// infoString returns a string of the form
// <d.Data.Bus>:<d.Data.VendorID>:<d.Data.ProductID>. Information
// regarding this device will be found under
// /sys/bus/hid/devices/<infoString>.<ID> where ID is a unique ID for
// each device the kernel recognizes.
func (d *Device) infoString() string {
return fmt.Sprintf("%04X:%04X:%04X", d.Data.bus, d.Data.vendorID, d.Data.productID)
}