blob: c824c6b7e071c0d017a3ac6802e824c32af7b7e4 [file] [log] [blame] [edit]
// SPDX-License-Identifier: GPL-2.0
/*
* Event notifications.
*
* Copyright (C) 2019 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*/
#include <linux/watch_queue.h>
#include <linux/syscalls.h>
#include <linux/init_task.h>
#include <linux/security.h>
/*
* Global queue for watching for device layer events.
*/
static struct watch_list device_watchers = {
.watchers = HLIST_HEAD_INIT,
.lock = __SPIN_LOCK_UNLOCKED(&device_watchers.lock),
};
static DEFINE_SPINLOCK(device_watchers_lock);
/**
* post_device_notification - Post notification of a device event
* @n - The notification to post
* @id - The device ID
*
* Note that there's only a global queue to which all events are posted. Might
* want to provide per-dev queues also.
*/
void post_device_notification(struct watch_notification *n, u64 id)
{
post_watch_notification(&device_watchers, n, &init_cred, id);
}
/**
* sys_watch_devices - Watch for device events.
* @watch_fd: The watch queue to send notifications to.
* @watch_id: The watch ID to be placed in the notification (-1 to remove watch)
* @flags: Flags (reserved for future)
*/
SYSCALL_DEFINE3(watch_devices, int, watch_fd, int, watch_id, unsigned int, flags)
{
struct watch_queue *wqueue;
struct watch_list *wlist = &device_watchers;
struct watch *watch = NULL;
long ret = -ENOMEM;
u64 id = 0; /* Might want to allow dev# here. */
if (watch_id < -1 || watch_id > 0xff || flags)
return -EINVAL;
wqueue = get_watch_queue(watch_fd);
if (IS_ERR(wqueue)) {
ret = PTR_ERR(wqueue);
goto err;
}
if (watch_id >= 0) {
watch = kzalloc(sizeof(*watch), GFP_KERNEL);
if (!watch)
goto err_wqueue;
init_watch(watch, wqueue);
watch->id = id;
watch->info_id = (u32)watch_id << WATCH_INFO_ID__SHIFT;
ret = security_watch_devices(watch);
if (ret < 0)
goto err_watch;
spin_lock(&device_watchers_lock);
ret = add_watch_to_object(watch, wlist);
spin_unlock(&device_watchers_lock);
if (ret == 0)
watch = NULL;
} else {
spin_lock(&device_watchers_lock);
ret = remove_watch_from_object(wlist, wqueue, id, false);
spin_unlock(&device_watchers_lock);
}
err_watch:
kfree(watch);
err_wqueue:
put_watch_queue(wqueue);
err:
return ret;
}