* If path equals "" it won't be published anymore.

* load_driver() now uses republish_driver() instead of publishing all
  devices every time. This allows published nodes to remain stable, no
  matter if the driver has been unloaded in the mean time or not.
* Offloaded driver reloading to a kernel daemon. Since this is regarded 
  to happen rarely, a simple event variable will be set when something
  happened, and the daemon will then check all known drivers.
* devfs_open() will now check if the driver has to be reloaded, and will
  do so before opening the device.
* Moved republish_driver() around again.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@24141 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Axel Dörfler 2008-02-26 14:54:41 +00:00
parent e1a5a1db60
commit e6fdb84b92
1 changed files with 238 additions and 197 deletions

View File

@ -165,12 +165,15 @@ static status_t unpublish_node(struct devfs *fs, devfs_vnode *node,
static status_t publish_device(struct devfs *fs, const char *path,
device_node_info *deviceNode, pnp_devfs_driver_info *info,
driver_entry *driver, device_hooks *ops, int32 apiVersion);
static status_t unload_driver(driver_entry *driver);
static status_t load_driver(driver_entry *driver);
/* the one and only allowed devfs instance */
static struct devfs *sDeviceFileSystem = NULL;
static int32 sDefaultApiVersion = 1;
static DriverWatcher sDriverWatcher;
static int32 sDriverEvents;
// #pragma mark - driver private
@ -199,180 +202,6 @@ driver_entry_compare(void *_driver, const void *_key)
}
static status_t
load_driver(driver_entry *driver)
{
status_t (*init_hardware)(void);
status_t (*init_driver)(void);
const char **devicePaths;
int32 exported = 0;
status_t status;
// load the module
image_id image = driver->image;
if (image < 0) {
image = load_kernel_add_on(driver->path);
if (image < 0)
return image;
}
// For a valid device driver the following exports are required
driver->api_version = &sDefaultApiVersion;
if (get_image_symbol(image, "api_version", B_SYMBOL_TYPE_DATA,
(void **)&driver->api_version) == B_OK) {
#if B_CUR_DRIVER_API_VERSION != 2
// just in case someone decides to bump up the api version
#error Add checks here for new vs old api version!
#endif
if (*driver->api_version > B_CUR_DRIVER_API_VERSION) {
dprintf("devfs: \"%s\" api_version %ld not handled\n", driver->name,
*driver->api_version);
status = B_BAD_VALUE;
goto error1;
}
if (*driver->api_version < 1) {
dprintf("devfs: \"%s\" api_version invalid\n", driver->name);
status = B_BAD_VALUE;
goto error1;
}
} 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;
}
// Init the driver
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;
}
// resolve and cache those for the driver unload code
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;
// The driver has successfully been initialized, now we can
// finally publish its device entries
// we keep the driver loaded if it exports at least a single interface
devicePaths = driver->publish_devices();
if (devicePaths == NULL) {
TRACE(("%s: publish_devices() returned NULL.\n", driver->name));
status = ENXIO;
goto error3;
}
for (; devicePaths[0]; devicePaths++) {
device_hooks *hooks = driver->find_device(devicePaths[0]);
if (hooks != NULL
&& publish_device(sDeviceFileSystem, devicePaths[0],
NULL, NULL, driver, hooks, *driver->api_version) == B_OK)
exported++;
}
// we're all done, driver will be kept loaded
if (exported > 0) {
driver->image = image;
return B_OK;
}
status = B_ERROR; // whatever...
error3:
if (driver->uninit_driver)
driver->uninit_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(driver_entry *driver)
{
if (driver->image < 0) {
// driver is not currently loaded
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;
return B_OK;
}
/*! Collects all devices belonging to the \a driver and unpublishs them.
*/
static void
unpublish_driver(driver_entry *driver)
{
RecursiveLocker locker(&sDeviceFileSystem->lock);
// Iterate through all nodes until all devices of this driver have
// been unpublished
while (driver->devices_published > 0) {
struct hash_iterator i;
hash_open(sDeviceFileSystem->vnode_hash, &i);
while (true) {
devfs_vnode *vnode = (devfs_vnode *)hash_next(
sDeviceFileSystem->vnode_hash, &i);
if (vnode == NULL)
break;
if (S_ISCHR(vnode->stream.type)
&& vnode->stream.u.dev.driver == driver)
unpublish_node(sDeviceFileSystem, vnode, S_IFCHR);
}
hash_close(sDeviceFileSystem->vnode_hash, &i, false);
}
}
/*! Collects all published devices of a driver, compares them to what the
driver would publish now, and then publishes/unpublishes the devices
as needed.
@ -470,6 +299,162 @@ republish_driver(driver_entry *driver)
}
static status_t
load_driver(driver_entry *driver)
{
status_t (*init_hardware)(void);
status_t (*init_driver)(void);
const char **devicePaths;
int32 exported = 0;
status_t status;
// load the module
image_id image = driver->image;
if (image < 0) {
image = load_kernel_add_on(driver->path);
if (image < 0)
return image;
}
// For a valid device driver the following exports are required
driver->api_version = &sDefaultApiVersion;
if (get_image_symbol(image, "api_version", B_SYMBOL_TYPE_DATA,
(void **)&driver->api_version) == B_OK) {
#if B_CUR_DRIVER_API_VERSION != 2
// just in case someone decides to bump up the api version
#error Add checks here for new vs old api version!
#endif
if (*driver->api_version > B_CUR_DRIVER_API_VERSION) {
dprintf("devfs: \"%s\" api_version %ld not handled\n", driver->name,
*driver->api_version);
status = B_BAD_VALUE;
goto error1;
}
if (*driver->api_version < 1) {
dprintf("devfs: \"%s\" api_version invalid\n", driver->name);
status = B_BAD_VALUE;
goto error1;
}
} 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;
}
// Init the driver
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;
}
// resolve and cache those for the driver unload code
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;
// The driver has successfully been initialized, now we can
// finally publish its device entries
driver->image = image;
return republish_driver(driver);
error3:
if (driver->uninit_driver)
driver->uninit_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(driver_entry *driver)
{
if (driver->image < 0) {
// driver is not currently loaded
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;
return B_OK;
}
/*! Collects all devices belonging to the \a driver and unpublishs them.
*/
static void
unpublish_driver(driver_entry *driver)
{
RecursiveLocker locker(&sDeviceFileSystem->lock);
// Iterate through all nodes until all devices of this driver have
// been unpublished
while (driver->devices_published > 0) {
struct hash_iterator i;
hash_open(sDeviceFileSystem->vnode_hash, &i);
while (true) {
devfs_vnode *vnode = (devfs_vnode *)hash_next(
sDeviceFileSystem->vnode_hash, &i);
if (vnode == NULL)
break;
if (S_ISCHR(vnode->stream.type)
&& vnode->stream.u.dev.driver == driver) {
unpublish_node(sDeviceFileSystem, vnode, S_IFCHR);
break;
}
}
hash_close(sDeviceFileSystem->vnode_hash, &i, false);
}
}
static const char *
get_leaf(const char *path)
{
@ -615,6 +600,52 @@ scan_for_drivers(devfs_vnode *dir)
}
static status_t
reload_driver(driver_entry *driver)
{
dprintf("devfs: reload driver \"%s\"\n", driver->name);
unload_driver(driver);
status_t status = load_driver(driver);
if (status < B_OK)
unpublish_driver(driver);
return status;
}
static void
handle_driver_events(void *_fs, int /*iteration*/)
{
struct devfs *fs = (devfs *)_fs;
if (atomic_and(&sDriverEvents, 0) == 0)
return;
// something happened, let's see what it was
RecursiveLocker locker(fs->lock);
hash_iterator iterator;
hash_open(sDeviceFileSystem->driver_hash, &iterator);
driver_entry *driver;
while (true) {
driver = (driver_entry *)hash_next(sDeviceFileSystem->driver_hash,
&iterator);
if (driver == NULL)
break;
if (!driver->binary_updated || driver->devices_used != 0)
continue;
// try to reload the driver
reload_driver(driver);
}
hash_close(sDeviceFileSystem->driver_hash, &iterator, false);
}
// #pragma mark - DriverWatcher
@ -643,19 +674,14 @@ DriverWatcher::EventOccured(NotificationService& service,
if (driver == NULL)
return;
driver->binary_updated = true;
dprintf("%s: devices published %ld, used %ld\n", driver->name, driver->devices_published, driver->devices_used);
if (driver->devices_used == 0) {
// reload driver
dprintf("devfs: reload driver \"%s\"\n", driver->name);
//unpublish_driver(driver);
// TODO: even though we would need to unpublish the driver, this
// doesn't work right now from here (dead lock in the node monitor
// notifications)
unload_driver(driver);
load_driver(driver);
// trigger a reload of the driver
atomic_add(&sDriverEvents, 1);
} else {
// driver is in use right now, only mark it to be updated when possible
// driver is in use right now
dprintf("devfs: changed driver \"%s\" is still in use\n", driver->name);
driver->binary_updated = true;
}
}
@ -749,8 +775,7 @@ devfs_delete_vnode(struct devfs *fs, struct devfs_vnode *vnode,
}
/** makes sure none of the dircookies point to the vnode passed in */
/*! Makes sure none of the dircookies point to the vnode passed in */
static void
update_dir_cookies(struct devfs_vnode *dir, struct devfs_vnode *vnode)
{
@ -1189,7 +1214,7 @@ publish_device(struct devfs *fs, const char *path, device_node_info *deviceNode,
}
if ((ops == NULL && (deviceNode == NULL || info == NULL))
|| path == NULL || path[0] == '/')
|| path == NULL || path[0] == '\0' || path[0] == '/')
return B_BAD_VALUE;
// are the provided device hooks okay?
@ -1242,11 +1267,10 @@ publish_device(struct devfs *fs, const char *path, device_node_info *deviceNode,
}
/** Construct complete device name (as used for device_open()).
* This is safe to use only when the device is in use (and therefore
* cannot be unpublished during the iteration).
*/
/*! Construct complete device name (as used for device_open()).
This is safe to use only when the device is in use (and therefore
cannot be unpublished during the iteration).
*/
static void
get_device_name(struct devfs_vnode *vnode, char *buffer, size_t size)
{
@ -1349,6 +1373,8 @@ devfs_mount(dev_t id, const char *devfs, uint32 flags, const char *args,
}
new(&sDriverWatcher) DriverWatcher;
register_kernel_daemon(&handle_driver_events, fs, 10);
// once every second
// create a vnode
vnode = devfs_create_vnode(fs, NULL, "");
@ -1396,6 +1422,8 @@ devfs_unmount(fs_volume _fs)
TRACE(("devfs_unmount: entry fs = %p\n", fs));
unregister_kernel_daemon(&handle_driver_events, fs);
// release the reference to the root
put_vnode(fs->id, fs->root_vnode->id);
@ -1631,6 +1659,20 @@ devfs_open(fs_volume _fs, fs_vnode _vnode, int openMode, fs_cookie *_cookie)
return B_NO_MEMORY;
if (S_ISCHR(vnode->stream.type)) {
RecursiveLocker locker(fs->lock);
driver_entry *driver = vnode->stream.u.dev.driver;
// TODO: we might want to check if the current node does still exist
// (it should fail in the driver's open(), though, if it doesn't)
if (driver != NULL
&& (driver->image < 0 || driver->binary_updated)) {
status = reload_driver(driver);
if (status < B_OK)
return status;
}
locker.Unlock();
if (vnode->stream.u.dev.node != NULL) {
status = vnode->stream.u.dev.info->open(
vnode->stream.u.dev.node->parent->cookie, openMode,
@ -1643,10 +1685,10 @@ devfs_open(fs_volume _fs, fs_vnode _vnode, int openMode, fs_cookie *_cookie)
&cookie->device_cookie);
}
RecursiveLocker _(fs->lock);
locker.Lock();
if (status == B_OK && vnode->stream.u.dev.driver != NULL)
vnode->stream.u.dev.driver->devices_used++;
if (status == B_OK && driver != NULL)
driver->devices_used++;
}
if (status < B_OK)
free(cookie);
@ -2460,8 +2502,7 @@ static const device_attr pnp_devfs_attrs[] = {
};
/** someone registered a device */
/*! Someone registered a device */
static status_t
pnp_devfs_register_device(device_node_handle parent)
{