* Copyright 2002-2011, Axel Dörfler, axeld@pinc-software.de.
* Distributed under the terms of the MIT License.
*/
#include "legacy_drivers.h"
#include <dirent.h>
#include <errno.h>
#include <new>
#include <stdio.h>
#include <FindDirectory.h>
#include <image.h>
#include <NodeMonitor.h>
#include <boot_device.h>
#include <boot/kernel_args.h>
#include <elf.h>
#include <find_directory_private.h>
#include <fs/devfs.h>
#include <fs/KPath.h>
#include <fs/node_monitor.h>
#include <Notifications.h>
#include <safemode.h>
#include <util/DoublyLinkedList.h>
#include <util/OpenHashTable.h>
#include <util/Stack.h>
#include <vfs.h>
#include "AbstractModuleDevice.h"
#include "devfs_private.h"
#ifdef TRACE_LEGACY_DRIVERS
# define TRACE(x) dprintf x
#else
# define TRACE(x)
#endif
#define DRIVER_HASH_SIZE 16
namespace {
struct legacy_driver;
class LegacyDevice : public AbstractModuleDevice,
public DoublyLinkedListLinkImpl<LegacyDevice> {
public:
LegacyDevice(legacy_driver* driver,
const char* path, device_hooks* hooks);
virtual ~LegacyDevice();
status_t InitCheck() const;
virtual status_t InitDevice();
virtual void UninitDevice();
virtual void Removed();
void SetHooks(device_hooks* hooks);
legacy_driver* Driver() const { return fDriver; }
const char* Path() const { return fPath; }
device_hooks* Hooks() const { return fHooks; }
virtual status_t Open(const char* path, int openMode,
void** _cookie);
virtual status_t Select(void* cookie, uint8 event, selectsync* sync);
virtual status_t Control(void* cookie, int32 op, void* buffer, size_t length);
bool Republished() const { return fRepublished; }
void SetRepublished(bool republished)
{ fRepublished = republished; }
void SetRemovedFromParent(bool removed)
{ fRemovedFromParent = removed; }
private:
legacy_driver* fDriver;
const char* fPath;
device_hooks* fHooks;
bool fRepublished;
bool fRemovedFromParent;
};
typedef DoublyLinkedList<LegacyDevice> DeviceList;
struct legacy_driver {
legacy_driver* next;
const char* path;
const char* name;
dev_t device;
ino_t node;
timespec last_modified;
image_id image;
uint32 devices_used;
bool binary_updated;
int32 priority;
DeviceList devices;
int32 api_version;
device_hooks* (*find_device)(const char *);
const char** (*publish_devices)(void);
status_t (*uninit_driver)(void);
status_t (*uninit_hardware)(void);
};
enum driver_event_type {
kAddDriver,
kRemoveDriver,
kAddWatcher,
kRemoveWatcher
};
struct driver_event : DoublyLinkedListLinkImpl<driver_event> {
driver_event(driver_event_type _type) : type(_type) {}
struct ref {
dev_t device;
ino_t node;
};
driver_event_type type;
union {
char path[B_PATH_NAME_LENGTH];
ref node;
};
};
typedef DoublyLinkedList<driver_event> DriverEventList;
struct driver_entry : DoublyLinkedListLinkImpl<driver_entry> {
char* path;
dev_t device;
ino_t node;
int32 busses;
};
typedef DoublyLinkedList<driver_entry> DriverEntryList;
struct node_entry : DoublyLinkedListLinkImpl<node_entry> {
};
typedef DoublyLinkedList<node_entry> NodeList;
struct directory_node_entry {
directory_node_entry* hash_link;
ino_t node;
};
struct DirectoryNodeHashDefinition {
typedef ino_t* KeyType;
typedef directory_node_entry ValueType;
size_t HashKey(ino_t* key) const
{ return _Hash(*key); }
size_t Hash(directory_node_entry* entry) const
{ return _Hash(entry->node); }
bool Compare(ino_t* key, directory_node_entry* entry) const
{ return *key == entry->node; }
directory_node_entry*&
GetLink(directory_node_entry* entry) const
{ return entry->hash_link; }
uint32 _Hash(ino_t node) const
{ return (uint32)(node >> 32) + (uint32)node; }
};
typedef BOpenHashTable<DirectoryNodeHashDefinition> DirectoryNodeHash;
class DirectoryIterator {
public:
DirectoryIterator(const char *path,
const char *subPath = NULL, bool recursive = false);
~DirectoryIterator();
void SetTo(const char *path, const char *subPath = NULL,
bool recursive = false);
status_t GetNext(KPath &path, struct stat &stat);
const char* CurrentName() const { return fCurrentName; }
void Unset();
void AddPath(const char *path, const char *subPath = NULL);
private:
Stack<KPath*> fPaths;
bool fRecursive;
DIR* fDirectory;
KPath* fBasePath;
const char* fCurrentName;
};
class DirectoryWatcher : public NotificationListener {
public:
DirectoryWatcher();
virtual ~DirectoryWatcher();
virtual void EventOccurred(NotificationService& service,
const KMessage* event);
};
class DriverWatcher : public NotificationListener {
public:
DriverWatcher();
virtual ~DriverWatcher();
virtual void EventOccurred(NotificationService& service,
const KMessage* event);
};
struct DriverHash {
typedef const char* KeyType;
typedef legacy_driver ValueType;
size_t HashKey(KeyType key) const
{
return hash_hash_string(key);
}
size_t Hash(ValueType* driver) const
{
return HashKey(driver->name);
}
bool Compare(KeyType key, ValueType* driver) const
{
return strcmp(driver->name, key) == 0;
}
ValueType*& GetLink(ValueType* value) const
{
return value->next;
}
};
typedef BOpenHashTable<DriverHash> DriverTable;
}
static status_t unload_driver(legacy_driver *driver);
static status_t load_driver(legacy_driver *driver);
static const directory_which kDriverPaths[] = {
B_USER_NONPACKAGED_ADDONS_DIRECTORY,
B_USER_ADDONS_DIRECTORY,
B_SYSTEM_NONPACKAGED_ADDONS_DIRECTORY,
B_SYSTEM_ADDONS_DIRECTORY
};
static DriverWatcher sDriverWatcher;
static int32 sDriverEventsPending;
static DriverEventList sDriverEvents;
static mutex sDriverEventsLock = MUTEX_INITIALIZER("driver events");
static DirectoryWatcher sDirectoryWatcher;
static DirectoryNodeHash sDirectoryNodeHash;
static recursive_lock sLock;
static bool sWatching;
static DriverTable* sDriverHash;
driver would publish now, and then publishes/unpublishes the devices
as needed.
If the driver does not publish any devices anymore, it is unloaded.
*/
static status_t
republish_driver(legacy_driver* driver)
{
if (driver->image < 0) {
return load_driver(driver);
}
DeviceList::Iterator iterator = driver->devices.GetIterator();
while (LegacyDevice* device = iterator.Next()) {
device->SetRepublished(false);
}
const char** devicePaths = driver->publish_devices();
int32 exported = 0;
for (; devicePaths != NULL && devicePaths[0]; devicePaths++) {
LegacyDevice* device;
iterator = driver->devices.GetIterator();
while ((device = iterator.Next()) != NULL) {
if (!strncmp(device->Path(), devicePaths[0], B_PATH_NAME_LENGTH)) {
device->SetRepublished(true);
exported++;
break;
}
}
device_hooks* hooks = driver->find_device(devicePaths[0]);
if (hooks == NULL)
continue;
if (device != NULL) {
device->SetHooks(hooks);
continue;
}
TRACE(("devfs: publishing new device \"%s\"\n", devicePaths[0]));
device = new(std::nothrow) LegacyDevice(driver, devicePaths[0], hooks);
if (device != NULL && device->InitCheck() == B_OK
&& devfs_publish_device(devicePaths[0], device) == B_OK) {
driver->devices.Add(device);
exported++;
} else
delete device;
}
iterator = driver->devices.GetIterator();
while (LegacyDevice* device = iterator.Next()) {
if (device->Republished())
continue;
TRACE(("devfs: unpublishing no more present \"%s\"\n", device->Path()));
iterator.Remove();
device->SetRemovedFromParent(true);
devfs_unpublish_device(device, true);
}
if (exported == 0 && driver->devices_used == 0 && gBootDevice >= 0) {
TRACE(("devfs: driver \"%s\" does not publish any more nodes and is "
"unloaded\n", driver->path));
unload_driver(driver);
}
return B_OK;
}
static status_t
load_driver(legacy_driver* driver)
{
status_t (*init_hardware)(void);
status_t (*init_driver)(void);
status_t status;
driver->binary_updated = false;
image_id image = driver->image;
if (image < 0) {
image = load_kernel_add_on(driver->path);
if (image < 0)
return image;
}
int32* apiVersion;
if (get_image_symbol(image, "api_version", B_SYMBOL_TYPE_DATA,
(void**)&apiVersion) == B_OK) {
#if B_CUR_DRIVER_API_VERSION != 2
#error Add checks here for new vs old api version!
#endif
if (*apiVersion > B_CUR_DRIVER_API_VERSION) {
dprintf("devfs: \"%s\" api_version %" B_PRId32 " not handled\n",
driver->name, *apiVersion);
status = B_BAD_VALUE;
goto error1;
}
if (*apiVersion < 1) {
dprintf("devfs: \"%s\" api_version invalid\n", driver->name);
status = B_BAD_VALUE;
goto error1;
}
driver->api_version = *apiVersion;
} else
dprintf("devfs: \"%s\" api_version missing\n", driver->name);
if (get_image_symbol(image, "publish_devices", B_SYMBOL_TYPE_TEXT,
(void**)&driver->publish_devices) != B_OK
|| get_image_symbol(image, "find_device", B_SYMBOL_TYPE_TEXT,
(void**)&driver->find_device) != B_OK) {
dprintf("devfs: \"%s\" mandatory driver symbol(s) missing!\n",
driver->name);
status = B_BAD_VALUE;
goto error1;
}
if (get_image_symbol(image, "init_hardware", B_SYMBOL_TYPE_TEXT,
(void**)&init_hardware) == B_OK
&& (status = init_hardware()) != B_OK) {
TRACE(("%s: init_hardware() failed: %s\n", driver->name,
strerror(status)));
status = ENXIO;
goto error1;
}
if (get_image_symbol(image, "init_driver", B_SYMBOL_TYPE_TEXT,
(void**)&init_driver) == B_OK
&& (status = init_driver()) != B_OK) {
TRACE(("%s: init_driver() failed: %s\n", driver->name,
strerror(status)));
status = ENXIO;
goto error2;
}
if (get_image_symbol(image, "uninit_driver", B_SYMBOL_TYPE_TEXT,
(void**)&driver->uninit_driver) != B_OK)
driver->uninit_driver = NULL;
if (get_image_symbol(image, "uninit_hardware", B_SYMBOL_TYPE_TEXT,
(void**)&driver->uninit_hardware) != B_OK)
driver->uninit_hardware = NULL;
driver->image = image;
return republish_driver(driver);
error2:
if (driver->uninit_hardware)
driver->uninit_hardware();
error1:
if (driver->image < 0) {
unload_kernel_add_on(image);
driver->image = status;
}
return status;
}
static status_t
unload_driver(legacy_driver* driver)
{
if (driver->image < 0) {
return B_NO_INIT;
}
if (driver->uninit_driver)
driver->uninit_driver();
if (driver->uninit_hardware)
driver->uninit_hardware();
unload_kernel_add_on(driver->image);
driver->image = -1;
driver->binary_updated = false;
driver->find_device = NULL;
driver->publish_devices = NULL;
driver->uninit_driver = NULL;
driver->uninit_hardware = NULL;
return B_OK;
}
static void
unpublish_driver(legacy_driver* driver)
{
while (LegacyDevice* device = driver->devices.RemoveHead()) {
device->SetRemovedFromParent(true);
devfs_unpublish_device(device, true);
}
}
static void
change_driver_watcher(dev_t device, ino_t node, bool add)
{
if (device == -1)
return;
driver_event* event = new (std::nothrow) driver_event(
add ? kAddWatcher : kRemoveWatcher);
if (event == NULL)
return;
event->node.device = device;
event->node.node = node;
MutexLocker _(sDriverEventsLock);
sDriverEvents.Add(event);
atomic_add(&sDriverEventsPending, 1);
}
static int32
get_priority(const char* path)
{
const directory_which whichPath[] = {
B_SYSTEM_DIRECTORY,
B_SYSTEM_NONPACKAGED_DIRECTORY,
B_USER_DIRECTORY
};
KPath pathBuffer;
for (uint32 index = 0; index < B_COUNT_OF(whichPath); index++) {
if (__find_directory(whichPath[index], gBootDevice, false,
pathBuffer.LockBuffer(), pathBuffer.BufferSize()) == B_OK) {
pathBuffer.UnlockBuffer();
if (strncmp(pathBuffer.Path(), path, pathBuffer.Length()) == 0)
return index;
} else
pathBuffer.UnlockBuffer();
}
return -1;
}
static const char*
get_leaf(const char* path)
{
const char* name = strrchr(path, '/');
if (name == NULL)
return path;
return name + 1;
}
static legacy_driver*
find_driver(dev_t device, ino_t node)
{
DriverTable::Iterator iterator(sDriverHash);
while (iterator.HasNext()) {
legacy_driver* driver = iterator.Next();
if (driver->device == device && driver->node == node)
return driver;
}
return NULL;
}
static status_t
add_driver(const char* path, image_id image)
{
struct stat stat;
if (image >= 0) {
stat.st_dev = -1;
stat.st_ino = -1;
} else {
if (::stat(path, &stat) != 0)
return errno;
}
int32 priority = get_priority(path);
RecursiveLocker _(sLock);
legacy_driver* driver = sDriverHash->Lookup(get_leaf(path));
if (driver != NULL) {
if (strcmp(driver->path, path) != 0 && priority >= driver->priority) {
free((char*)driver->path);
driver->path = strdup(path);
driver->name = get_leaf(driver->path);
driver->binary_updated = true;
}
if (priority >= driver->priority) {
driver->binary_updated = true;
return B_OK;
}
if (driver->image < B_OK)
return driver->image;
return B_OK;
}
driver = (legacy_driver*)malloc(sizeof(legacy_driver));
if (driver == NULL)
return B_NO_MEMORY;
driver->path = strdup(path);
if (driver->path == NULL) {
free(driver);
return B_NO_MEMORY;
}
driver->name = get_leaf(driver->path);
driver->device = stat.st_dev;
driver->node = stat.st_ino;
driver->image = image;
driver->last_modified = stat.st_mtim;
driver->devices_used = 0;
driver->binary_updated = false;
driver->priority = priority;
driver->api_version = 1;
driver->find_device = NULL;
driver->publish_devices = NULL;
driver->uninit_driver = NULL;
driver->uninit_hardware = NULL;
new(&driver->devices) DeviceList;
sDriverHash->Insert(driver);
if (stat.st_dev > 0)
change_driver_watcher(stat.st_dev, stat.st_ino, true);
return load_driver(driver);
}
symbol
*/
extern "C" status_t load_driver_symbols(const char* driverName);
status_t
load_driver_symbols(const char* driverName)
{
return B_OK;
}
static status_t
reload_driver(legacy_driver* driver)
{
dprintf("devfs: reload driver \"%s\" (%" B_PRIdDEV ", %" B_PRIdINO ")\n",
driver->name, driver->device, driver->node);
unload_driver(driver);
struct stat stat;
if (::stat(driver->path, &stat) == 0
&& (stat.st_dev != driver->device || stat.st_ino != driver->node)) {
change_driver_watcher(driver->device, driver->node, false);
driver->device = stat.st_dev;
driver->node = stat.st_ino;
change_driver_watcher(driver->device, driver->node, true);
}
status_t status = load_driver(driver);
if (status != B_OK)
unpublish_driver(driver);
return status;
}
static void
handle_driver_events(void* , int )
{
if (atomic_and(&sDriverEventsPending, 0) == 0)
return;
while (true) {
MutexLocker eventLocker(sDriverEventsLock);
driver_event* event = sDriverEvents.RemoveHead();
if (event == NULL)
break;
eventLocker.Unlock();
TRACE(("driver event %p, type %d\n", event, event->type));
switch (event->type) {
case kAddDriver:
{
RecursiveLocker locker(sLock);
TRACE((" add driver %p\n", event->path));
legacy_driver* driver = sDriverHash->Lookup(
get_leaf(event->path));
if (driver == NULL)
legacy_driver_add(event->path);
else if (get_priority(event->path) >= driver->priority)
driver->binary_updated = true;
break;
}
case kRemoveDriver:
{
RecursiveLocker locker(sLock);
TRACE((" remove driver %p\n", event->path));
legacy_driver* driver = sDriverHash->Lookup(
get_leaf(event->path));
if (driver != NULL
&& get_priority(event->path) >= driver->priority)
driver->binary_updated = true;
break;
}
case kAddWatcher:
TRACE((" add watcher %" B_PRId32 ":%" B_PRIdINO "\n", event->node.device,
event->node.node));
add_node_listener(event->node.device, event->node.node,
B_WATCH_STAT | B_WATCH_NAME, sDriverWatcher);
break;
case kRemoveWatcher:
TRACE((" remove watcher %" B_PRId32 ":%" B_PRIdINO "\n", event->node.device,
event->node.node));
remove_node_listener(event->node.device, event->node.node,
sDriverWatcher);
break;
}
delete event;
}
RecursiveLocker locker(sLock);
DriverTable::Iterator iterator(sDriverHash);
while (iterator.HasNext()) {
legacy_driver* driver = iterator.Next();
if (!driver->binary_updated || driver->devices_used != 0)
continue;
reload_driver(driver);
}
locker.Unlock();
}
DriverWatcher::DriverWatcher()
{
}
DriverWatcher::~DriverWatcher()
{
}
void
DriverWatcher::EventOccurred(NotificationService& service,
const KMessage* event)
{
int32 opcode = event->GetInt32("opcode", -1);
if (opcode != B_STAT_CHANGED
|| (event->GetInt32("fields", 0) & B_STAT_MODIFICATION_TIME) == 0)
return;
RecursiveLocker locker(sLock);
legacy_driver* driver = find_driver(event->GetInt32("device", -1),
event->GetInt64("node", 0));
if (driver == NULL)
return;
driver->binary_updated = true;
if (driver->devices_used == 0) {
atomic_add(&sDriverEventsPending, 1);
} else {
dprintf("devfs: changed driver \"%s\" is still in use\n", driver->name);
}
}
static void
dump_driver(legacy_driver* driver)
{
kprintf("DEVFS DRIVER: %p\n", driver);
kprintf(" name: %s\n", driver->name);
kprintf(" path: %s\n", driver->path);
kprintf(" image: %" B_PRId32 "\n", driver->image);
kprintf(" device: %" B_PRIdDEV "\n", driver->device);
kprintf(" node: %" B_PRIdINO "\n", driver->node);
kprintf(" last modified: %" B_PRIdTIME ".%ld\n", driver->last_modified.tv_sec,
driver->last_modified.tv_nsec);
kprintf(" devs used: %" B_PRIu32 "\n", driver->devices_used);
kprintf(" devs published: %" B_PRId32 "\n", driver->devices.Count());
kprintf(" binary updated: %d\n", driver->binary_updated);
kprintf(" priority: %" B_PRId32 "\n", driver->priority);
kprintf(" api version: %" B_PRId32 "\n", driver->api_version);
kprintf(" hooks: find_device %p, publish_devices %p\n"
" uninit_driver %p, uninit_hardware %p\n",
driver->find_device, driver->publish_devices, driver->uninit_driver,
driver->uninit_hardware);
}
static int
dump_device(int argc, char** argv)
{
if (argc < 2 || !strcmp(argv[1], "--help")) {
kprintf("usage: %s [device]\n", argv[0]);
return 0;
}
LegacyDevice* device = (LegacyDevice*)parse_expression(argv[1]);
kprintf("LEGACY DEVICE: %p\n", device);
kprintf(" path: %s\n", device->Path());
kprintf(" hooks: %p\n", device->Hooks());
device_hooks* hooks = device->Hooks();
kprintf(" close() %p\n", hooks->close);
kprintf(" free() %p\n", hooks->free);
kprintf(" control() %p\n", hooks->control);
kprintf(" read() %p\n", hooks->read);
kprintf(" write() %p\n", hooks->write);
kprintf(" select() %p\n", hooks->select);
kprintf(" deselect() %p\n", hooks->deselect);
dump_driver(device->Driver());
return 0;
}
static int
dump_driver(int argc, char** argv)
{
if (argc < 2) {
kprintf("address image used publ. pri name\n");
DriverTable::Iterator iterator(sDriverHash);
while (iterator.HasNext()) {
legacy_driver* driver = iterator.Next();
kprintf("%p %5" B_PRId32 " %3" B_PRIu32 " %5" B_PRId32 " %c "
"%3" B_PRId32 " %s\n", driver,
driver->image < 0 ? -1 : driver->image,
driver->devices_used, driver->devices.Count(),
driver->binary_updated ? 'U' : ' ', driver->priority,
driver->name);
}
return 0;
}
if (!strcmp(argv[1], "--help")) {
kprintf("usage: %s [name]\n", argv[0]);
return 0;
}
legacy_driver* driver = sDriverHash->Lookup(argv[1]);
if (driver == NULL) {
kprintf("Driver named \"%s\" not found.\n", argv[1]);
return 0;
}
dump_driver(driver);
return 0;
}
DirectoryIterator::DirectoryIterator(const char* path, const char* subPath,
bool recursive)
:
fDirectory(NULL),
fBasePath(NULL),
fCurrentName(NULL)
{
SetTo(path, subPath, recursive);
}
DirectoryIterator::~DirectoryIterator()
{
Unset();
}
void
DirectoryIterator::SetTo(const char* path, const char* subPath, bool recursive)
{
Unset();
fRecursive = recursive;
const bool disableUserAddOns = get_safemode_boolean(B_SAFEMODE_DISABLE_USER_ADD_ONS, false);
if (path == NULL) {
KPath pathBuffer;
for (int32 i = B_COUNT_OF(kDriverPaths) - 1; i >= 0; i--) {
if (i < 3 && disableUserAddOns)
continue;
if (__find_directory(kDriverPaths[i], gBootDevice, true,
pathBuffer.LockBuffer(), pathBuffer.BufferSize()) == B_OK) {
pathBuffer.UnlockBuffer();
pathBuffer.Append("kernel");
AddPath(pathBuffer.Path(), subPath);
} else
pathBuffer.UnlockBuffer();
}
} else
AddPath(path, subPath);
}
status_t
DirectoryIterator::GetNext(KPath& path, struct stat& stat)
{
next_directory:
while (fDirectory == NULL) {
delete fBasePath;
fBasePath = NULL;
if (!fPaths.Pop(&fBasePath))
return B_ENTRY_NOT_FOUND;
fDirectory = opendir(fBasePath->Path());
}
next_entry:
struct dirent* dirent = readdir(fDirectory);
if (dirent == NULL) {
closedir(fDirectory);
fDirectory = NULL;
goto next_directory;
}
if (!strcmp(dirent->d_name, "..") || !strcmp(dirent->d_name, "."))
goto next_entry;
fCurrentName = dirent->d_name;
path.SetTo(fBasePath->Path());
path.Append(fCurrentName);
if (::stat(path.Path(), &stat) != 0)
goto next_entry;
if (S_ISDIR(stat.st_mode) && fRecursive) {
KPath* nextPath = new(nothrow) KPath(path);
if (!nextPath)
return B_NO_MEMORY;
if (fPaths.Push(nextPath) != B_OK)
return B_NO_MEMORY;
goto next_entry;
}
return B_OK;
}
void
DirectoryIterator::Unset()
{
if (fDirectory != NULL) {
closedir(fDirectory);
fDirectory = NULL;
}
delete fBasePath;
fBasePath = NULL;
KPath* path;
while (fPaths.Pop(&path))
delete path;
}
void
DirectoryIterator::AddPath(const char* basePath, const char* subPath)
{
KPath* path = new(nothrow) KPath(basePath);
if (!path)
panic("out of memory");
if (subPath != NULL)
path->Append(subPath);
fPaths.Push(path);
}
DirectoryWatcher::DirectoryWatcher()
{
}
DirectoryWatcher::~DirectoryWatcher()
{
}
void
DirectoryWatcher::EventOccurred(NotificationService& service,
const KMessage* event)
{
int32 opcode = event->GetInt32("opcode", -1);
dev_t device = event->GetInt32("device", -1);
ino_t directory = event->GetInt64("directory", -1);
const char* name = event->GetString("name", NULL);
if (opcode == B_ENTRY_MOVED) {
ino_t from = event->GetInt64("from directory", -1);
ino_t to = event->GetInt64("to directory", -1);
if (sDirectoryNodeHash.Lookup(&from) == NULL) {
directory = to;
opcode = B_ENTRY_CREATED;
} else if (sDirectoryNodeHash.Lookup(&to) == NULL) {
directory = from;
opcode = B_ENTRY_REMOVED;
} else {
return;
}
}
KPath path(B_PATH_NAME_LENGTH + 1);
if (path.InitCheck() != B_OK || vfs_entry_ref_to_path(device, directory,
name, true, path.LockBuffer(), path.BufferSize()) != B_OK)
return;
path.UnlockBuffer();
dprintf("driver \"%s\" %s\n", path.Leaf(),
opcode == B_ENTRY_CREATED ? "added" : "removed");
driver_event* driverEvent = new(std::nothrow) driver_event(
opcode == B_ENTRY_CREATED ? kAddDriver : kRemoveDriver);
if (driverEvent == NULL)
return;
strlcpy(driverEvent->path, path.Path(), sizeof(driverEvent->path));
MutexLocker _(sDriverEventsLock);
sDriverEvents.Add(driverEvent);
atomic_add(&sDriverEventsPending, 1);
}
static void
start_watching(const char* base, const char* sub)
{
KPath path(base);
path.Append(sub);
struct stat stat;
if (::stat(path.Path(), &stat) != 0)
return;
add_node_listener(stat.st_dev, stat.st_ino, B_WATCH_DIRECTORY,
sDirectoryWatcher);
directory_node_entry* entry = new(std::nothrow) directory_node_entry;
if (entry != NULL) {
entry->node = stat.st_ino;
sDirectoryNodeHash.Insert(entry);
}
}
static struct driver_entry*
new_driver_entry(const char* path, dev_t device, ino_t node)
{
driver_entry* entry = new(std::nothrow) driver_entry;
if (entry == NULL)
return NULL;
entry->path = strdup(path);
if (entry->path == NULL) {
delete entry;
return NULL;
}
entry->device = device;
entry->node = node;
entry->busses = 0;
return entry;
}
The list is emptied and freed during the traversal.
*/
static status_t
try_drivers(DriverEntryList& list)
{
while (true) {
driver_entry* entry = list.RemoveHead();
if (entry == NULL)
break;
image_id image = load_kernel_add_on(entry->path);
if (image >= 0) {
if (legacy_driver_add(entry->path) == B_OK) {
dprintf("loaded driver %s\n", entry->path);
}
unload_kernel_add_on(image);
}
free(entry->path);
delete entry;
}
return B_OK;
}
static status_t
probe_for_drivers(const char* type)
{
TRACE(("probe_for_drivers(type = %s)\n", type));
if (gBootDevice < 0)
return B_OK;
DriverEntryList drivers;
DirectoryIterator iterator(NULL, type, false);
struct stat stat;
KPath path;
while (iterator.GetNext(path, stat) == B_OK) {
if (S_ISDIR(stat.st_mode)) {
add_node_listener(stat.st_dev, stat.st_ino, B_WATCH_DIRECTORY,
sDirectoryWatcher);
directory_node_entry* entry
= new(std::nothrow) directory_node_entry;
if (entry != NULL) {
entry->node = stat.st_ino;
sDirectoryNodeHash.Insert(entry);
}
size_t length = strlen("drivers/dev");
if (strncmp(type, "drivers/dev", length))
continue;
path.SetTo(type);
path.Append(iterator.CurrentName());
devfs_publish_directory(path.Path() + length + 1);
continue;
}
driver_entry* entry = new_driver_entry(path.Path(), stat.st_dev,
stat.st_ino);
if (entry == NULL)
return B_NO_MEMORY;
TRACE(("found potential driver: %s\n", path.Path()));
drivers.Add(entry);
}
if (drivers.IsEmpty())
return B_OK;
try_drivers(drivers);
return B_OK;
}
LegacyDevice::LegacyDevice(legacy_driver* driver, const char* path,
device_hooks* hooks)
:
fDriver(driver),
fRepublished(true),
fRemovedFromParent(false)
{
fDeviceModule = (device_module_info*)malloc(sizeof(device_module_info));
if (fDeviceModule != NULL) {
memset(fDeviceModule, 0, sizeof(device_module_info));
SetHooks(hooks);
}
fDeviceData = this;
fPath = strdup(path);
}
LegacyDevice::~LegacyDevice()
{
free(fDeviceModule);
free((char*)fPath);
}
status_t
LegacyDevice::InitCheck() const
{
return fDeviceModule != NULL && fPath != NULL ? B_OK : B_NO_MEMORY;
}
status_t
LegacyDevice::InitDevice()
{
RecursiveLocker _(sLock);
if (fInitialized++ > 0)
return B_OK;
if (fDriver != NULL && fDriver->devices_used == 0
&& (fDriver->image < 0 || fDriver->binary_updated)) {
status_t status = reload_driver(fDriver);
if (status < B_OK)
return status;
}
if (fDriver != NULL)
fDriver->devices_used++;
return B_OK;
}
void
LegacyDevice::UninitDevice()
{
RecursiveLocker _(sLock);
if (fInitialized-- > 1)
return;
if (fDriver != NULL) {
if (--fDriver->devices_used == 0 && fDriver->devices.IsEmpty())
unload_driver(fDriver);
fDriver = NULL;
}
}
void
LegacyDevice::Removed()
{
RecursiveLocker _(sLock);
if (!fRemovedFromParent && fDriver != NULL)
fDriver->devices.Remove(this);
delete this;
}
status_t
LegacyDevice::Control(void* _cookie, int32 op, void* buffer, size_t length)
{
switch (op) {
case B_GET_DRIVER_FOR_DEVICE:
if (length != 0 && length <= strlen(fDriver->path))
return ERANGE;
return user_strlcpy(static_cast<char*>(buffer), fDriver->path, length);
default:
return AbstractModuleDevice::Control(_cookie, op, buffer, length);
}
}
void
LegacyDevice::SetHooks(device_hooks* hooks)
{
fHooks = hooks;
fDeviceModule->close = hooks->close;
fDeviceModule->free = hooks->free;
fDeviceModule->control = hooks->control;
fDeviceModule->read = hooks->read;
fDeviceModule->write = hooks->write;
if (fDriver == NULL || fDriver->api_version >= 2) {
if (hooks->select != NULL) {
fDeviceModule->select = (status_t (*)(void*, uint8, selectsync*))~0;
}
fDeviceModule->deselect = hooks->deselect;
}
}
status_t
LegacyDevice::Open(const char* path, int openMode, void** _cookie)
{
return Hooks()->open(path, openMode, _cookie);
}
status_t
LegacyDevice::Select(void* cookie, uint8 event, selectsync* sync)
{
return Hooks()->select(cookie, event, 0, sync);
}
extern "C" void
legacy_driver_add_preloaded(kernel_args* args)
{
KPath basePath;
status_t status = __find_directory(B_SYSTEM_ADDONS_DIRECTORY,
gBootDevice, false, basePath.LockBuffer(), basePath.BufferSize());
if (status != B_OK) {
dprintf("legacy_driver_add_preloaded: find_directory() failed: "
"%s\n", strerror(status));
}
basePath.UnlockBuffer();
if (status == B_OK)
status = basePath.Append("kernel");
if (status != B_OK) {
dprintf("legacy_driver_add_preloaded: constructing base driver "
"path failed: %s\n", strerror(status));
return;
}
struct preloaded_image* image;
for (image = args->preloaded_images; image != NULL; image = image->next) {
if (image->is_module || image->id < 0)
continue;
KPath imagePath(basePath);
status = imagePath.Append(image->name);
TRACE(("legacy_driver_add_preloaded: adding driver %s\n",
imagePath.Path()));
if (status == B_OK)
status = add_driver(imagePath.Path(), image->id);
if (status != B_OK) {
dprintf("legacy_driver_add_preloaded: Failed to add \"%s\": %s\n",
(char*)image->name, strerror(status));
unload_kernel_add_on(image->id);
}
}
}
extern "C" status_t
legacy_driver_add(const char* path)
{
return add_driver(path, -1);
}
extern "C" status_t
legacy_driver_publish(const char* path, device_hooks* hooks)
{
LegacyDevice* device = new(std::nothrow) LegacyDevice(NULL, path, hooks);
if (device == NULL)
return B_NO_MEMORY;
status_t status = device->InitCheck();
if (status == B_OK)
status = devfs_publish_device(path, device);
if (status != B_OK)
delete device;
return status;
}
extern "C" status_t
legacy_driver_rescan(const char* driverName)
{
RecursiveLocker locker(sLock);
legacy_driver* driver = sDriverHash->Lookup(driverName);
if (driver == NULL)
return B_ENTRY_NOT_FOUND;
return republish_driver(driver);
}
extern "C" status_t
legacy_driver_probe(const char* subPath)
{
TRACE(("legacy_driver_probe(type = %s)\n", subPath));
char devicePath[64];
snprintf(devicePath, sizeof(devicePath), "drivers/dev%s%s",
subPath[0] ? "/" : "", subPath);
if (!sWatching && gBootDevice > 0) {
KPath path;
new(&sDirectoryWatcher) DirectoryWatcher;
bool disableUserAddOns = get_safemode_boolean(
B_SAFEMODE_DISABLE_USER_ADD_ONS, false);
for (uint32 i = 0; i < sizeof(kDriverPaths) / sizeof(kDriverPaths[0]); i++) {
if (i < 3 && disableUserAddOns)
continue;
if (__find_directory(kDriverPaths[i], gBootDevice, true,
path.LockBuffer(), path.BufferSize()) == B_OK) {
path.UnlockBuffer();
path.Append("kernel/drivers");
start_watching(path.Path(), "bin");
} else
path.UnlockBuffer();
}
sWatching = true;
}
return probe_for_drivers(devicePath);
}
extern "C" status_t
legacy_driver_init(void)
{
sDriverHash = new(std::nothrow) DriverTable();
if (sDriverHash == NULL || sDriverHash->Init(DRIVER_HASH_SIZE) != B_OK)
return B_NO_MEMORY;
recursive_lock_init(&sLock, "legacy driver");
new(&sDriverWatcher) DriverWatcher;
new(&sDriverEvents) DriverEventList;
register_kernel_daemon(&handle_driver_events, NULL, 10);
add_debugger_command("legacy_driver", &dump_driver,
"info about a legacy driver entry");
add_debugger_command("legacy_device", &dump_device,
"info about a legacy device");
return B_OK;
}