Rewriting the usb_hid driver. It has now gotten a driver part and a device part
structured into the HIDDevice base class and KeyboardDevice and MouseDevice subclasses. This can be extended easily to support more device types like game controllers, joysticks and other HID devices. The parsing code remains untouched while the interpretation code has been integrated into the device classes. The driver should work much the same way as before including the boot protocol only keyboard limitation. On the other hand composite devices that combine multiple devices should now work, as long as they expose those devices as two seperate interfaces. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@25657 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Driver for USB Human Interface Devices.
|
||||
Copyright (C) 2008 Michael Lotz <[email protected]>
|
||||
Distributed under the terms of the MIT license.
|
||||
*/
|
||||
#ifndef HAIKU_TARGET_PLATFORM_HAIKU
|
||||
#ifndef _BEOS_COMPATIBILITY_H_
|
||||
#define _BEOS_COMPATIBILITY_H_
|
||||
|
||||
#include <OS.h>
|
||||
|
||||
typedef struct mutex {
|
||||
sem_id sem;
|
||||
int32 count;
|
||||
} mutex;
|
||||
|
||||
|
||||
static inline void
|
||||
mutex_init(mutex *lock, const char *name)
|
||||
{
|
||||
lock->sem = create_sem(0, name);
|
||||
lock->count = 0;
|
||||
}
|
||||
|
||||
|
||||
static inline void
|
||||
mutex_destroy(mutex *lock)
|
||||
{
|
||||
delete_sem(lock->sem);
|
||||
}
|
||||
|
||||
|
||||
static inline status_t
|
||||
mutex_lock(mutex *lock)
|
||||
{
|
||||
if (atomic_add(&lock->count, -1) < 0)
|
||||
return acquire_sem(lock->sem);
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static inline void
|
||||
mutex_unlock(mutex *lock)
|
||||
{
|
||||
if (atomic_add(&lock->count, 1) < -1)
|
||||
release_sem(lock->sem);
|
||||
}
|
||||
|
||||
#endif /* !HAIKU_TARGET_PLATFORM_HAIKU */
|
||||
#endif /* _BEOS_COMPATIBILITY_H_ */
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
Generic device list for use in drivers.
|
||||
Copyright (C) 2008 Michael Lotz <[email protected]>
|
||||
Distributed under the terms of the MIT license.
|
||||
*/
|
||||
#include "DeviceList.h"
|
||||
#include <kernel_cpp.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct device_list_entry {
|
||||
char * name;
|
||||
void * device;
|
||||
device_list_entry * next;
|
||||
};
|
||||
|
||||
|
||||
DeviceList::DeviceList()
|
||||
: fDeviceList(NULL),
|
||||
fDeviceCount(0),
|
||||
fPublishList(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
DeviceList::~DeviceList()
|
||||
{
|
||||
_FreePublishList();
|
||||
|
||||
device_list_entry *current = fDeviceList;
|
||||
while (current) {
|
||||
device_list_entry *next = current->next;
|
||||
free(current->name);
|
||||
delete current;
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
DeviceList::AddDevice(const char *name, void *device)
|
||||
{
|
||||
device_list_entry *entry = new device_list_entry;
|
||||
if (entry == NULL)
|
||||
return B_NO_MEMORY;
|
||||
|
||||
entry->name = strdup(name);
|
||||
if (entry->name == NULL)
|
||||
return B_NO_MEMORY;
|
||||
|
||||
entry->device = device;
|
||||
entry->next = NULL;
|
||||
|
||||
if (fDeviceList == NULL)
|
||||
fDeviceList = entry;
|
||||
else {
|
||||
device_list_entry *current = fDeviceList;
|
||||
while (current) {
|
||||
if (current->next == NULL) {
|
||||
current->next = entry;
|
||||
break;
|
||||
}
|
||||
|
||||
current = current->next;
|
||||
}
|
||||
}
|
||||
|
||||
fDeviceCount++;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
DeviceList::RemoveDevice(const char *name, void *device)
|
||||
{
|
||||
if (name == NULL && device == NULL)
|
||||
return B_BAD_VALUE;
|
||||
|
||||
device_list_entry *previous = NULL;
|
||||
device_list_entry *current = fDeviceList;
|
||||
while (current) {
|
||||
if ((name != NULL && strcmp(current->name, name) == 0)
|
||||
|| (device != NULL && current->device == device)) {
|
||||
if (previous == NULL)
|
||||
fDeviceList = current->next;
|
||||
else
|
||||
previous->next = current->next;
|
||||
|
||||
free(current->name);
|
||||
delete current;
|
||||
fDeviceCount--;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
return B_ENTRY_NOT_FOUND;
|
||||
}
|
||||
|
||||
|
||||
void *
|
||||
DeviceList::FindDevice(const char *name, void *device)
|
||||
{
|
||||
if (name == NULL && device == NULL)
|
||||
return NULL;
|
||||
|
||||
device_list_entry *current = fDeviceList;
|
||||
while (current) {
|
||||
if ((name != NULL && strcmp(current->name, name) == 0)
|
||||
|| (device != NULL && current->device == device))
|
||||
return current->device;
|
||||
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
int32
|
||||
DeviceList::CountDevices(const char *baseName)
|
||||
{
|
||||
if (baseName == NULL)
|
||||
return fDeviceCount;
|
||||
|
||||
int32 count = 0;
|
||||
int32 baseNameLength = strlen(baseName);
|
||||
device_list_entry *current = fDeviceList;
|
||||
while (current) {
|
||||
if (strncmp(current->name, baseName, baseNameLength) == 0)
|
||||
count++;
|
||||
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
void *
|
||||
DeviceList::DeviceAt(int32 index)
|
||||
{
|
||||
device_list_entry *current = fDeviceList;
|
||||
while (current) {
|
||||
if (index-- == 0)
|
||||
return current->device;
|
||||
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
const char **
|
||||
DeviceList::PublishDevices()
|
||||
{
|
||||
_FreePublishList();
|
||||
|
||||
fPublishList = (char **)malloc((fDeviceCount + 1) * sizeof(char *));
|
||||
if (fPublishList == NULL)
|
||||
return NULL;
|
||||
|
||||
int32 index = 0;
|
||||
device_list_entry *current = fDeviceList;
|
||||
while (current) {
|
||||
fPublishList[index++] = strdup(current->name);
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
fPublishList[index] = NULL;
|
||||
return (const char **)fPublishList;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
DeviceList::_FreePublishList()
|
||||
{
|
||||
if (fPublishList == NULL)
|
||||
return;
|
||||
|
||||
int32 index = 0;
|
||||
while (fPublishList[index] != NULL) {
|
||||
free(fPublishList[index]);
|
||||
index++;
|
||||
}
|
||||
|
||||
free(fPublishList);
|
||||
fPublishList = NULL;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Generic device list for use in drivers.
|
||||
Copyright (C) 2008 Michael Lotz <[email protected]>
|
||||
Distributed under the terms of the MIT license.
|
||||
*/
|
||||
#ifndef _DEVICE_LIST_H_
|
||||
#define _DEVICE_LIST_H_
|
||||
|
||||
#include <OS.h>
|
||||
|
||||
struct device_list_entry;
|
||||
|
||||
class DeviceList {
|
||||
public:
|
||||
DeviceList();
|
||||
~DeviceList();
|
||||
|
||||
status_t AddDevice(const char *name, void *device);
|
||||
status_t RemoveDevice(const char *name, void *device = NULL);
|
||||
void * FindDevice(const char *name, void *device = NULL);
|
||||
|
||||
int32 CountDevices(const char *baseName = NULL);
|
||||
void * DeviceAt(int32 index);
|
||||
|
||||
const char ** PublishDevices();
|
||||
|
||||
private:
|
||||
void _FreePublishList();
|
||||
|
||||
device_list_entry * fDeviceList;
|
||||
int32 fDeviceCount;
|
||||
char ** fPublishList;
|
||||
};
|
||||
|
||||
#endif // _DEVICE_LIST_H_
|
||||
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
Driver for USB Human Interface Devices.
|
||||
Copyright (C) 2008 Michael Lotz <[email protected]>
|
||||
Distributed under the terms of the MIT license.
|
||||
|
||||
Some parts of the code are based on the previous usb_hid driver which
|
||||
was written by Jérôme Duval.
|
||||
*/
|
||||
#include "DeviceList.h"
|
||||
#include "Driver.h"
|
||||
#include "HIDDevice.h"
|
||||
|
||||
#ifdef HAIKU_TARGET_PLATFORM_HAIKU
|
||||
#include <lock.h> // for mutex
|
||||
#else
|
||||
#include "BeOSCompatibility.h" // for pseudo mutex
|
||||
#endif
|
||||
|
||||
|
||||
int32 api_version = B_CUR_DRIVER_API_VERSION;
|
||||
usb_module_info *gUSBModule = NULL;
|
||||
DeviceList *gDeviceList = NULL;
|
||||
static int32 sParentCookie = 0;
|
||||
static mutex sDriverLock;
|
||||
|
||||
|
||||
// #pragma mark - notify hooks
|
||||
|
||||
|
||||
static status_t
|
||||
usb_hid_device_added(usb_device device, void **cookie)
|
||||
{
|
||||
TRACE("device_added()\n");
|
||||
const usb_device_descriptor *deviceDescriptor
|
||||
= gUSBModule->get_device_descriptor(device);
|
||||
|
||||
TRACE("vendor id: 0x%04x; product id: 0x%04x\n",
|
||||
deviceDescriptor->vendor_id, deviceDescriptor->product_id);
|
||||
|
||||
// wacom devices are handled by the dedicated wacom driver
|
||||
if (deviceDescriptor->vendor_id == USB_VENDOR_WACOM)
|
||||
return B_ERROR;
|
||||
|
||||
const usb_configuration_info *config
|
||||
= gUSBModule->get_nth_configuration(device, USB_DEFAULT_CONFIGURATION);
|
||||
if (config == NULL) {
|
||||
TRACE_ALWAYS("cannot get default configuration\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
// ensure default configuration is set
|
||||
status_t result = gUSBModule->set_configuration(device, config);
|
||||
if (result != B_OK) {
|
||||
TRACE_ALWAYS("set_configuration() failed 0x%08lx\n", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// refresh config
|
||||
config = gUSBModule->get_configuration(device);
|
||||
if (config == NULL) {
|
||||
TRACE_ALWAYS("cannot get current configuration\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
bool devicesFound = false;
|
||||
int32 parentCookie = atomic_add(&sParentCookie, 1);
|
||||
for (size_t i = 0; i < config->interface_count; i++) {
|
||||
const usb_interface_info *interface = config->interface[i].active;
|
||||
uint8 interfaceClass = interface->descr->interface_class;
|
||||
uint8 interfaceSubclass = interface->descr->interface_subclass;
|
||||
uint8 interfaceProtocol = interface->descr->interface_protocol;
|
||||
TRACE("interface %lu: class: %u; subclass: %u; protocol: %u\n",
|
||||
i, interfaceClass, interfaceSubclass, interfaceProtocol);
|
||||
|
||||
if (interfaceClass == USB_INTERFACE_CLASS_HID
|
||||
&& interfaceSubclass == USB_INTERFACE_SUBCLASS_HID_BOOT) {
|
||||
mutex_lock(&sDriverLock);
|
||||
HIDDevice *hidDevice = HIDDevice::MakeHIDDevice(device, config, i);
|
||||
|
||||
if (hidDevice != NULL && hidDevice->InitCheck() == B_OK) {
|
||||
hidDevice->SetParentCookie(parentCookie);
|
||||
gDeviceList->AddDevice(hidDevice->Name(), hidDevice);
|
||||
devicesFound = true;
|
||||
} else
|
||||
delete hidDevice;
|
||||
mutex_unlock(&sDriverLock);
|
||||
}
|
||||
}
|
||||
|
||||
if (!devicesFound)
|
||||
return B_ERROR;
|
||||
|
||||
*cookie = (void *)parentCookie;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
usb_hid_device_removed(void *cookie)
|
||||
{
|
||||
mutex_lock(&sDriverLock);
|
||||
int32 parentCookie = (int32)cookie;
|
||||
TRACE("device_removed(%ld)\n", parentCookie);
|
||||
|
||||
for (int32 i = 0; i < gDeviceList->CountDevices(); i++) {
|
||||
HIDDevice *device = (HIDDevice *)gDeviceList->DeviceAt(i);
|
||||
if (!device)
|
||||
continue;
|
||||
|
||||
if (device->ParentCookie() == parentCookie) {
|
||||
// this device belongs to the one removed
|
||||
if (device->IsOpen()) {
|
||||
// the device will be deleted upon being freed
|
||||
device->Removed();
|
||||
} else {
|
||||
// remove the device and start over
|
||||
gDeviceList->RemoveDevice(NULL, device);
|
||||
i = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&sDriverLock);
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark - driver hooks
|
||||
|
||||
|
||||
static status_t
|
||||
usb_hid_open(const char *name, uint32 flags, void **cookie)
|
||||
{
|
||||
TRACE("open(%s, %lu, %p)\n", name, flags, cookie);
|
||||
mutex_lock(&sDriverLock);
|
||||
|
||||
HIDDevice *device = (HIDDevice *)gDeviceList->FindDevice(name);
|
||||
if (device == NULL) {
|
||||
mutex_unlock(&sDriverLock);
|
||||
return B_ENTRY_NOT_FOUND;
|
||||
}
|
||||
|
||||
status_t result = device->Open(flags);
|
||||
*cookie = device;
|
||||
mutex_unlock(&sDriverLock);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
usb_hid_read(void *cookie, off_t position, void *buffer, size_t *numBytes)
|
||||
{
|
||||
TRACE("read(%p, %Ld, %p, %lu)\n", cookie, position, buffer, *numBytes);
|
||||
HIDDevice *device = (HIDDevice *)cookie;
|
||||
return device->Read((uint8 *)buffer, numBytes);
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
usb_hid_write(void *cookie, off_t position, const void *buffer,
|
||||
size_t *numBytes)
|
||||
{
|
||||
TRACE("write(%p, %Ld, %p, %lu)\n", cookie, position, buffer, *numBytes);
|
||||
HIDDevice *device = (HIDDevice *)cookie;
|
||||
return device->Write((const uint8 *)buffer, numBytes);
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
usb_hid_control(void *cookie, uint32 op, void *buffer, size_t length)
|
||||
{
|
||||
TRACE("control(%p, %lu, %p, %lu)\n", cookie, op, buffer, length);
|
||||
HIDDevice *device = (HIDDevice *)cookie;
|
||||
return device->Control(op, buffer, length);
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
usb_hid_close(void *cookie)
|
||||
{
|
||||
TRACE("close(%p)\n", cookie);
|
||||
HIDDevice *device = (HIDDevice *)cookie;
|
||||
return device->Close();
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
usb_hid_free(void *cookie)
|
||||
{
|
||||
TRACE("free(%p)\n", cookie);
|
||||
HIDDevice *device = (HIDDevice *)cookie;
|
||||
mutex_lock(&sDriverLock);
|
||||
status_t status = device->Free();
|
||||
if (gDeviceList->RemoveDevice(NULL, device) == B_OK) {
|
||||
// the device is removed already but as it was open the removed hook
|
||||
// has not deleted the object
|
||||
delete device;
|
||||
}
|
||||
|
||||
mutex_unlock(&sDriverLock);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark - driver API
|
||||
|
||||
|
||||
status_t
|
||||
init_hardware()
|
||||
{
|
||||
TRACE("init_hardware() " __DATE__ " " __TIME__ "\n");
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
init_driver()
|
||||
{
|
||||
TRACE("init_driver() " __DATE__ " " __TIME__ "\n");
|
||||
if (get_module(B_USB_MODULE_NAME, (module_info **)&gUSBModule) != B_OK)
|
||||
return B_ERROR;
|
||||
|
||||
gDeviceList = new DeviceList();
|
||||
if (gDeviceList == NULL) {
|
||||
put_module(B_USB_MODULE_NAME);
|
||||
return B_NO_MEMORY;
|
||||
}
|
||||
|
||||
static usb_notify_hooks notifyHooks = {
|
||||
&usb_hid_device_added,
|
||||
&usb_hid_device_removed
|
||||
};
|
||||
|
||||
static usb_support_descriptor supportDescriptor = {
|
||||
USB_INTERFACE_CLASS_HID, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
gUSBModule->register_driver(DRIVER_NAME, &supportDescriptor, 1, NULL);
|
||||
gUSBModule->install_notify(DRIVER_NAME, ¬ifyHooks);
|
||||
TRACE("init_driver() OK\n");
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
uninit_driver()
|
||||
{
|
||||
TRACE("uninit_driver()\n");
|
||||
gUSBModule->uninstall_notify(DRIVER_NAME);
|
||||
put_module(B_USB_MODULE_NAME);
|
||||
delete gDeviceList;
|
||||
gDeviceList = NULL;
|
||||
}
|
||||
|
||||
|
||||
const char **
|
||||
publish_devices()
|
||||
{
|
||||
TRACE("publish_devices()\n");
|
||||
const char **publishList = gDeviceList->PublishDevices();
|
||||
|
||||
int32 index = 0;
|
||||
while (publishList[index] != NULL) {
|
||||
TRACE("publishing %s\n", publishList[index]);
|
||||
index++;
|
||||
}
|
||||
|
||||
return publishList;
|
||||
}
|
||||
|
||||
|
||||
device_hooks *
|
||||
find_device(const char *name)
|
||||
{
|
||||
static device_hooks hooks = {
|
||||
usb_hid_open,
|
||||
usb_hid_close,
|
||||
usb_hid_free,
|
||||
usb_hid_control,
|
||||
usb_hid_read,
|
||||
usb_hid_write,
|
||||
NULL, /* select */
|
||||
NULL /* deselect */
|
||||
};
|
||||
|
||||
TRACE("find_device(%s)\n", name);
|
||||
if (gDeviceList->FindDevice(name) == NULL) {
|
||||
TRACE_ALWAYS("didn't find device %s\n", name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &hooks;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Driver for USB Human Interface Devices.
|
||||
Copyright (C) 2008 Michael Lotz <[email protected]>
|
||||
Distributed under the terms of the MIT license.
|
||||
*/
|
||||
#ifndef _USB_HID_DRIVER_H_
|
||||
#define _USB_HID_DRIVER_H_
|
||||
|
||||
#include <Drivers.h>
|
||||
#include <KernelExport.h>
|
||||
#include <OS.h>
|
||||
#include <USB3.h>
|
||||
|
||||
#include "DeviceList.h"
|
||||
#include "kernel_cpp.h"
|
||||
|
||||
#define DRIVER_NAME "usb_hid"
|
||||
|
||||
#define USB_INTERFACE_CLASS_HID 3
|
||||
#define USB_INTERFACE_SUBCLASS_HID_BOOT 1
|
||||
#define USB_DEFAULT_CONFIGURATION 0
|
||||
#define USB_VENDOR_WACOM 0x056a
|
||||
|
||||
#define USB_HID_DEVICE_TYPE_KEYBOARD 0x06090105
|
||||
#define USB_HID_DEVICE_TYPE_MOUSE 0x02090105
|
||||
|
||||
extern usb_module_info *gUSBModule;
|
||||
extern DeviceList *gDeviceList;
|
||||
|
||||
extern "C" {
|
||||
status_t usb_hid_device_added(usb_device device, void **cookie);
|
||||
status_t usb_hid_device_removed(void *cookie);
|
||||
|
||||
status_t init_hardware();
|
||||
void uninit_driver();
|
||||
const char ** publish_devices();
|
||||
device_hooks * find_device(const char *name);
|
||||
}
|
||||
|
||||
#define TRACE(x...) /*dprintf(DRIVER_NAME ": " x)*/
|
||||
#define TRACE_ALWAYS(x...) dprintf(DRIVER_NAME ": " x)
|
||||
|
||||
#endif //_USB_HID_DRIVER_H_
|
||||
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
Driver for USB Human Interface Devices.
|
||||
Copyright (C) 2008 Michael Lotz <[email protected]>
|
||||
Distributed under the terms of the MIT license.
|
||||
*/
|
||||
#include "Driver.h"
|
||||
#include "HIDDevice.h"
|
||||
#include <usb/USB_hid.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
// includes for the different device types
|
||||
#include "KeyboardDevice.h"
|
||||
#include "MouseDevice.h"
|
||||
|
||||
|
||||
HIDDevice::HIDDevice(usb_device device, usb_pipe interruptPipe,
|
||||
size_t interfaceIndex, report_insn *instructions, size_t instructionCount,
|
||||
size_t totalReportSize, size_t ringBufferSize)
|
||||
: fStatus(B_NO_INIT),
|
||||
fDevice(device),
|
||||
fInterruptPipe(interruptPipe),
|
||||
fInterfaceIndex(interfaceIndex),
|
||||
fInstructions(instructions),
|
||||
fInstructionCount(instructionCount),
|
||||
fTotalReportSize(totalReportSize),
|
||||
fTransferUnprocessed(false),
|
||||
fTransferStatus(B_ERROR),
|
||||
fTransferActualLength(0),
|
||||
fTransferBuffer(NULL),
|
||||
fTransferNotifySem(-1),
|
||||
fName(NULL),
|
||||
fParentCookie(-1),
|
||||
fOpen(false),
|
||||
fRemoved(false),
|
||||
fRingBuffer(NULL)
|
||||
{
|
||||
if (ringBufferSize > 0)
|
||||
fRingBuffer = create_ring_buffer(ringBufferSize);
|
||||
|
||||
fTransferNotifySem = create_sem(0, "hid device transfer notify sem");
|
||||
if (fTransferNotifySem < B_OK) {
|
||||
fStatus = fTransferNotifySem;
|
||||
return;
|
||||
}
|
||||
|
||||
fTransferBuffer = (uint8 *)malloc(fTotalReportSize);
|
||||
if (fTransferBuffer == NULL) {
|
||||
fStatus = B_NO_MEMORY;
|
||||
return;
|
||||
}
|
||||
|
||||
fStatus = B_OK;
|
||||
}
|
||||
|
||||
|
||||
HIDDevice::~HIDDevice()
|
||||
{
|
||||
if (fRingBuffer) {
|
||||
delete_ring_buffer(fRingBuffer);
|
||||
fRingBuffer = NULL;
|
||||
}
|
||||
|
||||
if (fTransferNotifySem >= 0)
|
||||
delete_sem(fTransferNotifySem);
|
||||
|
||||
free(fTransferBuffer);
|
||||
free(fName);
|
||||
}
|
||||
|
||||
|
||||
HIDDevice *
|
||||
HIDDevice::MakeHIDDevice(usb_device device,
|
||||
const usb_configuration_info *config, size_t interfaceIndex)
|
||||
{
|
||||
// read HID descriptor
|
||||
size_t descriptorLength = sizeof(usb_hid_descriptor);
|
||||
usb_hid_descriptor *hidDescriptor = (usb_hid_descriptor *)malloc(descriptorLength);
|
||||
if (hidDescriptor == NULL)
|
||||
return NULL;
|
||||
|
||||
status_t result = gUSBModule->send_request(device,
|
||||
USB_REQTYPE_INTERFACE_IN | USB_REQTYPE_STANDARD,
|
||||
USB_REQUEST_GET_DESCRIPTOR,
|
||||
USB_HID_DESCRIPTOR_HID << 8, interfaceIndex, descriptorLength,
|
||||
hidDescriptor, &descriptorLength);
|
||||
|
||||
TRACE("get_hid_desc: result: 0x%08lx; length: %lu\n", result, descriptorLength);
|
||||
if (result == B_OK)
|
||||
descriptorLength = hidDescriptor->descriptor_info[0].descriptor_length;
|
||||
else
|
||||
descriptorLength = 256; /* XXX */
|
||||
free(hidDescriptor);
|
||||
|
||||
uint8 *reportDescriptor = (uint8 *)malloc(descriptorLength);
|
||||
if (reportDescriptor == NULL)
|
||||
return NULL;
|
||||
|
||||
result = gUSBModule->send_request(device,
|
||||
USB_REQTYPE_INTERFACE_IN | USB_REQTYPE_STANDARD,
|
||||
USB_REQUEST_GET_DESCRIPTOR,
|
||||
USB_HID_DESCRIPTOR_REPORT << 8, interfaceIndex, descriptorLength,
|
||||
reportDescriptor, &descriptorLength);
|
||||
|
||||
TRACE("get_hid_rep_desc: result: 0x%08lx; length: %lu\n", result, descriptorLength);
|
||||
if (result != B_OK) {
|
||||
free(reportDescriptor);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if 1
|
||||
// save report descriptor for troubleshooting
|
||||
const usb_device_descriptor *deviceDescriptor
|
||||
= gUSBModule->get_device_descriptor(device);
|
||||
char outputFile[128];
|
||||
sprintf(outputFile, "/tmp/usb_hid_report_descriptor_%04x_%04x.bin",
|
||||
deviceDescriptor->vendor_id, deviceDescriptor->product_id);
|
||||
int fd = open(outputFile, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (fd >= 0) {
|
||||
write(fd, reportDescriptor, descriptorLength);
|
||||
close(fd);
|
||||
}
|
||||
#endif
|
||||
|
||||
// decompose report descriptor
|
||||
size_t itemCount = descriptorLength;
|
||||
decomp_item *items = (decomp_item *)malloc(sizeof(decomp_item) * itemCount);
|
||||
if (items == NULL) {
|
||||
free(reportDescriptor);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
decompose_report_descriptor(reportDescriptor, descriptorLength, items,
|
||||
&itemCount);
|
||||
uint32 deviceType = *(uint32 *)reportDescriptor;
|
||||
free(reportDescriptor);
|
||||
|
||||
// parse report descriptor
|
||||
size_t instructionCount = itemCount;
|
||||
report_insn *instructions
|
||||
= (report_insn *)malloc(sizeof(report_insn) * instructionCount);
|
||||
if (instructions == NULL) {
|
||||
free(items);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int firstReportID = 0;
|
||||
size_t totalReportSize = 0;
|
||||
parse_report_descriptor(items, itemCount, instructions,
|
||||
&instructionCount, &totalReportSize, &firstReportID);
|
||||
free(items);
|
||||
|
||||
report_insn *finalInstructions = (report_insn *)realloc(instructions,
|
||||
sizeof(report_insn) * instructionCount);
|
||||
if (finalInstructions == NULL) {
|
||||
free(instructions);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
TRACE("%lu items, %lu instructions, %lu bytes\n", itemCount,
|
||||
instructionCount, totalReportSize);
|
||||
|
||||
// find the interrupt in pipe
|
||||
usb_pipe interruptPipe = 0;
|
||||
usb_interface_info *interface = config->interface[interfaceIndex].active;
|
||||
for (size_t i = 0; i < interface->endpoint_count; i++) {
|
||||
usb_endpoint_descriptor *descriptor = interface->endpoint[i].descr;
|
||||
if ((descriptor->endpoint_address & USB_ENDPOINT_ADDR_DIR_IN)
|
||||
&& (descriptor->attributes & USB_ENDPOINT_ATTR_MASK) == USB_ENDPOINT_ATTR_INTERRUPT) {
|
||||
interruptPipe = interface->endpoint[i].handle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (interruptPipe == 0) {
|
||||
TRACE_ALWAYS("didn't find a suitable interrupt pipe\n");
|
||||
free(finalInstructions);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// determine device type and create the device object
|
||||
if (deviceType == USB_HID_DEVICE_TYPE_KEYBOARD) {
|
||||
return new KeyboardDevice(device, interruptPipe, interfaceIndex,
|
||||
finalInstructions, instructionCount, totalReportSize);
|
||||
} else if (deviceType == USB_HID_DEVICE_TYPE_MOUSE) {
|
||||
return new MouseDevice(device, interruptPipe, interfaceIndex,
|
||||
finalInstructions, instructionCount, totalReportSize);
|
||||
}
|
||||
|
||||
TRACE_ALWAYS("unsupported device type 0x%08lx\n", deviceType);
|
||||
free(finalInstructions);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
HIDDevice::SetBaseName(const char *baseName)
|
||||
{
|
||||
// As devices can be un- and replugged at will, we cannot simply rely on
|
||||
// a device count. If there is just one keyboard, this does not mean that
|
||||
// it uses the 0 name. There might have been two keyboards and the one
|
||||
// using 0 might have been unplugged. So we just generate names until we
|
||||
// find one that is not currently in use.
|
||||
int32 index = 0;
|
||||
char nameBuffer[128];
|
||||
while (true) {
|
||||
sprintf(nameBuffer, "%s%ld", baseName, index++);
|
||||
if (gDeviceList->FindDevice(nameBuffer) == NULL) {
|
||||
// this name is still free, use it
|
||||
free(fName);
|
||||
fName = strdup(nameBuffer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
HIDDevice::SetParentCookie(int32 cookie)
|
||||
{
|
||||
fParentCookie = cookie;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
HIDDevice::Open(uint32 flags)
|
||||
{
|
||||
fOpen = true;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
HIDDevice::Close()
|
||||
{
|
||||
fOpen = false;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
HIDDevice::Free()
|
||||
{
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
HIDDevice::Read(uint8 *buffer, size_t *numBytes)
|
||||
{
|
||||
TRACE_ALWAYS("read on hid device\n");
|
||||
*numBytes = 0;
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
HIDDevice::Write(const uint8 *buffer, size_t *numBytes)
|
||||
{
|
||||
TRACE_ALWAYS("write on hid device\n");
|
||||
*numBytes = 0;
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
HIDDevice::Control(uint32 op, void *buffer, size_t length)
|
||||
{
|
||||
TRACE_ALWAYS("control on base class\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
HIDDevice::Removed()
|
||||
{
|
||||
fRemoved = true;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
HIDDevice::_SetTransferProcessed()
|
||||
{
|
||||
fTransferUnprocessed = false;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
HIDDevice::_IsTransferUnprocessed()
|
||||
{
|
||||
return fTransferUnprocessed;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
HIDDevice::_ScheduleTransfer()
|
||||
{
|
||||
if (fTransferUnprocessed)
|
||||
return B_BUSY;
|
||||
|
||||
status_t result = gUSBModule->queue_interrupt(fInterruptPipe,
|
||||
fTransferBuffer, fTotalReportSize, _TransferCallback, this);
|
||||
if (result < B_OK) {
|
||||
TRACE_ALWAYS("failed to schedule interrupt transfer 0x%08lx\n", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
fTransferUnprocessed = true;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
int32
|
||||
HIDDevice::_RingBufferReadable()
|
||||
{
|
||||
return ring_buffer_readable(fRingBuffer);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
HIDDevice::_RingBufferRead(void *buffer, size_t length)
|
||||
{
|
||||
ring_buffer_user_read(fRingBuffer, (uint8 *)buffer, length);
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
HIDDevice::_RingBufferWrite(const void *buffer, size_t length)
|
||||
{
|
||||
ring_buffer_write(fRingBuffer, (const uint8 *)buffer, length);
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
HIDDevice::_TransferCallback(void *cookie, status_t status, void *data,
|
||||
size_t actualLength)
|
||||
{
|
||||
HIDDevice *device = (HIDDevice *)cookie;
|
||||
device->fTransferStatus = status;
|
||||
device->fTransferActualLength = actualLength;
|
||||
release_sem_etc(device->fTransferNotifySem, 1, B_DO_NOT_RESCHEDULE);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
Driver for USB Human Interface Devices.
|
||||
Copyright (C) 2008 Michael Lotz <[email protected]>
|
||||
Distributed under the terms of the MIT license.
|
||||
*/
|
||||
#ifndef _USB_HID_DEVICE_H_
|
||||
#define _USB_HID_DEVICE_H_
|
||||
|
||||
#include "hidparse.h"
|
||||
#include "ring_buffer.h"
|
||||
#include <USB3.h>
|
||||
|
||||
class HIDDevice {
|
||||
public:
|
||||
HIDDevice(usb_device device,
|
||||
usb_pipe interruptPipe,
|
||||
size_t interfaceIndex,
|
||||
report_insn *instructions,
|
||||
size_t instructionCount,
|
||||
size_t totalReportSize,
|
||||
size_t ringBufferSize);
|
||||
virtual ~HIDDevice();
|
||||
|
||||
static HIDDevice * MakeHIDDevice(usb_device device,
|
||||
const usb_configuration_info *config,
|
||||
size_t interfaceIndex);
|
||||
|
||||
void SetBaseName(const char *baseName);
|
||||
const char * Name() { return fName; };
|
||||
|
||||
void SetParentCookie(int32 cookie);
|
||||
int32 ParentCookie() { return fParentCookie; };
|
||||
|
||||
status_t InitCheck() { return fStatus; };
|
||||
|
||||
virtual status_t Open(uint32 flags);
|
||||
bool IsOpen() { return fOpen; };
|
||||
|
||||
virtual status_t Close();
|
||||
virtual status_t Free();
|
||||
|
||||
virtual status_t Read(uint8 *buffer, size_t *numBytes);
|
||||
virtual status_t Write(const uint8 *buffer, size_t *numBytes);
|
||||
virtual status_t Control(uint32 op, void *buffer, size_t length);
|
||||
|
||||
virtual void Removed();
|
||||
bool IsRemoved() { return fRemoved; };
|
||||
|
||||
protected:
|
||||
void _SetTransferProcessed();
|
||||
bool _IsTransferUnprocessed();
|
||||
status_t _ScheduleTransfer();
|
||||
|
||||
int32 _RingBufferReadable();
|
||||
status_t _RingBufferRead(void *buffer, size_t length);
|
||||
status_t _RingBufferWrite(const void *buffer,
|
||||
size_t length);
|
||||
|
||||
status_t fStatus;
|
||||
usb_device fDevice;
|
||||
usb_pipe fInterruptPipe;
|
||||
size_t fInterfaceIndex;
|
||||
report_insn * fInstructions;
|
||||
size_t fInstructionCount;
|
||||
size_t fTotalReportSize;
|
||||
|
||||
// transfer data
|
||||
bool fTransferUnprocessed;
|
||||
status_t fTransferStatus;
|
||||
size_t fTransferActualLength;
|
||||
uint8 * fTransferBuffer;
|
||||
sem_id fTransferNotifySem;
|
||||
|
||||
private:
|
||||
static void _TransferCallback(void *cookie,
|
||||
status_t status, void *data,
|
||||
size_t actualLength);
|
||||
|
||||
char * fName;
|
||||
int32 fParentCookie;
|
||||
bool fOpen;
|
||||
bool fRemoved;
|
||||
|
||||
struct ring_buffer * fRingBuffer;
|
||||
};
|
||||
|
||||
#endif // _USB_HID_DEVICE_H_
|
||||
@@ -2,6 +2,8 @@ SubDir HAIKU_TOP src add-ons kernel drivers input usb_hid ;
|
||||
|
||||
SetSubDirSupportedPlatformsBeOSCompatible ;
|
||||
|
||||
SubDirC++Flags -fno-rtti ;
|
||||
|
||||
SubDirSysHdrs $(HAIKU_TOP) headers os drivers ;
|
||||
UsePrivateHeaders [ FDirName kernel util ] input ;
|
||||
UsePrivateHeaders kernel ;
|
||||
@@ -12,23 +14,19 @@ if ! $(TARGET_PLATFORM_HAIKU_COMPATIBLE) {
|
||||
}
|
||||
|
||||
KernelAddon usb_hid :
|
||||
hid.c
|
||||
DeviceList.cpp
|
||||
Driver.cpp
|
||||
HIDDevice.cpp
|
||||
KeyboardDevice.cpp
|
||||
MouseDevice.cpp
|
||||
hidparse.c
|
||||
devlist.c
|
||||
$(buffer_impl)
|
||||
;
|
||||
|
||||
SEARCH on [ FGristFiles ring_buffer.cpp ] = [ FDirName $(HAIKU_TOP) src system kernel util ] ;
|
||||
|
||||
ObjectHdrs [ FGristFiles hid$(SUFOBJ) ]
|
||||
: [ FDirName $(TARGET_COMMON_DEBUG_OBJECT_DIR) preferences devices ] ;
|
||||
|
||||
Includes [ FGristFiles hid.c ] :
|
||||
<src!preferences!devices>usbdevs.h <src!preferences!devices>usbdevs_data.h ;
|
||||
|
||||
Package haiku-inputkit-cvs :
|
||||
usb_hid :
|
||||
boot home config add-ons kernel drivers bin ;
|
||||
|
||||
PackageDriverSymLink haiku-inputkit-cvs : input usb_hid ;
|
||||
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
Driver for USB Human Interface Devices.
|
||||
Copyright (C) 2008 Michael Lotz <mmlr@mlotz.ch>
|
||||
Distributed under the terms of the MIT license.
|
||||
|
||||
Interpretation code based on the previous usb_hid driver which was written
|
||||
by Jérôme Duval.
|
||||
*/
|
||||
#include "Driver.h"
|
||||
#include "KeyboardDevice.h"
|
||||
#include <string.h>
|
||||
#include <usb/USB_hid.h>
|
||||
|
||||
// input server private for raw_key_info, KB_READ, etc...
|
||||
#include "kb_mouse_driver.h"
|
||||
|
||||
|
||||
KeyboardDevice::KeyboardDevice(usb_device device, usb_pipe interruptPipe,
|
||||
size_t interfaceIndex, report_insn *instructions, size_t instructionCount,
|
||||
size_t totalReportSize)
|
||||
: HIDDevice(device, interruptPipe, interfaceIndex, instructions,
|
||||
instructionCount, totalReportSize, 512),
|
||||
fRepeatDelay(300000),
|
||||
fRepeatRate(35000),
|
||||
fLastTransferBuffer(NULL)
|
||||
{
|
||||
fCurrentRepeatDelay = B_INFINITE_TIMEOUT;
|
||||
fCurrentRepeatKey = 0;
|
||||
|
||||
fLastTransferBuffer = (uint8 *)malloc(totalReportSize);
|
||||
if (fLastTransferBuffer == NULL) {
|
||||
fStatus = B_NO_MEMORY;
|
||||
return;
|
||||
}
|
||||
|
||||
SetBaseName("input/keyboard/usb/");
|
||||
}
|
||||
|
||||
|
||||
KeyboardDevice::~KeyboardDevice()
|
||||
{
|
||||
free(fLastTransferBuffer);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
KeyboardDevice::Control(uint32 op, void *buffer, size_t length)
|
||||
{
|
||||
switch (op) {
|
||||
case KB_READ:
|
||||
while (_RingBufferReadable() == 0) {
|
||||
if (!_IsTransferUnprocessed()) {
|
||||
status_t result = _ScheduleTransfer();
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
// NOTE: this thread is now blocking until the semaphore is
|
||||
// released from the callback function or the repeat timeout
|
||||
// expires
|
||||
status_t result = acquire_sem_etc(fTransferNotifySem, 1,
|
||||
B_CAN_INTERRUPT | B_RELATIVE_TIMEOUT, fCurrentRepeatDelay);
|
||||
if (result == B_OK) {
|
||||
result = _InterpretBuffer();
|
||||
_SetTransferProcessed();
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
} else if (result == B_TIMED_OUT) {
|
||||
// this case is for handling key repeats, it means
|
||||
// no interrupt transfer has happened
|
||||
_WriteKey(fCurrentRepeatKey, true);
|
||||
// the next timeout is reduced to the repeat_rate
|
||||
fCurrentRepeatDelay = fRepeatRate;
|
||||
} else if (result == B_INTERRUPTED)
|
||||
continue;
|
||||
else if (result != B_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
// process what is in the ring_buffer, it could be written
|
||||
// there because we handled an interrupt transfer or because
|
||||
// we wrote the current repeat key
|
||||
return _RingBufferRead(buffer, sizeof(raw_key_info));
|
||||
|
||||
case KB_SET_LEDS:
|
||||
return _SetLEDs((uint8 *)buffer);
|
||||
}
|
||||
|
||||
TRACE_ALWAYS("keyboard device unhandled control 0x%08lx\n", op);
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
KeyboardDevice::_WriteKey(uint32 key, bool down)
|
||||
{
|
||||
raw_key_info info;
|
||||
info.be_keycode = key;
|
||||
info.is_keydown = down;
|
||||
info.timestamp = system_time();
|
||||
|
||||
_RingBufferWrite(&info, sizeof(raw_key_info));
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
KeyboardDevice::_SetLEDs(uint8 *data)
|
||||
{
|
||||
uint8 leds = 0;
|
||||
if (data[0] == 1)
|
||||
leds |= (1 << 0);
|
||||
if (data[1] == 1)
|
||||
leds |= (1 << 1);
|
||||
if (data[2] == 1)
|
||||
leds |= (1 << 2);
|
||||
|
||||
size_t actualLength;
|
||||
return gUSBModule->send_request(fDevice,
|
||||
USB_REQTYPE_INTERFACE_OUT | USB_REQTYPE_CLASS,
|
||||
USB_REQUEST_HID_SET_REPORT,
|
||||
0x200 | 0 /* TODO: report id */, fInterfaceIndex,
|
||||
sizeof(leds), &leds, &actualLength);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
KeyboardDevice::_InterpretBuffer()
|
||||
{
|
||||
static uint32 sModifierTable[] = {
|
||||
KEY_ControlL,
|
||||
KEY_ShiftL,
|
||||
KEY_AltL,
|
||||
KEY_WinL,
|
||||
KEY_ControlR,
|
||||
KEY_ShiftR,
|
||||
KEY_AltR,
|
||||
KEY_WinR
|
||||
};
|
||||
|
||||
static uint32 sKeyTable[] = {
|
||||
0x00, // ERROR
|
||||
0x00, // ERROR
|
||||
0x00, // ERROR
|
||||
0x00, // ERROR
|
||||
0x3c, // A
|
||||
0x50, // B
|
||||
0x4e, // C
|
||||
0x3e, // D
|
||||
0x29, // E
|
||||
0x3f, // F
|
||||
0x40, // G
|
||||
0x41, // H
|
||||
0x2e, // I
|
||||
0x42, // J
|
||||
0x43, // K
|
||||
0x44, // L
|
||||
0x52, // M
|
||||
0x51, // N
|
||||
0x2f, // O
|
||||
0x30, // P
|
||||
0x27, // Q
|
||||
0x2a, // R
|
||||
0x3d, // S
|
||||
0x2b, // T
|
||||
0x2d, // U
|
||||
0x4f, // V
|
||||
0x28, // W
|
||||
0x4d, // X
|
||||
0x2c, // Y
|
||||
0x4c, // Z
|
||||
0x12, // 1
|
||||
0x13, // 2
|
||||
0x14, // 3
|
||||
0x15, // 4
|
||||
0x16, // 5
|
||||
0x17, // 6
|
||||
0x18, // 7
|
||||
0x19, // 8
|
||||
0x1a, // 9
|
||||
0x1b, // 0
|
||||
0x47, // enter
|
||||
0x01, // Esc
|
||||
0x1e, // Backspace
|
||||
0x26, // Tab
|
||||
0x5e, // Space
|
||||
0x1c, // -
|
||||
0x1d, // =
|
||||
0x31, // [
|
||||
0x32, // ]
|
||||
0x00, // unmapped
|
||||
0x33, // \
|
||||
0x45, // ;
|
||||
0x46, // '
|
||||
0x11, // `
|
||||
0x53, // ,
|
||||
0x54, // .
|
||||
0x55, // /
|
||||
KEY_CapsLock, // Caps
|
||||
0x02, // F1
|
||||
0x03, // F2
|
||||
0x04, // F3
|
||||
0x05, // F4
|
||||
0x06, // F5
|
||||
0x07, // F6
|
||||
0x08, // F7
|
||||
0x09, // F8
|
||||
0x0a, // F9
|
||||
0x0b, // F10
|
||||
0x0c, // F11
|
||||
0x0d, // F12
|
||||
0x0e, // PrintScreen
|
||||
KEY_Scroll, // Scroll Lock
|
||||
KEY_Pause, // Pause (0x7f with Ctrl)
|
||||
0x1f, // Insert
|
||||
0x20, // Home
|
||||
0x21, // Page up
|
||||
0x34, // Delete
|
||||
0x35, // End
|
||||
0x36, // Page down
|
||||
0x63, // Right arrow
|
||||
0x61, // Left arrow
|
||||
0x62, // Down arrow
|
||||
0x57, // Up arrow
|
||||
0x22, // Num Lock
|
||||
0x23, // Pad /
|
||||
0x24, // Pad *
|
||||
0x25, // Pad -
|
||||
0x3a, // Pad +
|
||||
0x5b, // Pad Enter
|
||||
0x58, // Pad 1
|
||||
0x59, // Pad 2
|
||||
0x5a, // Pad 3
|
||||
0x48, // Pad 4
|
||||
0x49, // Pad 5
|
||||
0x4a, // Pad 6
|
||||
0x37, // Pad 7
|
||||
0x38, // Pad 8
|
||||
0x39, // Pad 9
|
||||
0x64, // Pad 0
|
||||
0x65, // Pad .
|
||||
0x69, // <
|
||||
KEY_Menu, // Menu
|
||||
KEY_Power, // Power
|
||||
KEY_NumEqual, // Pad =
|
||||
0x00, // F13 unmapped
|
||||
0x00, // F14 unmapped
|
||||
0x00, // F15 unmapped
|
||||
0x00, // F16 unmapped
|
||||
0x00, // F17 unmapped
|
||||
0x00, // F18 unmapped
|
||||
0x00, // F19 unmapped
|
||||
0x00, // F20 unmapped
|
||||
0x00, // F21 unmapped
|
||||
0x00, // F22 unmapped
|
||||
0x00, // F23 unmapped
|
||||
0x00, // F24 unmapped
|
||||
0x00, // Execute unmapped
|
||||
0x00, // Help unmapped
|
||||
0x00, // Menu unmapped
|
||||
0x00, // Select unmapped
|
||||
0x00, // Stop unmapped
|
||||
0x00, // Again unmapped
|
||||
0x00, // Undo unmapped
|
||||
0x00, // Cut unmapped
|
||||
0x00, // Copy unmapped
|
||||
0x00, // Paste unmapped
|
||||
0x00, // Find unmapped
|
||||
0x00, // Mute unmapped
|
||||
0x00, // Volume up unmapped
|
||||
0x00, // Volume down unmapped
|
||||
0x00, // CapsLock unmapped
|
||||
0x00, // NumLock unmapped
|
||||
0x00, // Scroll lock unmapped
|
||||
0x70, // Keypad . on Brazilian ABNT2
|
||||
0x00, // = sign
|
||||
0x6b, // Ro (\\ key, japanese)
|
||||
0x6e, // Katakana/Hiragana, second key right to spacebar, japanese
|
||||
0x6a, // Yen (macron key, japanese)
|
||||
0x6d, // Henkan, first key right to spacebar, japanese
|
||||
0x6c, // Muhenkan, key left to spacebar, japanese
|
||||
};
|
||||
|
||||
static size_t sKeyTableSize = sizeof(sKeyTable) / sizeof(sKeyTable[0]);
|
||||
|
||||
uint8 modifierChange = fLastTransferBuffer[0] ^ fTransferBuffer[0];
|
||||
for (uint8 i = 0; modifierChange; i++, modifierChange >>= 1) {
|
||||
if (modifierChange & 1)
|
||||
_WriteKey(sModifierTable[i], (fTransferBuffer[0] >> i) & 1);
|
||||
}
|
||||
|
||||
bool keyDown = false;
|
||||
uint8 *current = fLastTransferBuffer;
|
||||
uint8 *compare = fTransferBuffer;
|
||||
for (int32 twice = 0; twice < 2; twice++) {
|
||||
for (size_t i = 2; i < fTotalReportSize; i++) {
|
||||
if (current[i] != 0x00 && current[i] != 0x01) {
|
||||
bool found = false;
|
||||
for (size_t j = 2; j < fTotalReportSize; j++) {
|
||||
if (compare[j] == current[i]) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
continue;
|
||||
|
||||
// a change occured
|
||||
uint32 key = 0;
|
||||
if (current[i] < sKeyTableSize)
|
||||
key = sKeyTable[current[i]];
|
||||
|
||||
if (key == KEY_Pause && (current[0] & 1))
|
||||
key = KEY_Break;
|
||||
else if (key == 0xe && (current[0] & 1))
|
||||
key = KEY_SysRq;
|
||||
#if HAIKU_TARGET_PLATFORM_HAIKU
|
||||
else if (keyDown && key == 0x0d) // ToDo: remove again
|
||||
panic("keyboard requested halt.\n");
|
||||
#endif
|
||||
else if (key == 0) {
|
||||
// unmapped key
|
||||
key = 0x200000 + current[i];
|
||||
}
|
||||
|
||||
_WriteKey(key, keyDown);
|
||||
|
||||
if (keyDown) {
|
||||
// repeat handling
|
||||
fCurrentRepeatKey = key;
|
||||
fCurrentRepeatDelay = fRepeatDelay;
|
||||
} else {
|
||||
// cancel the repeats if they are for this key
|
||||
if (fCurrentRepeatKey == key)
|
||||
fCurrentRepeatDelay = B_INFINITE_TIMEOUT;
|
||||
}
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
current = fTransferBuffer;
|
||||
compare = fLastTransferBuffer;
|
||||
keyDown = true;
|
||||
}
|
||||
|
||||
memcpy(fLastTransferBuffer, fTransferBuffer, fTotalReportSize);
|
||||
return B_OK;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Driver for USB Human Interface Devices.
|
||||
Copyright (C) 2008 Michael Lotz <mmlr@mlotz.ch>
|
||||
Distributed under the terms of the MIT license.
|
||||
*/
|
||||
#ifndef _USB_KEYBOARD_DEVICE_H_
|
||||
#define _USB_KEYBOARD_DEVICE_H_
|
||||
|
||||
#include "HIDDevice.h"
|
||||
|
||||
class KeyboardDevice : public HIDDevice {
|
||||
public:
|
||||
KeyboardDevice(usb_device device,
|
||||
usb_pipe interruptPipe,
|
||||
size_t interfaceIndex,
|
||||
report_insn *instructions,
|
||||
size_t instructionCount,
|
||||
size_t totalReportSize);
|
||||
virtual ~KeyboardDevice();
|
||||
|
||||
virtual status_t Control(uint32 op, void *buffer, size_t length);
|
||||
|
||||
private:
|
||||
void _WriteKey(uint32 key, bool down);
|
||||
status_t _SetLEDs(uint8 *data);
|
||||
status_t _InterpretBuffer();
|
||||
|
||||
bigtime_t fRepeatDelay;
|
||||
bigtime_t fRepeatRate;
|
||||
bigtime_t fCurrentRepeatDelay;
|
||||
uint32 fCurrentRepeatKey;
|
||||
|
||||
uint8 * fLastTransferBuffer;
|
||||
};
|
||||
|
||||
#endif // _USB_KEYBOARD_DEVICE_H_
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
Driver for USB Human Interface Devices.
|
||||
Copyright (C) 2008 Michael Lotz <mmlr@mlotz.ch>
|
||||
Distributed under the terms of the MIT license.
|
||||
|
||||
Interpretation code based on the previous usb_hid driver which was written
|
||||
by Jérôme Duval.
|
||||
*/
|
||||
#include "Driver.h"
|
||||
#include "MouseDevice.h"
|
||||
#include <string.h>
|
||||
#include <usb/USB_hid.h>
|
||||
|
||||
// input server private for mouse_movement, MS_READ, etc...
|
||||
#include "kb_mouse_driver.h"
|
||||
|
||||
|
||||
MouseDevice::MouseDevice(usb_device device, usb_pipe interruptPipe,
|
||||
size_t interfaceIndex, report_insn *instructions, size_t instructionCount,
|
||||
size_t totalReportSize)
|
||||
: HIDDevice(device, interruptPipe, interfaceIndex, instructions,
|
||||
instructionCount, totalReportSize, 512),
|
||||
fLastButtons(0),
|
||||
fClickCount(0),
|
||||
fLastClickTime(0),
|
||||
fClickSpeed(250000),
|
||||
fMaxButtons(16)
|
||||
{
|
||||
SetBaseName("input/mouse/usb/");
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
MouseDevice::Control(uint32 op, void *buffer, size_t length)
|
||||
{
|
||||
switch (op) {
|
||||
case MS_READ:
|
||||
while (_RingBufferReadable() == 0) {
|
||||
if (!_IsTransferUnprocessed()) {
|
||||
status_t result = _ScheduleTransfer();
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
// NOTE: this thread is now blocking until the semaphore is
|
||||
// released in the callback function
|
||||
status_t result = acquire_sem_etc(fTransferNotifySem, 1,
|
||||
B_CAN_INTERRUPT, 0);
|
||||
if (result == B_INTERRUPTED)
|
||||
continue;
|
||||
else if (result != B_OK)
|
||||
return result;
|
||||
|
||||
result = _InterpretBuffer();
|
||||
_SetTransferProcessed();
|
||||
if (result != B_OK)
|
||||
return result;
|
||||
}
|
||||
|
||||
return _RingBufferRead(buffer, sizeof(mouse_movement));
|
||||
|
||||
case MS_NUM_EVENTS:
|
||||
return _RingBufferReadable() / sizeof(mouse_movement);
|
||||
|
||||
case MS_SET_CLICKSPEED:
|
||||
#ifdef __HAIKU__
|
||||
return user_memcpy(&fClickSpeed, buffer, sizeof(bigtime_t));
|
||||
#else
|
||||
fClickSpeed = *(bigtime_t *)buffer;
|
||||
return B_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
return B_ERROR;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
MouseDevice::_InterpretBuffer()
|
||||
{
|
||||
mouse_movement info;
|
||||
memset(&info, 0, sizeof(info));
|
||||
for (size_t i = 0; i < fInstructionCount; i++) {
|
||||
const report_insn *instruction = &fInstructions[i];
|
||||
int32 value = (((fTransferBuffer[instruction->byte_idx + 1] << 8)
|
||||
| fTransferBuffer[instruction->byte_idx]) >> instruction->bit_pos)
|
||||
& ((1 << instruction->num_bits) - 1);
|
||||
|
||||
switch (instruction->usage_page) {
|
||||
case USAGE_PAGE_BUTTON:
|
||||
if (instruction->usage_id - 1 < fMaxButtons)
|
||||
info.buttons |= (value & 1) << (instruction->usage_id - 1);
|
||||
break;
|
||||
|
||||
case USAGE_PAGE_GENERIC_DESKTOP:
|
||||
if (instruction->is_phy_signed)
|
||||
value = sign_extend(value, instruction->num_bits);
|
||||
|
||||
switch (instruction->usage_id) {
|
||||
case USAGE_ID_X:
|
||||
info.xdelta = value;
|
||||
break;
|
||||
|
||||
case USAGE_ID_Y:
|
||||
info.ydelta = -value;
|
||||
break;
|
||||
|
||||
case USAGE_ID_WHEEL:
|
||||
info.wheel_ydelta = -value;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bigtime_t timestamp = system_time();
|
||||
if (info.buttons != 0) {
|
||||
if (fLastButtons == 0) {
|
||||
if (fLastClickTime + fClickSpeed > timestamp)
|
||||
fClickCount++;
|
||||
else
|
||||
fClickCount = 1;
|
||||
}
|
||||
|
||||
fLastClickTime = timestamp;
|
||||
info.clicks = fClickCount;
|
||||
}
|
||||
|
||||
fLastButtons = info.buttons;
|
||||
info.timestamp = timestamp;
|
||||
return _RingBufferWrite(&info, sizeof(mouse_movement));
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Driver for USB Human Interface Devices.
|
||||
Copyright (C) 2008 Michael Lotz <mmlr@mlotz.ch>
|
||||
Distributed under the terms of the MIT license.
|
||||
*/
|
||||
#ifndef _USB_MOUSE_DEVICE_H_
|
||||
#define _USB_MOUSE_DEVICE_H_
|
||||
|
||||
#include "HIDDevice.h"
|
||||
|
||||
class MouseDevice : public HIDDevice {
|
||||
public:
|
||||
MouseDevice(usb_device device,
|
||||
usb_pipe interruptPipe,
|
||||
size_t interfaceIndex,
|
||||
report_insn *instructions,
|
||||
size_t instructionCount,
|
||||
size_t totalReportSize);
|
||||
|
||||
virtual status_t Control(uint32 op, void *buffer, size_t length);
|
||||
|
||||
private:
|
||||
status_t _InterpretBuffer();
|
||||
|
||||
uint32 fLastButtons;
|
||||
uint32 fClickCount;
|
||||
bigtime_t fLastClickTime;
|
||||
bigtime_t fClickSpeed;
|
||||
uint32 fMaxButtons;
|
||||
};
|
||||
|
||||
#endif // _USB_MOUSE_DEVICE_H_
|
||||
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-2006, Haiku, Inc. All Rights Reserved.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*
|
||||
* Authors:
|
||||
* Jérôme Duval
|
||||
*
|
||||
* Some portions of code are copyrighted by
|
||||
* USB Joystick driver for BeOS R5
|
||||
* Copyright 2000 (C) ITO, Takayuki. All rights reserved
|
||||
*/
|
||||
|
||||
|
||||
#include "hid.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
sem_id gDeviceListLock = -1;
|
||||
bool gDeviceListChanged = true; /* added or removed */
|
||||
/* dynamically generated */
|
||||
char **gDeviceNames = NULL;
|
||||
|
||||
|
||||
static hid_device_info *sDeviceList = NULL;
|
||||
static int sDeviceCount = 0;
|
||||
|
||||
|
||||
void
|
||||
add_device_info(hid_device_info *device)
|
||||
{
|
||||
assert(device != NULL);
|
||||
|
||||
acquire_sem(gDeviceListLock);
|
||||
device->next = sDeviceList;
|
||||
sDeviceList = device;
|
||||
sDeviceCount++;
|
||||
gDeviceListChanged = true;
|
||||
release_sem(gDeviceListLock);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
remove_device_info(hid_device_info *device)
|
||||
{
|
||||
assert(device != NULL);
|
||||
|
||||
acquire_sem(gDeviceListLock);
|
||||
|
||||
if (sDeviceList == device) {
|
||||
sDeviceList = device->next;
|
||||
--sDeviceCount;
|
||||
gDeviceListChanged = true;
|
||||
} else {
|
||||
hid_device_info *previous;
|
||||
for (previous = sDeviceList; previous != NULL; previous = previous->next) {
|
||||
if (previous->next == device) {
|
||||
previous->next = device->next;
|
||||
--sDeviceCount;
|
||||
gDeviceListChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(previous != NULL);
|
||||
}
|
||||
release_sem(gDeviceListLock);
|
||||
}
|
||||
|
||||
|
||||
hid_device_info *
|
||||
search_device_info(const char* name)
|
||||
{
|
||||
hid_device_info *device;
|
||||
|
||||
acquire_sem(gDeviceListLock);
|
||||
for (device = sDeviceList; device != NULL; device = device->next) {
|
||||
if (strcmp(device->name, name) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
release_sem(gDeviceListLock);
|
||||
return device;
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark - device names
|
||||
|
||||
|
||||
void
|
||||
alloc_device_names(void)
|
||||
{
|
||||
assert(gDeviceNames == NULL);
|
||||
gDeviceNames = malloc(sizeof(char *) * (sDeviceCount + 1));
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
free_device_names(void)
|
||||
{
|
||||
if (gDeviceNames != NULL) {
|
||||
int i;
|
||||
for (i = 0; gDeviceNames [i] != NULL; i++) {
|
||||
free(gDeviceNames[i]);
|
||||
}
|
||||
|
||||
free(gDeviceNames);
|
||||
gDeviceNames = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
rebuild_device_names(void)
|
||||
{
|
||||
int i;
|
||||
hid_device_info *device;
|
||||
|
||||
assert(gDeviceNames != NULL);
|
||||
acquire_sem(gDeviceListLock);
|
||||
for (i = 0, device = sDeviceList; device != NULL; device = device->next) {
|
||||
gDeviceNames[i++] = strdup(device->name);
|
||||
DPRINTF_INFO((MY_ID "publishing %s\n", device->name));
|
||||
}
|
||||
gDeviceNames[i] = NULL;
|
||||
release_sem(gDeviceListLock);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004-2007, Haiku, Inc. All Rights Reserved.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*
|
||||
* Authors:
|
||||
* Jérôme Duval
|
||||
*
|
||||
* Some portions of code are copyrighted by
|
||||
* USB Joystick driver for BeOS R5
|
||||
* Copyright 2000 (C) ITO, Takayuki. All rights reserved
|
||||
*/
|
||||
#ifndef _HID_H_
|
||||
#define _HID_H_
|
||||
|
||||
#include "hidparse.h"
|
||||
#include "ring_buffer.h"
|
||||
|
||||
#include <Drivers.h>
|
||||
#include <USB.h>
|
||||
#include <usb/USB_hid.h>
|
||||
|
||||
#if DEBUG
|
||||
#define DPRINTF_INFO(x) dprintf x
|
||||
#define DPRINTF_ERR(x) dprintf x
|
||||
#else
|
||||
#define DPRINTF_INFO(x)
|
||||
#define DPRINTF_ERR(x) dprintf x
|
||||
#endif
|
||||
|
||||
/* driver specific definitions */
|
||||
|
||||
#define DRIVER_NAME "usb_hid"
|
||||
|
||||
#define MY_ID "\033[34m" DRIVER_NAME ":\033[m "
|
||||
#define MY_ERR "\033[31merror:\033[m "
|
||||
#define MY_WARN "\033[31mwarning:\033[m "
|
||||
#define assert(x) \
|
||||
((x) ? 0 : dprintf (MY_ID "assertion failed at " __FILE__ ", line %d\n", __LINE__))
|
||||
|
||||
/* 0-origin */
|
||||
#define DEFAULT_CONFIGURATION 0
|
||||
|
||||
#define BUF_SIZ B_PAGE_SIZE
|
||||
|
||||
struct driver_cookie;
|
||||
|
||||
struct hid_repeat_timer {
|
||||
bigtime_t current_delay;
|
||||
uint32 key;
|
||||
};
|
||||
|
||||
typedef struct hid_device_info {
|
||||
/* list structure */
|
||||
struct hid_device_info *next;
|
||||
|
||||
/* maintain device */
|
||||
sem_id sem_cb;
|
||||
sem_id sem_lock;
|
||||
area_id buffer_area;
|
||||
void *buffer;
|
||||
|
||||
uint8 last_buffer[32];
|
||||
const usb_device *dev;
|
||||
uint16 ifno;
|
||||
char name[30];
|
||||
|
||||
struct ring_buffer *rbuf;
|
||||
|
||||
bool active;
|
||||
int open;
|
||||
struct driver_cookie *open_fds;
|
||||
|
||||
/* workarea for transfer */
|
||||
int usbd_status, bus_status, cmd_status;
|
||||
int actual_length;
|
||||
const usb_endpoint_info *ept;
|
||||
bool transfer_scheduled;
|
||||
|
||||
report_insn *insns;
|
||||
size_t num_insns;
|
||||
size_t total_report_size;
|
||||
int num_buttons, num_axes, num_hats;
|
||||
bigtime_t timestamp;
|
||||
uint flags;
|
||||
bool is_keyboard;
|
||||
|
||||
// double click (please move this into the input_server one day)
|
||||
uint32 last_buttons;
|
||||
uint32 click_count;
|
||||
bigtime_t click_speed;
|
||||
bigtime_t last_click_time;
|
||||
|
||||
// key repeats
|
||||
struct hid_repeat_timer repeat_timer;
|
||||
bigtime_t repeat_delay;
|
||||
bigtime_t repeat_rate;
|
||||
} hid_device_info;
|
||||
|
||||
/* hid.c */
|
||||
|
||||
extern usb_module_info* usb;
|
||||
extern const char* my_driver_name;
|
||||
extern const char* keyboard_base_name;
|
||||
extern const char* mouse_base_name;
|
||||
|
||||
hid_device_info* create_device(const usb_device *dev,
|
||||
const usb_interface_info *ii, uint16 ifno, bool is_keyboard);
|
||||
void delete_device(hid_device_info *device);
|
||||
|
||||
/* devlist.c */
|
||||
|
||||
extern sem_id gDeviceListLock;
|
||||
extern bool gDeviceListChanged;
|
||||
|
||||
void add_device_info(hid_device_info *device);
|
||||
void remove_device_info(hid_device_info *device);
|
||||
hid_device_info *search_device_info(const char *name);
|
||||
|
||||
extern char **gDeviceNames;
|
||||
|
||||
void alloc_device_names(void);
|
||||
void free_device_names(void);
|
||||
void rebuild_device_names(void);
|
||||
|
||||
#endif // _HID_H_
|
||||
@@ -41,6 +41,16 @@
|
||||
decompose report descriptor into array of uniform structure
|
||||
*/
|
||||
|
||||
int
|
||||
sign_extend(int value, int size)
|
||||
{
|
||||
if (value & (1 << (size - 1)))
|
||||
return value | (UINT_MAX << size);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
decompose_report_descriptor(const unsigned char *desc,
|
||||
size_t desc_len, decomp_item *items, size_t *num_items)
|
||||
|
||||
@@ -149,6 +149,12 @@ typedef struct
|
||||
|
||||
} report_insn;
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
int sign_extend(int value, int size);
|
||||
|
||||
int decompose_report_descriptor
|
||||
(const unsigned char *desc,
|
||||
size_t desc_len,
|
||||
@@ -162,3 +168,7 @@ int parse_report_descriptor
|
||||
size_t *num_insns,
|
||||
size_t *total_report_size,
|
||||
int *first_report_id);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef _KERNEL_CPP_H_
|
||||
#define _KERNEL_CPP_H_
|
||||
|
||||
#include <malloc.h>
|
||||
|
||||
inline void *
|
||||
operator new(size_t size)
|
||||
{
|
||||
return malloc(size);
|
||||
}
|
||||
|
||||
|
||||
inline void *
|
||||
operator new[](size_t size)
|
||||
{
|
||||
return malloc(size);
|
||||
}
|
||||
|
||||
|
||||
inline void
|
||||
operator delete(void *pointer)
|
||||
{
|
||||
free(pointer);
|
||||
}
|
||||
|
||||
|
||||
inline void
|
||||
operator delete[](void *pointer)
|
||||
{
|
||||
free(pointer);
|
||||
}
|
||||
|
||||
|
||||
inline void
|
||||
terminate(void)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
static inline void
|
||||
__throw()
|
||||
{
|
||||
}
|
||||
|
||||
#endif // _KERNEL_CPP_H_
|
||||
Reference in New Issue
Block a user