See Documentation/vbus.txt for details Signed-off-by: Gregory Haskins <ghaskins@xxxxxxxxxx> --- Documentation/vbus.txt | 386 +++++++++++++++++++++++++++++ arch/x86/Kconfig | 2 fs/proc/base.c | 96 +++++++ include/linux/sched.h | 4 include/linux/vbus.h | 147 +++++++++++ include/linux/vbus_device.h | 416 ++++++++++++++++++++++++++++++++ kernel/Makefile | 1 kernel/exit.c | 2 kernel/fork.c | 2 kernel/vbus/Kconfig | 14 + kernel/vbus/Makefile | 1 kernel/vbus/attribute.c | 52 ++++ kernel/vbus/config.c | 275 +++++++++++++++++++++ kernel/vbus/core.c | 567 +++++++++++++++++++++++++++++++++++++++++++ kernel/vbus/devclass.c | 124 +++++++++ kernel/vbus/map.c | 72 +++++ kernel/vbus/map.h | 41 +++ kernel/vbus/vbus.h | 116 +++++++++ 18 files changed, 2318 insertions(+), 0 deletions(-) create mode 100644 Documentation/vbus.txt create mode 100644 include/linux/vbus.h create mode 100644 include/linux/vbus_device.h create mode 100644 kernel/vbus/Kconfig create mode 100644 kernel/vbus/Makefile create mode 100644 kernel/vbus/attribute.c create mode 100644 kernel/vbus/config.c create mode 100644 kernel/vbus/core.c create mode 100644 kernel/vbus/devclass.c create mode 100644 kernel/vbus/map.c create mode 100644 kernel/vbus/map.h create mode 100644 kernel/vbus/vbus.h diff --git a/Documentation/vbus.txt b/Documentation/vbus.txt new file mode 100644 index 0000000..e8a05da --- /dev/null +++ b/Documentation/vbus.txt @@ -0,0 +1,386 @@ + +Virtual-Bus: +====================== +Author: Gregory Haskins <ghaskins@xxxxxxxxxx> + + + + +What is it? +-------------------- + +Virtual-Bus is a kernel based IO resource container technology. It is modeled +on a concept similar to the Linux Device-Model (LDM), where we have buses, +devices, and drivers as the primary actors. However, VBUS has several +distinctions when contrasted with LDM: + + 1) "Busses" in LDM are relatively static and global to the kernel (e.g. + "PCI", "USB", etc). VBUS buses are arbitrarily created and destroyed + dynamically, and are not globally visible. Instead they are defined as + visible only to a specific subset of the system (the contained context). + 2) "Devices" in LDM are typically tangible physical (or sometimes logical) + devices. VBUS devices are purely software abstractions (which may or + may not have one or more physical devices behind them). Devices may + also be arbitrarily created or destroyed by software/administrative action + as opposed to by a hardware discovery mechanism. + 3) "Drivers" in LDM sit within the same kernel context as the busses and + devices they interact with. VBUS drivers live in a foreign + context (such as userspace, or a virtual-machine guest). + +The idea is that a vbus is created to contain access to some IO services. +Virtual devices are then instantiated and linked to a bus to grant access to +drivers actively present on the bus. Drivers will only have visibility to +devices present on their respective bus, and nothing else. + +Virtual devices are defined by modules which register a deviceclass with the +system. A deviceclass simply represents a type of device that _may_ be +instantiated into a device, should an administrator wish to do so. Once +this has happened, the device may be associated with one or more buses where +it will become visible to all clients of those respective buses. + +Why do we need this? +---------------------- + +There are various reasons why such a construct may be useful. One of the +most interesting use cases is for virtualization, such as KVM. Hypervisors +today provide virtualized IO resources to a guest, but this is often at a cost +in both latency and throughput compared to bare metal performance. Utilizing +para-virtual resources instead of emulated devices helps to mitigate this +penalty, but even these techniques to date have not fully realized the +potential of the underlying bare-metal hardware. + +Some of the performance differential is unavoidable just given the extra +processing that occurs due to the deeper stack (guest+host). However, some of +this overhead is a direct result of the rather indirect path most hypervisors +use to route IO. For instance, KVM uses PIO faults from the guest to trigger +a guest->host-kernel->host-userspace->host-kernel sequence of events. +Contrast this to a typical userspace application on the host which must only +traverse app->kernel for most IO. + +The fact is that the linux kernel is already great at managing access to IO +resources. Therefore, if you have a hypervisor that is based on the linux +kernel, is there some way that we can allow the hypervisor to manage IO +directly instead of forcing this convoluted path? + +The short answer is: "not yet" ;) + +In order to use such a concept, we need some new facilties. For one, we +need to be able to define containers with their corresponding access-control so +that guests do not have unmitigated access to anything they wish. Second, +we also need to define some forms of memory access that is uniform in the face +of various clients (e.g. "copy_to_user()" cannot be assumed to work for, say, +a KVM vcpu context). Lastly, we need to provide access to these resources in +a way that makes sense for the application, such as asynchronous communication +paths and minimizing context switches. + +So we introduce VBUS as a framework to provide such facilities. The net +result is a *substantial* reduction in IO overhead, even when compared to +state of the art para-virtualization techniques (such as virtio-net). + +How do I use it? +------------------------ + +There are two components to utilizing a virtual-bus. One is the +administrative function (creating and configuring a bus and its devices). The +other is the consumption of the resources on the bus by a client (e.g. a +virtual machine, or a userspace application). The former occurs on the host +kernel by means of interacting with various special filesystems (e.g. sysfs, +configfs, etc). The latter occurs by means of a "vbus connector" which must +be developed specifically to bridge a particular environment. To date, we +have developed such connectors for host-userspace and kvm-guests. Conceivably +we could develop other connectors as needs arise (e.g. lguest, xen, +guest-userspace, etc). This document deals with the administrative interface. +Details about developing a connector are out of scope for this document. + +Interacting with vbus +------------------------ + +The first step is to enable virtual-bus support (CONFIG_VBUS) as well as any +desired vbus-device modules (e.g. CONFIG_VBUS_VENETTAP), and ensure that your +environment mounts both sysfs and configfs somewhere in the filesystem. This +document will assume they are mounted to /sys and /config, respectively. + +VBUS will create a top-level directory "vbus" in each of the two respective +filesystems. At boot-up, they will look like the following: + +/sys/vbus/ +|-- deviceclass +|-- devices +|-- instances +`-- version + +/config/vbus/ +|-- devices +`-- instances + +Following their respective roles, /config/vbus is for userspace to manage the +lifetime of some number of objects/attributes. This is in contrast to +/sys/vbus which is a reflection of objects managed by the kernel. It is +assumed the reader is already familiar with these two facilities, so we will +not go into depth about their general operation. Suffice to say that vbus +consists of objects that are managed both by userspace and the kernel. +Modification of objects via /config/vbus will typically be reflected in the +/sys/vbus area. + +It all starts with a deviceclass +-------------------------------- + +Before you can do anything useful with vbus, you need some registered +deviceclasses. A deviceclass provides the implementation of a specific type +of virtual device. A deviceclass will typically be registered by loading a +kernel-module. Once loaded, the available device types are enumerated under +/sys/vbus/deviceclass. For example, we will load our "venet-tap" module, +which provides network services: + +# modprobe venet-tap +# tree /sys/vbus +/sys/vbus +|-- deviceclass +| `-- venet-tap +|-- devices +|-- instances +`-- version + +An administrative agent should be able to enumerate /sys/vbus/deviceclass to +determine what services are available on a given platform. + +Create the container +------------------- + +The next step is to create a new container. In vbus, this comes in the form +of a vbus-instance and it is created by a simple "mkdir" in the +/config/vbus/instances area. The only requirement is that the instance is +given a host-wide unique name. This may be some kind of association to the +application (e.g. the unique VM GUID) or it can be arbitrary. For the +purposes of example, we will let $(uuidgen) generate a random UUID for us. + +# mkdir /config/vbus/instances/$(uuidgen) +# tree /sys/vbus/ +/sys/vbus/ +|-- deviceclass +| `-- venet-tap +|-- devices +|-- instances +| `-- beb4df8f-7483-4028-b3f7-767512e2a18c +| |-- devices +| `-- members +`-- version + +So we can see that we have now created a vbus called + + "beb4df8f-7483-4028-b3f7-767512e2a18c" + +in the /config area, and it was immediately reflected in the +/sys/vbus/instances area as well (with a few subobjects of its own: "devices" +and "members"). The "devices" object denotes any devices that are present on +the bus (in this case: none). Likewise, "members" denotes the pids of any +tasks that are members of the bus (in this case: none). We will come back to +this later. For now, we move on to the next step + +Create a device instance +------------------------ + +Devices are instantiated by again utilizing the /config/vbus configfs area. +At first you may suspect that devices are created as subordinate objects of a +bus/container instance, but you would be mistaken. Devices are actually +root-level objects in vbus specifically to allow greater flexibility in the +association of a device. For instance, it may be desirable to have a single +device that spans multiple VMs (consider an ethernet switch, or a shared disk +for a cluster). Therefore, device lifecycles are managed by creating/deleting +objects in /config/vbus/devices. + +Note: Creating a device instance is actually a two step process: We need to +give the device instance a unique name, and we also need to give it a specific +device type. It is hard to express both parameters using standard filesystem +operations like mkdir, so the design decision was made to require performing +the operation in two steps. + +Our first step is to create a unique instance. We will again utilize +$(uuidgen) to yield an arbitrary name. Any name will suffice as long as it is +unqie on this particular host. + +# mkdir /config/vbus/devices/$(uuidgen) +# tree /sys/vbus +/sys/vbus +|-- deviceclass +| `-- venet-tap +|-- devices +| `-- 6a1aff24-5dc0-4aea-9c35-435daef90e55 +| `-- interfaces +|-- instances +| `-- beb4df8f-7483-4028-b3f7-767512e2a18c +| |-- devices +| `-- members +`-- version + +At this point we have created a partial instance, since we have not yet +assigned a type to the device. Even so, we can see that some state has +changed under /sys/vbus/devices. We now have an instance named + + 6a1aff24-5dc0-4aea-9c35-435daef90e55 + +and it has a single subordinate object: "interfaces". This object in +particular is provided by the infrastructure, though do note that a +deviceclass may also provide its own attributes/objects once it is created. + +We will go ahead and give this device a type to complete its construction. We +do this by setting the /config/vbus/devices/$devname/type attribute with a +valid deviceclass type: + +# echo foo > /config/vbus/devices/6a1aff24-5dc0-4aea-9c35-435daef90e55/type +bash: echo: write error: No such file or directory + +Oops! What happened? "foo" is not a valid deviceclass. We need to consult +the /sys/vbus/deviceclass area to find out what our options are: + +# tree /sys/vbus/deviceclass/ +/sys/vbus/deviceclass/ +`-- venet-tap + +Lets try again: + +# echo venet-tap > /config/vbus/devices/6a1aff24-5dc0-4aea-9c35-435daef90e55/type +# tree /sys/vbus/ +/sys/vbus/ +|-- deviceclass +| `-- venet-tap +|-- devices +| `-- 6a1aff24-5dc0-4aea-9c35-435daef90e55 +| |-- class -> ../../deviceclass/venet-tap +| |-- client_mac +| |-- enabled +| |-- host_mac +| |-- ifname +| `-- interfaces +|-- instances +| `-- beb4df8f-7483-4028-b3f7-767512e2a18c +| |-- devices +| `-- members +`-- version + +Ok, that looks better. And note that /sys/vbus/devices now has some more +subordinate objects. Most of those were registered when the venet-tap +deviceclass was given a chance to create an instance of itself. Those +attributes are a property of the venet-tap and therefore are out of scope +for this document. Please see the documentation that accompanies a particular +module for more details. + +Put the device on the bus +------------------------- + +The next administrative step is to associate our new device with our bus. +This is accomplished using a symbolic link from the bus instance to our device +instance. + +ln -s /config/vbus/devices/6a1aff24-5dc0-4aea-9c35-435daef90e55/ /config/vbus/instances/beb4df8f-7483-4028-b3f7-767512e2a18c/ +# tree /sys/vbus/ +/sys/vbus/ +|-- deviceclass +| `-- venet-tap +|-- devices +| `-- 6a1aff24-5dc0-4aea-9c35-435daef90e55 +| |-- class -> ../../deviceclass/venet-tap +| |-- client_mac +| |-- enabled +| |-- host_mac +| |-- ifname +| `-- interfaces +| `-- 0 -> ../../../instances/beb4df8f-7483-4028-b3f7-767512e2a18c/devices/0 +|-- instances +| `-- beb4df8f-7483-4028-b3f7-767512e2a18c +| |-- devices +| | `-- 0 +| | |-- device -> ../../../../devices/6a1aff24-5dc0-4aea-9c35-435daef90e55 +| | `-- type +| `-- members +`-- version + +We can now see that the device indicates that it has an interface registered +to a bus: + +/sys/vbus/devices/6a1aff24-5dc0-4aea-9c35-435daef90e55/interfaces/ +`-- 0 -> ../../../instances/beb4df8f-7483-4028-b3f7-767512e2a18c/devices/0 + +Likewise, we can see that the bus has a device listed (id = "0"): + +/sys/vbus/instances/beb4df8f-7483-4028-b3f7-767512e2a18c/devices/ +`-- 0 + |-- device -> ../../../../devices/6a1aff24-5dc0-4aea-9c35-435daef90e55 + `-- type + +At this point, our container is ready for use. However, it currently has 0 +members, so lets fix that + +Add some members +-------------------- + +Membership is controlled by an attribute: /proc/$pid/vbus. A pid can only be +a member of one (or zero) busses at a time. To establish membership, we set +the name of the bus, like so: + +# echo beb4df8f-7483-4028-b3f7-767512e2a18c > /proc/self/vbus +# tree /sys/vbus/ +/sys/vbus/ +|-- deviceclass +| `-- venet-tap +|-- devices +| `-- 6a1aff24-5dc0-4aea-9c35-435daef90e55 +| |-- class -> ../../deviceclass/venet-tap +| |-- client_mac +| |-- enabled +| |-- host_mac +| |-- ifname +| `-- interfaces +| `-- 0 -> ../../../instances/beb4df8f-7483-4028-b3f7-767512e2a18c/devices/0 +|-- instances +| `-- beb4df8f-7483-4028-b3f7-767512e2a18c +| |-- devices +| | `-- 0 +| | |-- device -> ../../../../devices/6a1aff24-5dc0-4aea-9c35-435daef90e55 +| | `-- type +| `-- members +| |-- 4382 +| `-- 4588 +`-- version + +Woah! Why are there two members? VBUS membership is inherited by forked +tasks. Therefore 4382 is the pid of our shell (which we set via /proc/self), +and 4588 is the pid of the forked/exec'ed "tree" process. This property can +be useful for having things like qemu set up the bus and then forking each +vcpu which will inherit access. + +At this point, we are ready to roll. Pid 4382 has access to a virtual-bus +namespace with one device, id=0. Its type is: + +# cat /sys/vbus/instances/beb4df8f-7483-4028-b3f7-767512e2a18c/devices/0/type +virtual-ethernet + +"virtual-ethernet"? Why is it not "venet-tap"? Device-classes are allowed to +register their interfaces under an id that is not required to be the same as +their deviceclass. This supports device polymorphism. For instance, +consider that an interface "virtual-ethernet" may provide basic 802.x packet +exchange. However, we could have various implementations of a device that +supports the 802.x interface, while having various implementations behind +them. + +For instance, "venet-tap" might act like a tuntap module, while +"venet-loopback" would loop packets back and "venet-switch" would form a +layer-2 domain among the participating guests. All three modules would +presumably support the same basic 802.x interface, yet all three have +completely different implementations. + +Drivers on this particular bus would see this instance id=0 as a type +"virtual-ethernet" even though the underlying implementation happens to be a +tap device. This means a single driver that supports the protocol advertised +by the "virtual-ethernet" type would be able to support the plethera of +available device types that we may wish to create. + +Teardown: +--------------- + +We can descontruct a vbus container doing pretty much the opposite of what we +did to create it. Echo "0" into /proc/self/vbus, rm the symlink between the +bus and device, and rmdir the bus and device objects. Once that is done, we +can even rmmod the venet-tap module. Note that the infrastructure will +maintain a module-ref while it is configured in a container, so be sure to +completely tear down the vbus/device before trying this. diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index bc2fbad..3fca247 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -1939,6 +1939,8 @@ source "drivers/pcmcia/Kconfig" source "drivers/pci/hotplug/Kconfig" +source "kernel/vbus/Kconfig" + endmenu diff --git a/fs/proc/base.c b/fs/proc/base.c index beaa0ce..03993fb 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -80,6 +80,7 @@ #include <linux/oom.h> #include <linux/elf.h> #include <linux/pid_namespace.h> +#include <linux/vbus.h> #include "internal.h" /* NOTE: @@ -1065,6 +1066,98 @@ static const struct file_operations proc_oom_adjust_operations = { .write = oom_adjust_write, }; +#ifdef CONFIG_VBUS + +static ssize_t vbus_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct task_struct *task = get_proc_task(file->f_path.dentry->d_inode); + struct vbus *vbus; + const char *name; + char buffer[256]; + size_t len; + + if (!task) + return -ESRCH; + + vbus = task_vbus_get(task); + + put_task_struct(task); + + name = vbus_name(vbus); + + len = snprintf(buffer, sizeof(buffer), "%s\n", name ? name : "<none>"); + + vbus_put(vbus); + + return simple_read_from_buffer(buf, count, ppos, buffer, len); +} + +static ssize_t vbus_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct task_struct *task; + struct vbus *vbus = NULL; + char buffer[256]; + int disable = 0; + + memset(buffer, 0, sizeof(buffer)); + if (count > sizeof(buffer) - 1) + count = sizeof(buffer) - 1; + if (copy_from_user(buffer, buf, count)) + return -EFAULT; + + if (buffer[count-1] == '\n') + buffer[count-1] = 0; + + task = get_proc_task(file->f_path.dentry->d_inode); + if (!task) + return -ESRCH; + + if (!capable(CAP_SYS_ADMIN)) { + put_task_struct(task); + return -EACCES; + } + + if (strcmp(buffer, "0") == 0) + disable = 1; + else + vbus = vbus_find(buffer); + + if (disable || vbus) + task_vbus_disassociate(task); + + if (vbus) { + int ret = vbus_associate(vbus, task); + + if (ret < 0) + printk(KERN_ERR \ + "vbus: could not associate %s/%d with bus %s", + task->comm, task->pid, vbus_name(vbus)); + else + rcu_assign_pointer(task->vbus, vbus); + + vbus_put(vbus); /* Counter the vbus_find() */ + } else if (!disable) { + put_task_struct(task); + return -ENOENT; + } + + put_task_struct(task); + + if (count == sizeof(buffer)-1) + return -EIO; + + return count; +} + +static const struct file_operations proc_vbus_operations = { + .read = vbus_read, + .write = vbus_write, +}; + +#endif /* CONFIG_VBUS */ + #ifdef CONFIG_AUDITSYSCALL #define TMPBUFLEN 21 static ssize_t proc_loginuid_read(struct file * file, char __user * buf, @@ -2556,6 +2649,9 @@ static const struct pid_entry tgid_base_stuff[] = { #ifdef CONFIG_TASK_IO_ACCOUNTING INF("io", S_IRUGO, proc_tgid_io_accounting), #endif +#ifdef CONFIG_VBUS + REG("vbus", S_IRUGO|S_IWUSR, proc_vbus_operations), +#endif }; static int proc_tgid_base_readdir(struct file * filp, diff --git a/include/linux/sched.h b/include/linux/sched.h index 011db2f..cd2f9b1 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -97,6 +97,7 @@ struct futex_pi_state; struct robust_list_head; struct bio; struct bts_tracer; +struct vbus; /* * List of flags we want to share for kernel threads, @@ -1329,6 +1330,9 @@ struct task_struct { unsigned int lockdep_recursion; struct held_lock held_locks[MAX_LOCK_DEPTH]; #endif +#ifdef CONFIG_VBUS + struct vbus *vbus; +#endif /* journalling filesystem info */ void *journal_info; diff --git a/include/linux/vbus.h b/include/linux/vbus.h new file mode 100644 index 0000000..5f0566c --- /dev/null +++ b/include/linux/vbus.h @@ -0,0 +1,147 @@ +/* + * Copyright 2009 Novell. All Rights Reserved. + * + * Virtual-Bus + * + * Author: + * Gregory Haskins <ghaskins@xxxxxxxxxx> + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _LINUX_VBUS_H +#define _LINUX_VBUS_H + +#ifdef CONFIG_VBUS + +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/rcupdate.h> +#include <linux/vbus_device.h> + +struct vbus; +struct task_struct; + +/** + * vbus_associate() - associate a task with a vbus + * @vbus: The bus context to associate with + * @p: The task to associate + * + * This function adds a task as a member of a vbus. Tasks must be members + * of a bus before they are allowed to use its resources. Tasks may only + * associate with a single bus at a time. + * + * Note: children inherit any association present at fork(). + * + * Returns: success = 0, <0 = ERRNO + * + **/ +int vbus_associate(struct vbus *vbus, struct task_struct *p); + +/** + * vbus_disassociate() - disassociate a task with a vbus + * @vbus: The bus context to disassociate with + * @p: The task to disassociate + * + * This function removes a task as a member of a vbus. + * + * Returns: success = 0, <0 = ERRNO + * + **/ +int vbus_disassociate(struct vbus *vbus, struct task_struct *p); + +struct vbus *vbus_get(struct vbus *); +void vbus_put(struct vbus *); + +/** + * vbus_name() - returns the name of a bus + * @vbus: The bus context + * + * Returns: (char *) name of bus + * + **/ +const char *vbus_name(struct vbus *vbus); + +/** + * vbus_find() - retreives a vbus pointer from its name + * @name: The name of the bus to find + * + * Returns: NULL = failure, non-null = (vbus *)bus-pointer + * + **/ +struct vbus *vbus_find(const char *name); + +/** + * task_vbus_get() - retreives an associated vbus pointer from a task + * @p: The task context + * + * Safely retreives a pointer to an associated (if any) vbus from a task + * + * Returns: NULL = no association, non-null = (vbus *)bus-pointer + * + **/ +static inline struct vbus *task_vbus_get(struct task_struct *p) +{ + struct vbus *vbus; + + rcu_read_lock(); + vbus = rcu_dereference(p->vbus); + if (vbus) + vbus_get(vbus); + rcu_read_unlock(); + + return vbus; +} + +/** + * fork_vbus() - Helper function to handle associated task forking + * @p: The task context + * + **/ +static inline void fork_vbus(struct task_struct *p) +{ + struct vbus *vbus = task_vbus_get(p); + + if (vbus) { + BUG_ON(vbus_associate(vbus, p) < 0); + vbus_put(vbus); + } +} + +/** + * task_vbus_disassociate() - Helper function to handle disassociating tasks + * @p: The task context + * + **/ +static inline void task_vbus_disassociate(struct task_struct *p) +{ + struct vbus *vbus = task_vbus_get(p); + + if (vbus) { + rcu_assign_pointer(p->vbus, NULL); + synchronize_rcu(); + + vbus_disassociate(vbus, p); + vbus_put(vbus); + } +} + +#else /* CONFIG_VBUS */ + +#define fork_vbus(p) do { } while (0) +#define task_vbus_disassociate(p) do { } while (0) + +#endif /* CONFIG_VBUS */ + +#endif /* _LINUX_VBUS_H */ diff --git a/include/linux/vbus_device.h b/include/linux/vbus_device.h new file mode 100644 index 0000000..705d92e --- /dev/null +++ b/include/linux/vbus_device.h @@ -0,0 +1,416 @@ +/* + * VBUS device models + * + * Copyright 2009 Novell. All Rights Reserved. + * + * Author: + * Gregory Haskins <ghaskins@xxxxxxxxxx> + * + * This file deals primarily with the definitions for interfacing a virtual + * device model to a virtual bus. In a nutshell, a devclass begets a device, + * which begets a device_interface, which begets a connection. + * + * devclass + * ------- + * + * To develop a vbus device, it all starts with a devclass. You must register + * a devclass using vbus_devclass_register(). Each registered devclass is + * enumerated under /sys/vbus/deviceclass. + * + * In of itself, a devclass doesnt do much. It is just an object factory for + * a device whose lifetime is managed by userspace. When userspace decides + * it would like to create an instance of a particular devclass, the + * devclass::create() callback is invoked (registered as part of the ops + * structure during vbus_devclass_register()). How and when userspace decides + * to do this is beyond the scope of this document. Please see: + * + * Documentation/vbus.txt + * + * for more details. + * + * device + * ------- + * + * A vbus device is created by a particular devclass during the invokation + * of its devclass::create() callback. A device is initially created without + * any association with a bus. One or more buses may attempt to connect to + * a device (controlled, again, by userspace). When this occurs, a + * device::bus_connect() callback is invoked. + * + * This bus_connect() callback gives the device a chance to decide if it will + * accept the connection, and if so, to register its interfaces. Most devices + * will likely only allow a connection to one bus. Therefore, they may return + * -EBUSY another bus is already connected. + * + * If the device accepts the connection, it should register one of more + * interfaces with the bus using vbus_device_interface_register(). Most + * devices will only support one interface, and therefore will only invoke + * this method once. However, some more elaborate devices may have multiple + * functions, or abstracted topologies. Therefore they may opt at their own + * discretion to register more than one interface. The interfaces do not need + * to be uniform in type. + * + * device_interface + * ------------------- + * + * The purpose of an interface is two fold: 1) advertise a particular ABI + * for communcation to a driver, 2) handle the initial connection of a driver. + * + * As such, a device_interface has a string "type" (which is akin to the + * abi type that this interface supports, like a PCI-ID). It also sports + * an interface::open() method. + * + * The interface::open callback is invoked whenever a driver attempts to + * connect to this device. The device implements its own policy regarding + * whether it accepts multiple connections or not. Most devices will likely + * only accept one connection at a time, and therefore will return -EBUSY if + * subsequent attempts are made. + * + * However, if successful, the interface::open() should return a + * vbus_connection object + * + * connections + * ----------- + * + * A connection represents an interface that is succesfully opened. It will + * remain in an active state as long as the client retains the connection. + * The connection::release() method is invoked if the client should die, + * restart, or explicitly close the connection. The device-model should use + * this release() callback as the indication to clean up any resources + * associated with a particular connection such as allocated queues, etc. + * + * --- + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _LINUX_VBUS_DEVICE_H +#define _LINUX_VBUS_DEVICE_H + +#include <linux/module.h> +#include <linux/configfs.h> +#include <linux/rbtree.h> +#include <linux/shm_signal.h> +#include <linux/vbus.h> +#include <asm/atomic.h> + +struct vbus_device_interface; +struct vbus_connection; +struct vbus_device; +struct vbus_devclass; +struct vbus_memctx; + +/* + * ---------------------- + * devclass + * ---------------------- + */ +struct vbus_devclass_ops { + int (*create)(struct vbus_devclass *dc, + struct vbus_device **dev); + void (*release)(struct vbus_devclass *dc); +}; + +struct vbus_devclass { + const char *name; + struct vbus_devclass_ops *ops; + struct rb_node node; + struct kobject kobj; + struct module *owner; +}; + +/** + * vbus_devclass_register() - register a devclass with the system + * @devclass: The devclass context to register + * + * Establishes a new device-class for consumption. Registered device-classes + * are enumerated under /sys/vbus/deviceclass. For more details, please see + * Documentation/vbus* + * + * Returns: success = 0, <0 = ERRNO + * + **/ +int vbus_devclass_register(struct vbus_devclass *devclass); + +/** + * vbus_devclass_unregister() - unregister a devclass with the system + * @devclass: The devclass context to unregister + * + * Removes a devclass from the system + * + * Returns: success = 0, <0 = ERRNO + * + **/ +int vbus_devclass_unregister(struct vbus_devclass *devclass); + +/** + * vbus_devclass_get() - acquire a devclass context reference + * @devclass: devclass context + * + **/ +static inline struct vbus_devclass * +vbus_devclass_get(struct vbus_devclass *devclass) +{ + if (!try_module_get(devclass->owner)) + return NULL; + + kobject_get(&devclass->kobj); + return devclass; +} + +/** + * vbus_devclass_put() - release a devclass context reference + * @devclass: devclass context + * + **/ +static inline void +vbus_devclass_put(struct vbus_devclass *devclass) +{ + kobject_put(&devclass->kobj); + module_put(devclass->owner); +} + +/* + * ---------------------- + * device + * ---------------------- + */ +struct vbus_device_attribute { + struct attribute attr; + ssize_t (*show)(struct vbus_device *dev, + struct vbus_device_attribute *attr, + char *buf); + ssize_t (*store)(struct vbus_device *dev, + struct vbus_device_attribute *attr, + const char *buf, size_t count); +}; + +struct vbus_device_ops { + int (*bus_connect)(struct vbus_device *dev, struct vbus *vbus); + int (*bus_disconnect)(struct vbus_device *dev, struct vbus *vbus); + void (*release)(struct vbus_device *dev); +}; + +struct vbus_device { + const char *type; + struct vbus_device_ops *ops; + struct attribute_group *attrs; + struct kobject *kobj; +}; + +/* + * ---------------------- + * device_interface + * ---------------------- + */ +struct vbus_device_interface_ops { + int (*open)(struct vbus_device_interface *intf, + struct vbus_memctx *ctx, + int version, + struct vbus_connection **conn); + void (*release)(struct vbus_device_interface *intf); +}; + +struct vbus_device_interface { + const char *name; + const char *type; + struct vbus_device_interface_ops *ops; + unsigned long id; + struct vbus_device *dev; + struct vbus *vbus; + struct rb_node node; + struct kobject kobj; +}; + +/** + * vbus_device_interface_register() - register an interface with a bus + * @dev: The device context of the caller + * @vbus: The bus context to register with + * @intf: The interface context to register + * + * This function is invoked (usually in the context of a device::bus_connect() + * callback) to register a interface on a bus. We make this an explicit + * operation instead of implicit on the bus_connect() to facilitate devices + * that may present multiple interfaces to a bus. In those cases, a device + * may invoke this function multiple times (one per supported interface). + * + * Returns: success = 0, <0 = ERRNO + * + **/ +int vbus_device_interface_register(struct vbus_device *dev, + struct vbus *vbus, + struct vbus_device_interface *intf); + +/** + * vbus_device_interface_unregister() - unregister an interface with a bus + * @intf: The interface context to unregister + * + * This function is the converse of interface_register. It is typically + * invoked in the context of a device::bus_disconnect(). + * + * Returns: success = 0, <0 = ERRNO + * + **/ +int vbus_device_interface_unregister(struct vbus_device_interface *intf); + +/* + * ---------------------- + * memory context + * ---------------------- + */ +struct vbus_memctx_ops { + unsigned long (*copy_to)(struct vbus_memctx *ctx, + void *dst, + const void *src, + unsigned long len); + unsigned long (*copy_from)(struct vbus_memctx *ctx, + void *dst, + const void *src, + unsigned long len); + void (*release)(struct vbus_memctx *ctx); +}; + +struct vbus_memctx { + atomic_t refs; + struct vbus_memctx_ops *ops; +}; + +static inline void +vbus_memctx_init(struct vbus_memctx *ctx, struct vbus_memctx_ops *ops) +{ + memset(ctx, 0, sizeof(*ctx)); + atomic_set(&ctx->refs, 1); + ctx->ops = ops; +} + +#define VBUS_MEMCTX_INIT(_ops) { \ + .refs = ATOMIC_INIT(1), \ + .ops = _ops, \ +} + +static inline void +vbus_memctx_get(struct vbus_memctx *ctx) +{ + atomic_inc(&ctx->refs); +} + +static inline void +vbus_memctx_put(struct vbus_memctx *ctx) +{ + if (atomic_dec_and_test(&ctx->refs)) + ctx->ops->release(ctx); +} + +/* + * ---------------------- + * memory context + * ---------------------- + */ +struct vbus_shm; + +struct vbus_shm_ops { + void (*release)(struct vbus_shm *shm); +}; + +struct vbus_shm { + atomic_t refs; + struct vbus_shm_ops *ops; + void *ptr; + size_t len; +}; + +static inline void +vbus_shm_init(struct vbus_shm *shm, struct vbus_shm_ops *ops, + void *ptr, size_t len) +{ + memset(shm, 0, sizeof(*shm)); + atomic_set(&shm->refs, 1); + shm->ops = ops; + shm->ptr = ptr; + shm->len = len; +} + +static inline void +vbus_shm_get(struct vbus_shm *shm) +{ + atomic_inc(&shm->refs); +} + +static inline void +vbus_shm_put(struct vbus_shm *shm) +{ + if (atomic_dec_and_test(&shm->refs)) + shm->ops->release(shm); +} + +/* + * ---------------------- + * connection + * ---------------------- + */ +struct vbus_connection_ops { + int (*call)(struct vbus_connection *conn, + unsigned long func, + void *data, + unsigned long len, + unsigned long flags); + int (*shm)(struct vbus_connection *conn, + unsigned long id, + struct vbus_shm *shm, + struct shm_signal *signal, + unsigned long flags); + void (*release)(struct vbus_connection *conn); +}; + +struct vbus_connection { + atomic_t refs; + struct vbus_connection_ops *ops; +}; + +/** + * vbus_connection_init() - initialize a vbus_connection + * @conn: connection context + * @ops: ops structure to assign to context + * + **/ +static inline void vbus_connection_init(struct vbus_connection *conn, + struct vbus_connection_ops *ops) +{ + memset(conn, 0, sizeof(*conn)); + atomic_set(&conn->refs, 1); + conn->ops = ops; +} + +/** + * vbus_connection_get() - acquire a connection context reference + * @conn: connection context + * + **/ +static inline void vbus_connection_get(struct vbus_connection *conn) +{ + atomic_inc(&conn->refs); +} + +/** + * vbus_connection_put() - release a connection context reference + * @conn: connection context + * + **/ +static inline void vbus_connection_put(struct vbus_connection *conn) +{ + if (atomic_dec_and_test(&conn->refs)) + conn->ops->release(conn); +} + +#endif /* _LINUX_VBUS_DEVICE_H */ diff --git a/kernel/Makefile b/kernel/Makefile index e4791b3..99a98a7 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -93,6 +93,7 @@ obj-$(CONFIG_HAVE_GENERIC_DMA_COHERENT) += dma-coherent.o obj-$(CONFIG_FUNCTION_TRACER) += trace/ obj-$(CONFIG_TRACING) += trace/ obj-$(CONFIG_SMP) += sched_cpupri.o +obj-$(CONFIG_VBUS) += vbus/ ifneq ($(CONFIG_SCHED_OMIT_FRAME_POINTER),y) # According to Alan Modra <alan@xxxxxxxxxxxxxxxx>, the -fno-omit-frame-pointer is diff --git a/kernel/exit.c b/kernel/exit.c index efd30cc..8736de6 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -48,6 +48,7 @@ #include <linux/tracehook.h> #include <linux/init_task.h> #include <trace/sched.h> +#include <linux/vbus.h> #include <asm/uaccess.h> #include <asm/unistd.h> @@ -1081,6 +1082,7 @@ NORET_TYPE void do_exit(long code) check_stack_usage(); exit_thread(); cgroup_exit(tsk, 1); + task_vbus_disassociate(tsk); if (group_dead && tsk->signal->leader) disassociate_ctty(1); diff --git a/kernel/fork.c b/kernel/fork.c index 4854c2c..5536053 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -61,6 +61,7 @@ #include <linux/proc_fs.h> #include <linux/blkdev.h> #include <trace/sched.h> +#include <linux/vbus.h> #include <asm/pgtable.h> #include <asm/pgalloc.h> @@ -1274,6 +1275,7 @@ static struct task_struct *copy_process(unsigned long clone_flags, write_unlock_irq(&tasklist_lock); proc_fork_connector(p); cgroup_post_fork(p); + fork_vbus(p); return p; bad_fork_free_graph: diff --git a/kernel/vbus/Kconfig b/kernel/vbus/Kconfig new file mode 100644 index 0000000..f2b92f5 --- /dev/null +++ b/kernel/vbus/Kconfig @@ -0,0 +1,14 @@ +# +# Virtual-Bus (VBus) configuration +# + +config VBUS + bool "Virtual Bus" + select CONFIGFS_FS + select SHM_SIGNAL + default n + help + Provides a mechansism for declaring virtual-bus objects and binding + various tasks and devices which reside on the bus. + + If unsure, say N diff --git a/kernel/vbus/Makefile b/kernel/vbus/Makefile new file mode 100644 index 0000000..367f65b --- /dev/null +++ b/kernel/vbus/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_VBUS) += core.o devclass.o config.o attribute.o map.o diff --git a/kernel/vbus/attribute.c b/kernel/vbus/attribute.c new file mode 100644 index 0000000..3928228 --- /dev/null +++ b/kernel/vbus/attribute.c @@ -0,0 +1,52 @@ +#include <linux/vbus.h> +#include <linux/uaccess.h> +#include <linux/kobject.h> +#include <linux/kallsyms.h> + +#include "vbus.h" + +static struct vbus_device_attribute *to_vattr(struct attribute *attr) +{ + return container_of(attr, struct vbus_device_attribute, attr); +} + +static struct vbus_devshell *to_devshell(struct kobject *kobj) +{ + return container_of(kobj, struct vbus_devshell, kobj); +} + +static ssize_t _dev_attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct vbus_devshell *ds = to_devshell(kobj); + struct vbus_device_attribute *vattr = to_vattr(attr); + ssize_t ret = -EIO; + + if (vattr->show) + ret = vattr->show(ds->dev, vattr, buf); + + if (ret >= (ssize_t)PAGE_SIZE) { + print_symbol("vbus_attr_show: %s returned bad count\n", + (unsigned long)vattr->show); + } + + return ret; +} + +static ssize_t _dev_attr_store(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + struct vbus_devshell *ds = to_devshell(kobj); + struct vbus_device_attribute *vattr = to_vattr(attr); + ssize_t ret = -EIO; + + if (vattr->store) + ret = vattr->store(ds->dev, vattr, buf, count); + + return ret; +} + +struct sysfs_ops vbus_dev_attr_ops = { + .show = _dev_attr_show, + .store = _dev_attr_store, +}; diff --git a/kernel/vbus/config.c b/kernel/vbus/config.c new file mode 100644 index 0000000..a40dbf1 --- /dev/null +++ b/kernel/vbus/config.c @@ -0,0 +1,275 @@ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include <linux/vbus.h> +#include <linux/configfs.h> + +#include "vbus.h" + +static struct config_item_type perms_type = { + .ct_owner = THIS_MODULE, +}; + +static struct vbus *to_vbus(struct config_group *group) +{ + return group ? container_of(group, struct vbus, ci.group) : NULL; +} + +static struct vbus *item_to_vbus(struct config_item *item) +{ + return to_vbus(to_config_group(item)); +} + +static struct vbus_devshell *to_devshell(struct config_group *group) +{ + return group ? container_of(group, struct vbus_devshell, ci_group) + : NULL; +} + +static struct vbus_devshell *to_vbus_devshell(struct config_item *item) +{ + return to_devshell(to_config_group(item)); +} + +static int +device_bus_connect(struct config_item *src, struct config_item *target) +{ + struct vbus *vbus = item_to_vbus(src); + struct vbus_devshell *ds; + + /* We only allow connections to devices */ + if (target->ci_parent != &vbus_root.devices.ci_group.cg_item) + return -EINVAL; + + ds = to_vbus_devshell(target); + BUG_ON(!ds); + + if (!ds->dev) + return -EINVAL; + + return ds->dev->ops->bus_connect(ds->dev, vbus); +} + +static int +device_bus_disconnect(struct config_item *src, struct config_item *target) +{ + struct vbus *vbus = item_to_vbus(src); + struct vbus_devshell *ds; + + ds = to_vbus_devshell(target); + BUG_ON(!ds); + + if (!ds->dev) + return -EINVAL; + + return ds->dev->ops->bus_disconnect(ds->dev, vbus); +} + +struct configfs_item_operations bus_ops = { + .allow_link = device_bus_connect, + .drop_link = device_bus_disconnect, +}; + +static struct config_item_type bus_type = { + .ct_item_ops = &bus_ops, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *bus_create(struct config_group *group, + const char *name) +{ + struct vbus *bus = NULL; + int ret; + + ret = vbus_create(name, &bus); + if (ret < 0) + return ERR_PTR(ret); + + config_group_init_type_name(&bus->ci.group, name, &bus_type); + bus->ci.group.default_groups = bus->ci.defgroups; + bus->ci.group.default_groups[0] = &bus->ci.perms; + bus->ci.group.default_groups[1] = NULL; + + config_group_init_type_name(&bus->ci.perms, "perms", &perms_type); + + return &bus->ci.group; +} + +static void bus_destroy(struct config_group *group, struct config_item *item) +{ + struct vbus *vbus = item_to_vbus(item); + + vbus_put(vbus); +} + +static struct configfs_group_operations buses_ops = { + .make_group = bus_create, + .drop_item = bus_destroy, +}; + +static struct config_item_type buses_type = { + .ct_group_ops = &buses_ops, + .ct_owner = THIS_MODULE, +}; + +CONFIGFS_ATTR_STRUCT(vbus_devshell); +#define DEVSHELL_ATTR(_name, _mode, _show, _store) \ +struct vbus_devshell_attribute vbus_devshell_attr_##_name = \ + __CONFIGFS_ATTR(_name, _mode, _show, _store) + +static ssize_t devshell_type_read(struct vbus_devshell *ds, char *page) +{ + if (ds->dev) + return sprintf(page, "%s\n", ds->dev->type); + else + return sprintf(page, "\n"); +} + +static ssize_t devshell_type_write(struct vbus_devshell *ds, const char *page, + size_t count) +{ + struct vbus_devclass *dc; + struct vbus_device *dev; + char name[256]; + int ret; + + /* + * The device-type can only be set once, and then it is permenent. + * The admin should delete the device-shell if they want to create + * a new type + */ + if (ds->dev) + return -EINVAL; + + if (count > sizeof(name)) + return -EINVAL; + + strcpy(name, page); + if (name[count-1] == '\n') + name[count-1] = 0; + + dc = vbus_devclass_find(name); + if (!dc) + return -ENOENT; + + ret = dc->ops->create(dc, &dev); + if (ret < 0) { + vbus_devclass_put(dc); + return ret; + } + + ds->dev = dev; + ds->dc = dc; + dev->kobj = &ds->kobj; + + ret = vbus_devshell_type_set(ds); + if (ret < 0) { + vbus_devclass_put(dc); + return ret; + } + + return count; +} + +DEVSHELL_ATTR(type, S_IRUGO | S_IWUSR, devshell_type_read, + devshell_type_write); + +static struct configfs_attribute *devshell_attrs[] = { + &vbus_devshell_attr_type.attr, + NULL, +}; + +CONFIGFS_ATTR_OPS(vbus_devshell); +static struct configfs_item_operations devshell_item_ops = { + .show_attribute = vbus_devshell_attr_show, + .store_attribute = vbus_devshell_attr_store, +}; + +static struct config_item_type devshell_type = { + .ct_item_ops = &devshell_item_ops, + .ct_attrs = devshell_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *devshell_create(struct config_group *group, + const char *name) +{ + struct vbus_devshell *ds = NULL; + int ret; + + ret = vbus_devshell_create(name, &ds); + if (ret < 0) + return ERR_PTR(ret); + + config_group_init_type_name(&ds->ci_group, name, &devshell_type); + + return &ds->ci_group; +} + +static void devshell_release(struct config_group *group, + struct config_item *item) +{ + struct vbus_devshell *ds = to_vbus_devshell(item); + + kobject_put(&ds->kobj); + + if (ds->dc) + vbus_devclass_put(ds->dc); +} + +static struct configfs_group_operations devices_ops = { + .make_group = devshell_create, + .drop_item = devshell_release, +}; + +static struct config_item_type devices_type = { + .ct_group_ops = &devices_ops, + .ct_owner = THIS_MODULE, +}; + +static struct config_item_type root_type = { + .ct_owner = THIS_MODULE, +}; + +int __init vbus_config_init(void) +{ + int ret; + struct configfs_subsystem *subsys = &vbus_root.ci.subsys; + + config_group_init_type_name(&subsys->su_group, "vbus", &root_type); + mutex_init(&subsys->su_mutex); + + subsys->su_group.default_groups = vbus_root.ci.defgroups; + subsys->su_group.default_groups[0] = &vbus_root.buses.ci_group; + subsys->su_group.default_groups[1] = &vbus_root.devices.ci_group; + subsys->su_group.default_groups[2] = NULL; + + config_group_init_type_name(&vbus_root.buses.ci_group, + "instances", &buses_type); + + config_group_init_type_name(&vbus_root.devices.ci_group, + "devices", &devices_type); + + ret = configfs_register_subsystem(subsys); + if (ret) { + printk(KERN_ERR "Error %d while registering subsystem %s\n", + ret, + subsys->su_group.cg_item.ci_namebuf); + goto out_unregister; + } + + return 0; + +out_unregister: + configfs_unregister_subsystem(subsys); + + return ret; +} + +void __exit vbus_config_exit(void) +{ + configfs_unregister_subsystem(&vbus_root.ci.subsys); +} + + diff --git a/kernel/vbus/core.c b/kernel/vbus/core.c new file mode 100644 index 0000000..033999f --- /dev/null +++ b/kernel/vbus/core.c @@ -0,0 +1,567 @@ +/* + * Copyright 2009 Novell. All Rights Reserved. + * + * Author: + * Gregory Haskins <ghaskins@xxxxxxxxxx> + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/vbus.h> +#include <linux/uaccess.h> + +#include "vbus.h" + +static struct vbus_device_interface *kobj_to_intf(struct kobject *kobj) +{ + return container_of(kobj, struct vbus_device_interface, kobj); +} + +static struct vbus_devshell *to_devshell(struct kobject *kobj) +{ + return container_of(kobj, struct vbus_devshell, kobj); +} + +static void interface_release(struct kobject *kobj) +{ + struct vbus_device_interface *intf = kobj_to_intf(kobj); + + if (intf->ops->release) + intf->ops->release(intf); +} + +static struct kobj_type interface_ktype = { + .release = interface_release, + .sysfs_ops = &kobj_sysfs_ops, +}; + +static ssize_t +type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct vbus_device_interface *intf = kobj_to_intf(kobj); + + return snprintf(buf, PAGE_SIZE, "%s\n", intf->type); +} + +static struct kobj_attribute devattr_type = + __ATTR_RO(type); + +static struct attribute *attrs[] = { + &devattr_type.attr, + NULL, +}; + +static struct attribute_group attr_group = { + .attrs = attrs, +}; + +/* + * Assumes dev->bus->lock is held + */ +static void _interface_unregister(struct vbus_device_interface *intf) +{ + struct vbus *vbus = intf->vbus; + struct vbus_devshell *ds = to_devshell(intf->dev->kobj); + + map_del(&vbus->devices.map, &intf->node); + sysfs_remove_link(&ds->intfs, intf->name); + sysfs_remove_link(&intf->kobj, "device"); + sysfs_remove_group(&intf->kobj, &attr_group); +} + +int vbus_device_interface_register(struct vbus_device *dev, + struct vbus *vbus, + struct vbus_device_interface *intf) +{ + int ret; + struct vbus_devshell *ds = to_devshell(dev->kobj); + + mutex_lock(&vbus->lock); + + if (vbus->next_id == -1) { + mutex_unlock(&vbus->lock); + return -ENOSPC; + } + + intf->id = vbus->next_id++; + intf->dev = dev; + intf->vbus = vbus; + + ret = map_add(&vbus->devices.map, &intf->node); + if (ret < 0) { + mutex_unlock(&vbus->lock); + return ret; + } + + kobject_init_and_add(&intf->kobj, &interface_ktype, + &vbus->devices.kobj, "%ld", intf->id); + + /* Create the basic attribute files associated with this kobject */ + ret = sysfs_create_group(&intf->kobj, &attr_group); + if (ret) + goto error; + + /* Create cross-referencing links between the device and bus */ + ret = sysfs_create_link(&intf->kobj, dev->kobj, "device"); + if (ret) + goto error; + + ret = sysfs_create_link(&ds->intfs, &intf->kobj, intf->name); + if (ret) + goto error; + + mutex_unlock(&vbus->lock); + + return 0; + +error: + _interface_unregister(intf); + mutex_unlock(&vbus->lock); + + kobject_put(&intf->kobj); + + return ret; +} +EXPORT_SYMBOL_GPL(vbus_device_interface_register); + +int vbus_device_interface_unregister(struct vbus_device_interface *intf) +{ + struct vbus *vbus = intf->vbus; + + mutex_lock(&vbus->lock); + _interface_unregister(intf); + mutex_unlock(&vbus->lock); + + kobject_put(&intf->kobj); + + return 0; +} +EXPORT_SYMBOL_GPL(vbus_device_interface_unregister); + +static struct vbus_device_interface *node_to_intf(struct rb_node *node) +{ + return node ? container_of(node, struct vbus_device_interface, node) + : NULL; +} + +static int interface_item_compare(struct rb_node *lhs, struct rb_node *rhs) +{ + struct vbus_device_interface *lintf = node_to_intf(lhs); + struct vbus_device_interface *rintf = node_to_intf(rhs); + + return lintf->id - rintf->id; +} + +static int interface_key_compare(const void *key, struct rb_node *node) +{ + struct vbus_device_interface *intf = node_to_intf(node); + unsigned long id = *(unsigned long *)key; + + return id - intf->id; +} + +static struct map_ops interface_map_ops = { + .key_compare = &interface_key_compare, + .item_compare = &interface_item_compare, +}; + +/* + *----------------- + * member + *----------------- + */ + +static struct vbus_member *node_to_member(struct rb_node *node) +{ + return node ? container_of(node, struct vbus_member, node) : NULL; +} + +static struct vbus_member *kobj_to_member(struct kobject *kobj) +{ + return kobj ? container_of(kobj, struct vbus_member, kobj) : NULL; +} + +static int member_item_compare(struct rb_node *lhs, struct rb_node *rhs) +{ + struct vbus_member *lmember = node_to_member(lhs); + struct vbus_member *rmember = node_to_member(rhs); + + return lmember->tsk->pid - rmember->tsk->pid; +} + +static int member_key_compare(const void *key, struct rb_node *node) +{ + struct vbus_member *member = node_to_member(node); + pid_t pid = *(pid_t *)key; + + return pid - member->tsk->pid; +} + +static struct map_ops member_map_ops = { + .key_compare = &member_key_compare, + .item_compare = &member_item_compare, +}; + +static void member_release(struct kobject *kobj) +{ + struct vbus_member *member = kobj_to_member(kobj); + + vbus_put(member->vbus); + put_task_struct(member->tsk); + + kfree(member); +} + +static struct kobj_type member_ktype = { + .release = member_release, +}; + +int vbus_associate(struct vbus *vbus, struct task_struct *tsk) +{ + struct vbus_member *member; + int ret; + + member = kzalloc(sizeof(struct vbus_member), GFP_KERNEL); + if (!member) + return -ENOMEM; + + mutex_lock(&vbus->lock); + + get_task_struct(tsk); + vbus_get(vbus); + + member->vbus = vbus; + member->tsk = tsk; + + ret = kobject_init_and_add(&member->kobj, &member_ktype, + &vbus->members.kobj, + "%d", tsk->pid); + if (ret < 0) + goto error; + + ret = map_add(&vbus->members.map, &member->node); + if (ret < 0) + goto error; + +out: + mutex_unlock(&vbus->lock); + return 0; + +error: + kobject_put(&member->kobj); + goto out; +} + +int vbus_disassociate(struct vbus *vbus, struct task_struct *tsk) +{ + struct vbus_member *member; + + mutex_lock(&vbus->lock); + + member = node_to_member(map_find(&vbus->members.map, &tsk->pid)); + BUG_ON(!member); + + map_del(&vbus->members.map, &member->node); + + mutex_unlock(&vbus->lock); + + kobject_put(&member->kobj); + + return 0; +} + +/* + *----------------- + * vbus_subdir + *----------------- + */ + +static void vbus_subdir_init(struct vbus_subdir *subdir, + const char *name, + struct kobject *parent, + struct kobj_type *type, + struct map_ops *map_ops) +{ + int ret; + + map_init(&subdir->map, map_ops); + + ret = kobject_init_and_add(&subdir->kobj, type, parent, name); + BUG_ON(ret < 0); +} + +/* + *----------------- + * vbus + *----------------- + */ + +static void vbus_destroy(struct kobject *kobj) +{ + struct vbus *vbus = container_of(kobj, struct vbus, kobj); + + kfree(vbus); +} + +static struct kobj_type vbus_ktype = { + .release = vbus_destroy, +}; + +static struct kobj_type null_ktype = { +}; + +int vbus_create(const char *name, struct vbus **bus) +{ + struct vbus *_bus = NULL; + int ret; + + _bus = kzalloc(sizeof(struct vbus), GFP_KERNEL); + if (!_bus) + return -ENOMEM; + + atomic_set(&_bus->refs, 1); + mutex_init(&_bus->lock); + + kobject_init_and_add(&_bus->kobj, &vbus_ktype, + vbus_root.buses.kobj, name); + + vbus_subdir_init(&_bus->devices, "devices", &_bus->kobj, + &null_ktype, &interface_map_ops); + vbus_subdir_init(&_bus->members, "members", &_bus->kobj, + &null_ktype, &member_map_ops); + + _bus->next_id = 0; + + mutex_lock(&vbus_root.lock); + + ret = map_add(&vbus_root.buses.map, &_bus->node); + BUG_ON(ret < 0); + + mutex_unlock(&vbus_root.lock); + + *bus = _bus; + + return 0; +} + +static void devshell_release(struct kobject *kobj) +{ + struct vbus_devshell *ds = container_of(kobj, + struct vbus_devshell, kobj); + + if (ds->dev) { + if (ds->dev->attrs) + sysfs_remove_group(&ds->kobj, ds->dev->attrs); + + if (ds->dev->ops->release) + ds->dev->ops->release(ds->dev); + } + + if (ds->dc) + sysfs_remove_link(&ds->kobj, "class"); + + kobject_put(&ds->intfs); + kfree(ds); +} + +static struct kobj_type devshell_ktype = { + .release = devshell_release, + .sysfs_ops = &vbus_dev_attr_ops, +}; + +static void _interfaces_init(struct vbus_devshell *ds) +{ + kobject_init_and_add(&ds->intfs, &null_ktype, &ds->kobj, "interfaces"); +} + +int vbus_devshell_create(const char *name, struct vbus_devshell **ds) +{ + struct vbus_devshell *_ds = NULL; + + _ds = kzalloc(sizeof(*_ds), GFP_KERNEL); + if (!_ds) + return -ENOMEM; + + kobject_init_and_add(&_ds->kobj, &devshell_ktype, + vbus_root.devices.kobj, name); + + _interfaces_init(_ds); + + *ds = _ds; + + return 0; +} + +int vbus_devshell_type_set(struct vbus_devshell *ds) +{ + int ret; + + if (!ds->dev) + return -EINVAL; + + if (!ds->dev->attrs) + return 0; + + ret = sysfs_create_link(&ds->kobj, &ds->dc->kobj, "class"); + if (ret < 0) + return ret; + + return sysfs_create_group(&ds->kobj, ds->dev->attrs); +} + +struct vbus *vbus_get(struct vbus *vbus) +{ + if (vbus) + atomic_inc(&vbus->refs); + + return vbus; +} +EXPORT_SYMBOL_GPL(vbus_get); + +void vbus_put(struct vbus *vbus) +{ + if (vbus && atomic_dec_and_test(&vbus->refs)) { + kobject_put(&vbus->devices.kobj); + kobject_put(&vbus->members.kobj); + kobject_put(&vbus->kobj); + } +} +EXPORT_SYMBOL_GPL(vbus_put); + +long vbus_interface_find(struct vbus *bus, + unsigned long id, + struct vbus_device_interface **intf) +{ + struct vbus_device_interface *_intf; + + BUG_ON(!bus); + + mutex_lock(&bus->lock); + + _intf = node_to_intf(map_find(&bus->devices.map, &id)); + if (likely(_intf)) + kobject_get(&_intf->kobj); + + mutex_unlock(&bus->lock); + + if (!_intf) + return -ENOENT; + + *intf = _intf; + + return 0; +} + +const char *vbus_name(struct vbus *vbus) +{ + return vbus ? vbus->kobj.name : NULL; +} + +/* + *--------------------- + * vbus_buses + *--------------------- + */ + +static struct vbus *node_to_bus(struct rb_node *node) +{ + return node ? container_of(node, struct vbus, node) : NULL; +} + +static int bus_item_compare(struct rb_node *lhs, struct rb_node *rhs) +{ + struct vbus *lbus = node_to_bus(lhs); + struct vbus *rbus = node_to_bus(rhs); + + return strcmp(lbus->kobj.name, rbus->kobj.name); +} + +static int bus_key_compare(const void *key, struct rb_node *node) +{ + struct vbus *bus = node_to_bus(node); + + return strcmp(key, bus->kobj.name); +} + +static struct map_ops bus_map_ops = { + .key_compare = &bus_key_compare, + .item_compare = &bus_item_compare, +}; + +struct vbus *vbus_find(const char *name) +{ + struct vbus *bus; + + mutex_lock(&vbus_root.lock); + + bus = node_to_bus(map_find(&vbus_root.buses.map, name)); + if (!bus) + goto out; + + vbus_get(bus); + +out: + mutex_unlock(&vbus_root.lock); + + return bus; + +} + +struct vbus_root vbus_root; + +static ssize_t version_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%d\n", VBUS_VERSION); +} + +static struct kobj_attribute version_attr = + __ATTR(version, S_IRUGO, version_show, NULL); + +static int __init vbus_init(void) +{ + int ret; + + mutex_init(&vbus_root.lock); + + ret = vbus_config_init(); + BUG_ON(ret < 0); + + vbus_root.kobj = kobject_create_and_add("vbus", NULL); + BUG_ON(!vbus_root.kobj); + + ret = sysfs_create_file(vbus_root.kobj, &version_attr.attr); + BUG_ON(ret); + + ret = vbus_devclass_init(); + BUG_ON(ret < 0); + + map_init(&vbus_root.buses.map, &bus_map_ops); + vbus_root.buses.kobj = kobject_create_and_add("instances", + vbus_root.kobj); + BUG_ON(!vbus_root.buses.kobj); + + vbus_root.devices.kobj = kobject_create_and_add("devices", + vbus_root.kobj); + BUG_ON(!vbus_root.devices.kobj); + + return 0; +} + +late_initcall(vbus_init); + + diff --git a/kernel/vbus/devclass.c b/kernel/vbus/devclass.c new file mode 100644 index 0000000..3f5ef0d --- /dev/null +++ b/kernel/vbus/devclass.c @@ -0,0 +1,124 @@ +/* + * Copyright 2009 Novell. All Rights Reserved. + * + * Author: + * Gregory Haskins <ghaskins@xxxxxxxxxx> + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <linux/module.h> +#include <linux/vbus.h> + +#include "vbus.h" + +static struct vbus_devclass *node_to_devclass(struct rb_node *node) +{ + return node ? container_of(node, struct vbus_devclass, node) : NULL; +} + +static int devclass_item_compare(struct rb_node *lhs, struct rb_node *rhs) +{ + struct vbus_devclass *ldc = node_to_devclass(lhs); + struct vbus_devclass *rdc = node_to_devclass(rhs); + + return strcmp(ldc->name, rdc->name); +} + +static int devclass_key_compare(const void *key, struct rb_node *node) +{ + struct vbus_devclass *dc = node_to_devclass(node); + + return strcmp((const char *)key, dc->name); +} + +static struct map_ops devclass_map_ops = { + .key_compare = &devclass_key_compare, + .item_compare = &devclass_item_compare, +}; + +int __init vbus_devclass_init(void) +{ + struct vbus_devclasses *c = &vbus_root.devclasses; + + map_init(&c->map, &devclass_map_ops); + + c->kobj = kobject_create_and_add("deviceclass", vbus_root.kobj); + BUG_ON(!c->kobj); + + return 0; +} + +static void devclass_release(struct kobject *kobj) +{ + struct vbus_devclass *dc = container_of(kobj, + struct vbus_devclass, + kobj); + + if (dc->ops->release) + dc->ops->release(dc); +} + +static struct kobj_type devclass_ktype = { + .release = devclass_release, +}; + +int vbus_devclass_register(struct vbus_devclass *dc) +{ + int ret; + + mutex_lock(&vbus_root.lock); + + ret = map_add(&vbus_root.devclasses.map, &dc->node); + if (ret < 0) + goto out; + + ret = kobject_init_and_add(&dc->kobj, &devclass_ktype, + vbus_root.devclasses.kobj, dc->name); + if (ret < 0) { + map_del(&vbus_root.devclasses.map, &dc->node); + goto out; + } + +out: + mutex_unlock(&vbus_root.lock); + + return ret; +} +EXPORT_SYMBOL_GPL(vbus_devclass_register); + +int vbus_devclass_unregister(struct vbus_devclass *dc) +{ + mutex_lock(&vbus_root.lock); + map_del(&vbus_root.devclasses.map, &dc->node); + mutex_unlock(&vbus_root.lock); + + kobject_put(&dc->kobj); + + return 0; +} +EXPORT_SYMBOL_GPL(vbus_devclass_unregister); + +struct vbus_devclass *vbus_devclass_find(const char *name) +{ + struct vbus_devclass *dev; + + mutex_lock(&vbus_root.lock); + dev = node_to_devclass(map_find(&vbus_root.devclasses.map, name)); + if (dev) + dev = vbus_devclass_get(dev); + mutex_unlock(&vbus_root.lock); + + return dev; +} diff --git a/kernel/vbus/map.c b/kernel/vbus/map.c new file mode 100644 index 0000000..a3bd841 --- /dev/null +++ b/kernel/vbus/map.c @@ -0,0 +1,72 @@ + +#include <linux/errno.h> + +#include "map.h" + +void map_init(struct map *map, struct map_ops *ops) +{ + map->root = RB_ROOT; + map->ops = ops; +} + +int map_add(struct map *map, struct rb_node *node) +{ + int ret = 0; + struct rb_root *root; + struct rb_node **new, *parent = NULL; + + root = &map->root; + new = &(root->rb_node); + + /* Figure out where to put new node */ + while (*new) { + int val; + + parent = *new; + + val = map->ops->item_compare(node, *new); + if (val < 0) + new = &((*new)->rb_left); + else if (val > 0) + new = &((*new)->rb_right); + else { + ret = -EEXIST; + break; + } + } + + if (!ret) { + /* Add new node and rebalance tree. */ + rb_link_node(node, parent, new); + rb_insert_color(node, root); + } + + return ret; +} + +struct rb_node *map_find(struct map *map, const void *key) +{ + struct rb_node *node; + + node = map->root.rb_node; + + while (node) { + int val; + + val = map->ops->key_compare(key, node); + if (val < 0) + node = node->rb_left; + else if (val > 0) + node = node->rb_right; + else + break; + } + + return node; +} + +void map_del(struct map *map, struct rb_node *node) +{ + rb_erase(node, &map->root); +} + diff --git a/kernel/vbus/map.h b/kernel/vbus/map.h new file mode 100644 index 0000000..7fb5164 --- /dev/null +++ b/kernel/vbus/map.h @@ -0,0 +1,41 @@ +/* + * Copyright 2009 Novell. All Rights Reserved. + * + * Author: + * Gregory Haskins <ghaskins@xxxxxxxxxx> + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __VBUS_MAP_H__ +#define __VBUS_MAP_H__ + +#include <linux/rbtree.h> + +struct map_ops { + int (*item_compare)(struct rb_node *lhs, struct rb_node *rhs); + int (*key_compare)(const void *key, struct rb_node *item); +}; + +struct map { + struct rb_root root; + struct map_ops *ops; +}; + +void map_init(struct map *map, struct map_ops *ops); +int map_add(struct map *map, struct rb_node *node); +struct rb_node *map_find(struct map *map, const void *key); +void map_del(struct map *map, struct rb_node *node); + +#endif /* __VBUS_MAP_H__ */ diff --git a/kernel/vbus/vbus.h b/kernel/vbus/vbus.h new file mode 100644 index 0000000..1266d69 --- /dev/null +++ b/kernel/vbus/vbus.h @@ -0,0 +1,116 @@ +/* + * Copyright 2009 Novell. All Rights Reserved. + * + * Author: + * Gregory Haskins <ghaskins@xxxxxxxxxx> + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __VBUS_H__ +#define __VBUS_H__ + +#include <linux/configfs.h> +#include <linux/rbtree.h> +#include <linux/mutex.h> +#include <linux/kobject.h> +#include <linux/cdev.h> +#include <linux/device.h> + +#include "map.h" + +#define VBUS_VERSION 1 + +struct vbus_subdir { + struct map map; + struct kobject kobj; +}; + +struct vbus { + struct { + struct config_group group; + struct config_group perms; + struct config_group *defgroups[2]; + } ci; + + atomic_t refs; + struct mutex lock; + struct kobject kobj; + struct vbus_subdir devices; + struct vbus_subdir members; + unsigned long next_id; + struct rb_node node; +}; + +struct vbus_member { + struct rb_node node; + struct task_struct *tsk; + struct vbus *vbus; + struct kobject kobj; +}; + +struct vbus_devclasses { + struct kobject *kobj; + struct map map; +}; + +struct vbus_buses { + struct config_group ci_group; + struct map map; + struct kobject *kobj; +}; + +struct vbus_devshell { + struct config_group ci_group; + struct vbus_device *dev; + struct vbus_devclass *dc; + struct kobject kobj; + struct kobject intfs; +}; + +struct vbus_devices { + struct config_group ci_group; + struct kobject *kobj; +}; + +struct vbus_root { + struct { + struct configfs_subsystem subsys; + struct config_group *defgroups[3]; + } ci; + + struct mutex lock; + struct kobject *kobj; + struct vbus_devclasses devclasses; + struct vbus_buses buses; + struct vbus_devices devices; +}; + +extern struct vbus_root vbus_root; +extern struct sysfs_ops vbus_dev_attr_ops; + +int vbus_config_init(void); +int vbus_devclass_init(void); + +int vbus_create(const char *name, struct vbus **bus); + +int vbus_devshell_create(const char *name, struct vbus_devshell **ds); +struct vbus_devclass *vbus_devclass_find(const char *name); +int vbus_devshell_type_set(struct vbus_devshell *ds); + +long vbus_interface_find(struct vbus *vbus, + unsigned long id, + struct vbus_device_interface **intf); + +#endif /* __VBUS_H__ */ -- To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html