* Copyright 2003-2008, Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Michael Lotz <mmlr@mlotz.ch>
* Niels S. Reedijk
*/
#include <module.h>
#include <unistd.h>
#include <util/kernel_cpp.h>
#include <util/AutoLock.h>
#include "usb_private.h"
#include "PhysicalMemoryAllocator.h"
#include <fs/devfs.h>
#include <kdevice_manager.h>
Stack::Stack()
: fExploreThread(-1),
fExploreSem(-1),
fAllocator(NULL),
fObjectIndex(1),
fObjectMaxCount(1024),
fObjectArray(NULL),
fDriverList(NULL)
{
TRACE("stack init\n");
mutex_init(&fStackLock, "usb stack lock");
mutex_init(&fExploreLock, "usb explore lock");
fExploreSem = create_sem(0, "usb explore sem");
if (fExploreSem < B_OK) {
TRACE_ERROR("failed to create semaphore\n");
return;
}
size_t objectArraySize = fObjectMaxCount * sizeof(Object *);
fObjectArray = (Object **)malloc(objectArraySize);
if (fObjectArray == NULL) {
TRACE_ERROR("failed to allocate object array\n");
return;
}
memset(fObjectArray, 0, objectArraySize);
fAllocator = new(std::nothrow) PhysicalMemoryAllocator("USB Stack Allocator",
8, B_PAGE_SIZE * 32, 64);
if (!fAllocator || fAllocator->InitCheck() < B_OK) {
TRACE_ERROR("failed to allocate the allocator\n");
delete fAllocator;
fAllocator = NULL;
return;
}
fExploreThread = spawn_kernel_thread(ExploreThread, "usb explore",
B_LOW_PRIORITY, this);
resume_thread(fExploreThread);
}
Stack::~Stack()
{
int32 result;
delete_sem(fExploreSem);
fExploreSem = -1;
wait_for_thread(fExploreThread, &result);
mutex_lock(&fStackLock);
mutex_destroy(&fStackLock);
mutex_lock(&fExploreLock);
mutex_destroy(&fExploreLock);
for (Vector<BusManager *>::Iterator i = fBusManagers.Begin();
i != fBusManagers.End(); i++) {
delete (*i);
}
delete fAllocator;
free(fObjectArray);
}
status_t
Stack::InitCheck()
{
return (fExploreThread >= 0) ? B_OK : B_NO_INIT;
}
bool
Stack::Lock()
{
return (mutex_lock(&fStackLock) == B_OK);
}
void
Stack::Unlock()
{
mutex_unlock(&fStackLock);
}
usb_id
Stack::GetUSBID(Object *object)
{
if (!Lock())
return fObjectMaxCount;
uint32 id = fObjectIndex;
uint32 tries = fObjectMaxCount;
while (tries-- > 0) {
if (fObjectArray[id] == NULL) {
fObjectIndex = (id + 1) % fObjectMaxCount;
fObjectArray[id] = object;
Unlock();
return (usb_id)id;
}
id = (id + 1) % fObjectMaxCount;
}
TRACE_ERROR("the stack has run out of usb_ids\n");
Unlock();
return 0;
}
void
Stack::PutUSBID(Object *object)
{
if (!Lock())
return;
usb_id id = object->USBID();
if (id >= fObjectMaxCount) {
TRACE_ERROR("tried to put an invalid usb_id\n");
Unlock();
return;
}
if (fObjectArray[id] != object) {
TRACE_ERROR("tried to put an object with incorrect usb_id\n");
Unlock();
return;
}
fObjectArray[id] = NULL;
#if KDEBUG
for (usb_id i = 0; i < fObjectMaxCount; i++) {
if (fObjectArray[i] == NULL)
continue;
ASSERT_PRINT(fObjectArray[i]->Parent() != object,
"%s", fObjectArray[i]->TypeName());
}
#endif
Unlock();
}
Object *
Stack::GetObject(usb_id id)
{
if (!Lock())
return NULL;
if (id >= fObjectMaxCount) {
TRACE_ERROR("tried to get object with invalid usb_id\n");
Unlock();
return NULL;
}
Object *result = fObjectArray[id];
if (result != NULL)
result->AcquireReference();
Unlock();
return result;
}
Object *
Stack::GetObjectNoLock(usb_id id) const
{
ASSERT(debug_debugger_running());
if (id >= fObjectMaxCount)
return NULL;
return fObjectArray[id];
}
int32
Stack::ExploreThread(void *data)
{
Stack *stack = (Stack *)data;
while (acquire_sem_etc(stack->fExploreSem, 1, B_RELATIVE_TIMEOUT,
USB_DELAY_HUB_EXPLORE) != B_BAD_SEM_ID) {
stack->Explore();
}
return B_OK;
}
void
Stack::Explore()
{
recursive_lock* dmLock = device_manager_get_lock();
if (find_thread(NULL) != fExploreThread
&& RECURSIVE_LOCK_HOLDER(dmLock) == find_thread(NULL)) {
if (mutex_lock_with_timeout(&fExploreLock, B_RELATIVE_TIMEOUT, 1000) != B_OK) {
release_sem(fExploreSem);
return;
}
} else {
RecursiveLocker dmLocker(dmLock);
if (mutex_lock(&fExploreLock) != B_OK)
return;
dmLocker.Unlock();
}
int32 semCount = 0;
get_sem_count(fExploreSem, &semCount);
if (semCount > 0)
acquire_sem_etc(fExploreSem, semCount, B_RELATIVE_TIMEOUT, 0);
rescan_item *rescanList = NULL;
change_item *changeItem = NULL;
for (int32 i = 0; i < fBusManagers.Count(); i++) {
Hub *rootHub = fBusManagers.ElementAt(i)->GetRootHub();
if (rootHub)
rootHub->Explore(&changeItem);
}
while (changeItem) {
NotifyDeviceChange(changeItem->device, &rescanList, changeItem->added);
if (!changeItem->added) {
changeItem->device->GetBusManager()->FreeDevice(changeItem->device);
}
change_item *next = changeItem->link;
delete changeItem;
changeItem = next;
}
mutex_unlock(&fExploreLock);
RescanDrivers(rescanList);
}
void
Stack::AddBusManager(BusManager *busManager)
{
MutexLocker _(fExploreLock);
fBusManagers.PushBack(busManager);
}
int32
Stack::IndexOfBusManager(BusManager *busManager) const
{
return fBusManagers.IndexOf(busManager);
}
BusManager *
Stack::BusManagerAt(int32 index) const
{
return fBusManagers.ElementAt(index);
}
status_t
Stack::AllocateChunk(void **logicalAddress, phys_addr_t *physicalAddress,
size_t size)
{
return fAllocator->Allocate(size, logicalAddress, physicalAddress);
}
status_t
Stack::FreeChunk(void *logicalAddress, phys_addr_t physicalAddress,
size_t size)
{
return fAllocator->Deallocate(size, logicalAddress, physicalAddress);
}
area_id
Stack::AllocateArea(void **logicalAddress, phys_addr_t *physicalAddress, size_t size,
const char *name)
{
TRACE("allocating %ld bytes for %s\n", size, name);
void *logAddress;
size = (size + B_PAGE_SIZE - 1) & ~(B_PAGE_SIZE - 1);
area_id area = create_area(name, &logAddress, B_ANY_KERNEL_ADDRESS, size,
B_32_BIT_CONTIGUOUS, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
if (area < B_OK) {
TRACE_ERROR("couldn't allocate area %s\n", name);
return B_ERROR;
}
physical_entry physicalEntry;
status_t result = get_memory_map(logAddress, size, &physicalEntry, 1);
if (result < B_OK) {
delete_area(area);
TRACE_ERROR("couldn't map area %s\n", name);
return B_ERROR;
}
memset(logAddress, 0, size);
if (logicalAddress)
*logicalAddress = logAddress;
if (physicalAddress)
*physicalAddress = (phys_addr_t)physicalEntry.address;
TRACE("area = %" B_PRId32 ", size = %" B_PRIuSIZE ", log = %p, phy = %#"
B_PRIxPHYSADDR "\n", area, size, logAddress, physicalEntry.address);
return area;
}
void
Stack::NotifyDeviceChange(Device *device, rescan_item **rescanList, bool added)
{
TRACE("device %s\n", added ? "added" : "removed");
usb_driver_info *element = fDriverList;
while (element) {
status_t result = device->ReportDevice(element->support_descriptors,
element->support_descriptor_count, &element->notify_hooks,
&element->cookies, added, false);
if (result >= B_OK) {
const char *driverName = element->driver_name;
if (element->republish_driver_name)
driverName = element->republish_driver_name;
bool already = false;
rescan_item *rescanItem = *rescanList;
while (rescanItem) {
if (strcmp(rescanItem->name, driverName) == 0) {
already = true;
break;
}
rescanItem = rescanItem->link;
}
if (!already) {
rescanItem = new(std::nothrow) rescan_item;
if (!rescanItem)
return;
rescanItem->name = driverName;
rescanItem->link = *rescanList;
*rescanList = rescanItem;
}
}
element = element->link;
}
}
void
Stack::RescanDrivers(rescan_item *rescanItem)
{
while (rescanItem) {
devfs_rescan_driver(rescanItem->name);
rescan_item *next = rescanItem->link;
delete rescanItem;
rescanItem = next;
}
}
status_t
Stack::RegisterDriver(const char *driverName,
const usb_support_descriptor *descriptors,
size_t descriptorCount, const char *republishDriverName)
{
TRACE("register driver \"%s\"\n", driverName);
if (!driverName)
return B_BAD_VALUE;
if (!Lock())
return B_ERROR;
usb_driver_info *element = fDriverList;
while (element) {
if (strcmp(element->driver_name, driverName) == 0) {
free((char *)element->republish_driver_name);
element->republish_driver_name = strdup(republishDriverName);
free(element->support_descriptors);
size_t descriptorsSize = descriptorCount * sizeof(usb_support_descriptor);
element->support_descriptors = (usb_support_descriptor *)malloc(descriptorsSize);
memcpy(element->support_descriptors, descriptors, descriptorsSize);
element->support_descriptor_count = descriptorCount;
Unlock();
return B_OK;
}
element = element->link;
}
usb_driver_info *info = new(std::nothrow) usb_driver_info;
if (!info) {
Unlock();
return B_NO_MEMORY;
}
info->driver_name = strdup(driverName);
info->republish_driver_name = strdup(republishDriverName);
size_t descriptorsSize = descriptorCount * sizeof(usb_support_descriptor);
info->support_descriptors = (usb_support_descriptor *)malloc(descriptorsSize);
memcpy(info->support_descriptors, descriptors, descriptorsSize);
info->support_descriptor_count = descriptorCount;
info->notify_hooks.device_added = NULL;
info->notify_hooks.device_removed = NULL;
info->cookies = NULL;
info->link = NULL;
if (fDriverList) {
usb_driver_info *element = fDriverList;
while (element->link)
element = element->link;
element->link = info;
} else
fDriverList = info;
Unlock();
return B_OK;
}
status_t
Stack::InstallNotify(const char *driverName, const usb_notify_hooks *hooks)
{
TRACE("installing notify hooks for driver \"%s\"\n", driverName);
usb_driver_info *element = fDriverList;
while (element) {
if (strcmp(element->driver_name, driverName) == 0) {
if (mutex_lock(&fExploreLock) != B_OK)
return B_ERROR;
for (int32 i = 0; i < fBusManagers.Count(); i++) {
Hub *rootHub = fBusManagers.ElementAt(i)->GetRootHub();
if (rootHub) {
rootHub->ReportDevice(element->support_descriptors,
element->support_descriptor_count, hooks,
&element->cookies, true, true);
}
}
element->notify_hooks.device_added = hooks->device_added;
element->notify_hooks.device_removed = hooks->device_removed;
mutex_unlock(&fExploreLock);
return B_OK;
}
element = element->link;
}
return B_NAME_NOT_FOUND;
}
status_t
Stack::UninstallNotify(const char *driverName)
{
TRACE("uninstalling notify hooks for driver \"%s\"\n", driverName);
usb_driver_info *element = fDriverList;
while (element) {
if (strcmp(element->driver_name, driverName) == 0) {
if (mutex_lock(&fExploreLock) != B_OK)
return B_ERROR;
for (int32 i = 0; i < fBusManagers.Count(); i++) {
Hub *rootHub = fBusManagers.ElementAt(i)->GetRootHub();
if (rootHub)
rootHub->ReportDevice(element->support_descriptors,
element->support_descriptor_count,
&element->notify_hooks, &element->cookies, false, true);
}
element->notify_hooks.device_added = NULL;
element->notify_hooks.device_removed = NULL;
mutex_unlock(&fExploreLock);
return B_OK;
}
element = element->link;
}
return B_NAME_NOT_FOUND;
}