xref: /openbmc/linux/drivers/firmware/dmi-sysfs.c (revision 948af1f0)
1948af1f0SMike Waychison /*
2948af1f0SMike Waychison  * dmi-sysfs.c
3948af1f0SMike Waychison  *
4948af1f0SMike Waychison  * This module exports the DMI tables read-only to userspace through the
5948af1f0SMike Waychison  * sysfs file system.
6948af1f0SMike Waychison  *
7948af1f0SMike Waychison  * Data is currently found below
8948af1f0SMike Waychison  *    /sys/firmware/dmi/...
9948af1f0SMike Waychison  *
10948af1f0SMike Waychison  * DMI attributes are presented in attribute files with names
11948af1f0SMike Waychison  * formatted using %d-%d, so that the first integer indicates the
12948af1f0SMike Waychison  * structure type (0-255), and the second field is the instance of that
13948af1f0SMike Waychison  * entry.
14948af1f0SMike Waychison  *
15948af1f0SMike Waychison  * Copyright 2011 Google, Inc.
16948af1f0SMike Waychison  */
17948af1f0SMike Waychison 
18948af1f0SMike Waychison #include <linux/kernel.h>
19948af1f0SMike Waychison #include <linux/init.h>
20948af1f0SMike Waychison #include <linux/module.h>
21948af1f0SMike Waychison #include <linux/types.h>
22948af1f0SMike Waychison #include <linux/kobject.h>
23948af1f0SMike Waychison #include <linux/dmi.h>
24948af1f0SMike Waychison #include <linux/capability.h>
25948af1f0SMike Waychison #include <linux/slab.h>
26948af1f0SMike Waychison #include <linux/list.h>
27948af1f0SMike Waychison #include <linux/io.h>
28948af1f0SMike Waychison 
29948af1f0SMike Waychison #define MAX_ENTRY_TYPE 255 /* Most of these aren't used, but we consider
30948af1f0SMike Waychison 			      the top entry type is only 8 bits */
31948af1f0SMike Waychison 
32948af1f0SMike Waychison struct dmi_sysfs_entry {
33948af1f0SMike Waychison 	struct dmi_header dh;
34948af1f0SMike Waychison 	struct kobject kobj;
35948af1f0SMike Waychison 	int instance;
36948af1f0SMike Waychison 	int position;
37948af1f0SMike Waychison 	struct list_head list;
38948af1f0SMike Waychison };
39948af1f0SMike Waychison 
40948af1f0SMike Waychison /*
41948af1f0SMike Waychison  * Global list of dmi_sysfs_entry.  Even though this should only be
42948af1f0SMike Waychison  * manipulated at setup and teardown, the lazy nature of the kobject
43948af1f0SMike Waychison  * system means we get lazy removes.
44948af1f0SMike Waychison  */
45948af1f0SMike Waychison static LIST_HEAD(entry_list);
46948af1f0SMike Waychison static DEFINE_SPINLOCK(entry_list_lock);
47948af1f0SMike Waychison 
48948af1f0SMike Waychison /* dmi_sysfs_attribute - Top level attribute. used by all entries. */
49948af1f0SMike Waychison struct dmi_sysfs_attribute {
50948af1f0SMike Waychison 	struct attribute attr;
51948af1f0SMike Waychison 	ssize_t (*show)(struct dmi_sysfs_entry *entry, char *buf);
52948af1f0SMike Waychison };
53948af1f0SMike Waychison 
54948af1f0SMike Waychison #define DMI_SYSFS_ATTR(_entry, _name) \
55948af1f0SMike Waychison struct dmi_sysfs_attribute dmi_sysfs_attr_##_entry##_##_name = { \
56948af1f0SMike Waychison 	.attr = {.name = __stringify(_name), .mode = 0400}, \
57948af1f0SMike Waychison 	.show = dmi_sysfs_##_entry##_##_name, \
58948af1f0SMike Waychison }
59948af1f0SMike Waychison 
60948af1f0SMike Waychison /*
61948af1f0SMike Waychison  * dmi_sysfs_mapped_attribute - Attribute where we require the entry be
62948af1f0SMike Waychison  * mapped in.  Use in conjunction with dmi_sysfs_specialize_attr_ops.
63948af1f0SMike Waychison  */
64948af1f0SMike Waychison struct dmi_sysfs_mapped_attribute {
65948af1f0SMike Waychison 	struct attribute attr;
66948af1f0SMike Waychison 	ssize_t (*show)(struct dmi_sysfs_entry *entry,
67948af1f0SMike Waychison 			const struct dmi_header *dh,
68948af1f0SMike Waychison 			char *buf);
69948af1f0SMike Waychison };
70948af1f0SMike Waychison 
71948af1f0SMike Waychison #define DMI_SYSFS_MAPPED_ATTR(_entry, _name) \
72948af1f0SMike Waychison struct dmi_sysfs_mapped_attribute dmi_sysfs_attr_##_entry##_##_name = { \
73948af1f0SMike Waychison 	.attr = {.name = __stringify(_name), .mode = 0400}, \
74948af1f0SMike Waychison 	.show = dmi_sysfs_##_entry##_##_name, \
75948af1f0SMike Waychison }
76948af1f0SMike Waychison 
77948af1f0SMike Waychison /*************************************************
78948af1f0SMike Waychison  * Generic DMI entry support.
79948af1f0SMike Waychison  *************************************************/
80948af1f0SMike Waychison 
81948af1f0SMike Waychison static struct dmi_sysfs_entry *to_entry(struct kobject *kobj)
82948af1f0SMike Waychison {
83948af1f0SMike Waychison 	return container_of(kobj, struct dmi_sysfs_entry, kobj);
84948af1f0SMike Waychison }
85948af1f0SMike Waychison 
86948af1f0SMike Waychison static struct dmi_sysfs_attribute *to_attr(struct attribute *attr)
87948af1f0SMike Waychison {
88948af1f0SMike Waychison 	return container_of(attr, struct dmi_sysfs_attribute, attr);
89948af1f0SMike Waychison }
90948af1f0SMike Waychison 
91948af1f0SMike Waychison static ssize_t dmi_sysfs_attr_show(struct kobject *kobj,
92948af1f0SMike Waychison 				   struct attribute *_attr, char *buf)
93948af1f0SMike Waychison {
94948af1f0SMike Waychison 	struct dmi_sysfs_entry *entry = to_entry(kobj);
95948af1f0SMike Waychison 	struct dmi_sysfs_attribute *attr = to_attr(_attr);
96948af1f0SMike Waychison 
97948af1f0SMike Waychison 	/* DMI stuff is only ever admin visible */
98948af1f0SMike Waychison 	if (!capable(CAP_SYS_ADMIN))
99948af1f0SMike Waychison 		return -EACCES;
100948af1f0SMike Waychison 
101948af1f0SMike Waychison 	return attr->show(entry, buf);
102948af1f0SMike Waychison }
103948af1f0SMike Waychison 
104948af1f0SMike Waychison static const struct sysfs_ops dmi_sysfs_attr_ops = {
105948af1f0SMike Waychison 	.show = dmi_sysfs_attr_show,
106948af1f0SMike Waychison };
107948af1f0SMike Waychison 
108948af1f0SMike Waychison typedef ssize_t (*dmi_callback)(struct dmi_sysfs_entry *,
109948af1f0SMike Waychison 				const struct dmi_header *dh, void *);
110948af1f0SMike Waychison 
111948af1f0SMike Waychison struct find_dmi_data {
112948af1f0SMike Waychison 	struct dmi_sysfs_entry	*entry;
113948af1f0SMike Waychison 	dmi_callback		callback;
114948af1f0SMike Waychison 	void			*private;
115948af1f0SMike Waychison 	int			instance_countdown;
116948af1f0SMike Waychison 	ssize_t			ret;
117948af1f0SMike Waychison };
118948af1f0SMike Waychison 
119948af1f0SMike Waychison static void find_dmi_entry_helper(const struct dmi_header *dh,
120948af1f0SMike Waychison 				  void *_data)
121948af1f0SMike Waychison {
122948af1f0SMike Waychison 	struct find_dmi_data *data = _data;
123948af1f0SMike Waychison 	struct dmi_sysfs_entry *entry = data->entry;
124948af1f0SMike Waychison 
125948af1f0SMike Waychison 	/* Is this the entry we want? */
126948af1f0SMike Waychison 	if (dh->type != entry->dh.type)
127948af1f0SMike Waychison 		return;
128948af1f0SMike Waychison 
129948af1f0SMike Waychison 	if (data->instance_countdown != 0) {
130948af1f0SMike Waychison 		/* try the next instance? */
131948af1f0SMike Waychison 		data->instance_countdown--;
132948af1f0SMike Waychison 		return;
133948af1f0SMike Waychison 	}
134948af1f0SMike Waychison 
135948af1f0SMike Waychison 	/*
136948af1f0SMike Waychison 	 * Don't ever revisit the instance.  Short circuit later
137948af1f0SMike Waychison 	 * instances by letting the instance_countdown run negative
138948af1f0SMike Waychison 	 */
139948af1f0SMike Waychison 	data->instance_countdown--;
140948af1f0SMike Waychison 
141948af1f0SMike Waychison 	/* Found the entry */
142948af1f0SMike Waychison 	data->ret = data->callback(entry, dh, data->private);
143948af1f0SMike Waychison }
144948af1f0SMike Waychison 
145948af1f0SMike Waychison /* State for passing the read parameters through dmi_find_entry() */
146948af1f0SMike Waychison struct dmi_read_state {
147948af1f0SMike Waychison 	char *buf;
148948af1f0SMike Waychison 	loff_t pos;
149948af1f0SMike Waychison 	size_t count;
150948af1f0SMike Waychison };
151948af1f0SMike Waychison 
152948af1f0SMike Waychison static ssize_t find_dmi_entry(struct dmi_sysfs_entry *entry,
153948af1f0SMike Waychison 			      dmi_callback callback, void *private)
154948af1f0SMike Waychison {
155948af1f0SMike Waychison 	struct find_dmi_data data = {
156948af1f0SMike Waychison 		.entry = entry,
157948af1f0SMike Waychison 		.callback = callback,
158948af1f0SMike Waychison 		.private = private,
159948af1f0SMike Waychison 		.instance_countdown = entry->instance,
160948af1f0SMike Waychison 		.ret = -EIO,  /* To signal the entry disappeared */
161948af1f0SMike Waychison 	};
162948af1f0SMike Waychison 	int ret;
163948af1f0SMike Waychison 
164948af1f0SMike Waychison 	ret = dmi_walk(find_dmi_entry_helper, &data);
165948af1f0SMike Waychison 	/* This shouldn't happen, but just in case. */
166948af1f0SMike Waychison 	if (ret)
167948af1f0SMike Waychison 		return -EINVAL;
168948af1f0SMike Waychison 	return data.ret;
169948af1f0SMike Waychison }
170948af1f0SMike Waychison 
171948af1f0SMike Waychison /*
172948af1f0SMike Waychison  * Calculate and return the byte length of the dmi entry identified by
173948af1f0SMike Waychison  * dh.  This includes both the formatted portion as well as the
174948af1f0SMike Waychison  * unformatted string space, including the two trailing nul characters.
175948af1f0SMike Waychison  */
176948af1f0SMike Waychison static size_t dmi_entry_length(const struct dmi_header *dh)
177948af1f0SMike Waychison {
178948af1f0SMike Waychison 	const char *p = (const char *)dh;
179948af1f0SMike Waychison 
180948af1f0SMike Waychison 	p += dh->length;
181948af1f0SMike Waychison 
182948af1f0SMike Waychison 	while (p[0] || p[1])
183948af1f0SMike Waychison 		p++;
184948af1f0SMike Waychison 
185948af1f0SMike Waychison 	return 2 + p - (const char *)dh;
186948af1f0SMike Waychison }
187948af1f0SMike Waychison 
188948af1f0SMike Waychison /*************************************************
189948af1f0SMike Waychison  * Generic DMI entry support.
190948af1f0SMike Waychison  *************************************************/
191948af1f0SMike Waychison 
192948af1f0SMike Waychison static ssize_t dmi_sysfs_entry_length(struct dmi_sysfs_entry *entry, char *buf)
193948af1f0SMike Waychison {
194948af1f0SMike Waychison 	return sprintf(buf, "%d\n", entry->dh.length);
195948af1f0SMike Waychison }
196948af1f0SMike Waychison 
197948af1f0SMike Waychison static ssize_t dmi_sysfs_entry_handle(struct dmi_sysfs_entry *entry, char *buf)
198948af1f0SMike Waychison {
199948af1f0SMike Waychison 	return sprintf(buf, "%d\n", entry->dh.handle);
200948af1f0SMike Waychison }
201948af1f0SMike Waychison 
202948af1f0SMike Waychison static ssize_t dmi_sysfs_entry_type(struct dmi_sysfs_entry *entry, char *buf)
203948af1f0SMike Waychison {
204948af1f0SMike Waychison 	return sprintf(buf, "%d\n", entry->dh.type);
205948af1f0SMike Waychison }
206948af1f0SMike Waychison 
207948af1f0SMike Waychison static ssize_t dmi_sysfs_entry_instance(struct dmi_sysfs_entry *entry,
208948af1f0SMike Waychison 					char *buf)
209948af1f0SMike Waychison {
210948af1f0SMike Waychison 	return sprintf(buf, "%d\n", entry->instance);
211948af1f0SMike Waychison }
212948af1f0SMike Waychison 
213948af1f0SMike Waychison static ssize_t dmi_sysfs_entry_position(struct dmi_sysfs_entry *entry,
214948af1f0SMike Waychison 					char *buf)
215948af1f0SMike Waychison {
216948af1f0SMike Waychison 	return sprintf(buf, "%d\n", entry->position);
217948af1f0SMike Waychison }
218948af1f0SMike Waychison 
219948af1f0SMike Waychison static DMI_SYSFS_ATTR(entry, length);
220948af1f0SMike Waychison static DMI_SYSFS_ATTR(entry, handle);
221948af1f0SMike Waychison static DMI_SYSFS_ATTR(entry, type);
222948af1f0SMike Waychison static DMI_SYSFS_ATTR(entry, instance);
223948af1f0SMike Waychison static DMI_SYSFS_ATTR(entry, position);
224948af1f0SMike Waychison 
225948af1f0SMike Waychison static struct attribute *dmi_sysfs_entry_attrs[] = {
226948af1f0SMike Waychison 	&dmi_sysfs_attr_entry_length.attr,
227948af1f0SMike Waychison 	&dmi_sysfs_attr_entry_handle.attr,
228948af1f0SMike Waychison 	&dmi_sysfs_attr_entry_type.attr,
229948af1f0SMike Waychison 	&dmi_sysfs_attr_entry_instance.attr,
230948af1f0SMike Waychison 	&dmi_sysfs_attr_entry_position.attr,
231948af1f0SMike Waychison 	NULL,
232948af1f0SMike Waychison };
233948af1f0SMike Waychison 
234948af1f0SMike Waychison static ssize_t dmi_entry_raw_read_helper(struct dmi_sysfs_entry *entry,
235948af1f0SMike Waychison 					 const struct dmi_header *dh,
236948af1f0SMike Waychison 					 void *_state)
237948af1f0SMike Waychison {
238948af1f0SMike Waychison 	struct dmi_read_state *state = _state;
239948af1f0SMike Waychison 	size_t entry_length;
240948af1f0SMike Waychison 
241948af1f0SMike Waychison 	entry_length = dmi_entry_length(dh);
242948af1f0SMike Waychison 
243948af1f0SMike Waychison 	return memory_read_from_buffer(state->buf, state->count,
244948af1f0SMike Waychison 				       &state->pos, dh, entry_length);
245948af1f0SMike Waychison }
246948af1f0SMike Waychison 
247948af1f0SMike Waychison static ssize_t dmi_entry_raw_read(struct file *filp,
248948af1f0SMike Waychison 				  struct kobject *kobj,
249948af1f0SMike Waychison 				  struct bin_attribute *bin_attr,
250948af1f0SMike Waychison 				  char *buf, loff_t pos, size_t count)
251948af1f0SMike Waychison {
252948af1f0SMike Waychison 	struct dmi_sysfs_entry *entry = to_entry(kobj);
253948af1f0SMike Waychison 	struct dmi_read_state state = {
254948af1f0SMike Waychison 		.buf = buf,
255948af1f0SMike Waychison 		.pos = pos,
256948af1f0SMike Waychison 		.count = count,
257948af1f0SMike Waychison 	};
258948af1f0SMike Waychison 
259948af1f0SMike Waychison 	return find_dmi_entry(entry, dmi_entry_raw_read_helper, &state);
260948af1f0SMike Waychison }
261948af1f0SMike Waychison 
262948af1f0SMike Waychison static const struct bin_attribute dmi_entry_raw_attr = {
263948af1f0SMike Waychison 	.attr = {.name = "raw", .mode = 0400},
264948af1f0SMike Waychison 	.read = dmi_entry_raw_read,
265948af1f0SMike Waychison };
266948af1f0SMike Waychison 
267948af1f0SMike Waychison static void dmi_sysfs_entry_release(struct kobject *kobj)
268948af1f0SMike Waychison {
269948af1f0SMike Waychison 	struct dmi_sysfs_entry *entry = to_entry(kobj);
270948af1f0SMike Waychison 	sysfs_remove_bin_file(&entry->kobj, &dmi_entry_raw_attr);
271948af1f0SMike Waychison 	spin_lock(&entry_list_lock);
272948af1f0SMike Waychison 	list_del(&entry->list);
273948af1f0SMike Waychison 	spin_unlock(&entry_list_lock);
274948af1f0SMike Waychison 	kfree(entry);
275948af1f0SMike Waychison }
276948af1f0SMike Waychison 
277948af1f0SMike Waychison static struct kobj_type dmi_sysfs_entry_ktype = {
278948af1f0SMike Waychison 	.release = dmi_sysfs_entry_release,
279948af1f0SMike Waychison 	.sysfs_ops = &dmi_sysfs_attr_ops,
280948af1f0SMike Waychison 	.default_attrs = dmi_sysfs_entry_attrs,
281948af1f0SMike Waychison };
282948af1f0SMike Waychison 
283948af1f0SMike Waychison static struct kobject *dmi_kobj;
284948af1f0SMike Waychison static struct kset *dmi_kset;
285948af1f0SMike Waychison 
286948af1f0SMike Waychison /* Global count of all instances seen.  Only for setup */
287948af1f0SMike Waychison static int __initdata instance_counts[MAX_ENTRY_TYPE + 1];
288948af1f0SMike Waychison 
289948af1f0SMike Waychison /* Global positional count of all entries seen.  Only for setup */
290948af1f0SMike Waychison static int __initdata position_count;
291948af1f0SMike Waychison 
292948af1f0SMike Waychison static void __init dmi_sysfs_register_handle(const struct dmi_header *dh,
293948af1f0SMike Waychison 					     void *_ret)
294948af1f0SMike Waychison {
295948af1f0SMike Waychison 	struct dmi_sysfs_entry *entry;
296948af1f0SMike Waychison 	int *ret = _ret;
297948af1f0SMike Waychison 
298948af1f0SMike Waychison 	/* If a previous entry saw an error, short circuit */
299948af1f0SMike Waychison 	if (*ret)
300948af1f0SMike Waychison 		return;
301948af1f0SMike Waychison 
302948af1f0SMike Waychison 	/* Allocate and register a new entry into the entries set */
303948af1f0SMike Waychison 	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
304948af1f0SMike Waychison 	if (!entry) {
305948af1f0SMike Waychison 		*ret = -ENOMEM;
306948af1f0SMike Waychison 		return;
307948af1f0SMike Waychison 	}
308948af1f0SMike Waychison 
309948af1f0SMike Waychison 	/* Set the key */
310948af1f0SMike Waychison 	entry->dh = *dh;
311948af1f0SMike Waychison 	entry->instance = instance_counts[dh->type]++;
312948af1f0SMike Waychison 	entry->position = position_count++;
313948af1f0SMike Waychison 
314948af1f0SMike Waychison 	entry->kobj.kset = dmi_kset;
315948af1f0SMike Waychison 	*ret = kobject_init_and_add(&entry->kobj, &dmi_sysfs_entry_ktype, NULL,
316948af1f0SMike Waychison 				    "%d-%d", dh->type, entry->instance);
317948af1f0SMike Waychison 
318948af1f0SMike Waychison 	if (*ret) {
319948af1f0SMike Waychison 		kfree(entry);
320948af1f0SMike Waychison 		return;
321948af1f0SMike Waychison 	}
322948af1f0SMike Waychison 
323948af1f0SMike Waychison 	/* Thread on the global list for cleanup */
324948af1f0SMike Waychison 	spin_lock(&entry_list_lock);
325948af1f0SMike Waychison 	list_add_tail(&entry->list, &entry_list);
326948af1f0SMike Waychison 	spin_unlock(&entry_list_lock);
327948af1f0SMike Waychison 
328948af1f0SMike Waychison 	/* Create the raw binary file to access the entry */
329948af1f0SMike Waychison 	*ret = sysfs_create_bin_file(&entry->kobj, &dmi_entry_raw_attr);
330948af1f0SMike Waychison 	if (*ret)
331948af1f0SMike Waychison 		goto out_err;
332948af1f0SMike Waychison 
333948af1f0SMike Waychison 	return;
334948af1f0SMike Waychison out_err:
335948af1f0SMike Waychison 	kobject_put(&entry->kobj);
336948af1f0SMike Waychison 	return;
337948af1f0SMike Waychison }
338948af1f0SMike Waychison 
339948af1f0SMike Waychison static void cleanup_entry_list(void)
340948af1f0SMike Waychison {
341948af1f0SMike Waychison 	struct dmi_sysfs_entry *entry, *next;
342948af1f0SMike Waychison 
343948af1f0SMike Waychison 	/* No locks, we are on our way out */
344948af1f0SMike Waychison 	list_for_each_entry_safe(entry, next, &entry_list, list) {
345948af1f0SMike Waychison 		kobject_put(&entry->kobj);
346948af1f0SMike Waychison 	}
347948af1f0SMike Waychison }
348948af1f0SMike Waychison 
349948af1f0SMike Waychison static int __init dmi_sysfs_init(void)
350948af1f0SMike Waychison {
351948af1f0SMike Waychison 	int error = -ENOMEM;
352948af1f0SMike Waychison 	int val;
353948af1f0SMike Waychison 
354948af1f0SMike Waychison 	/* Set up our directory */
355948af1f0SMike Waychison 	dmi_kobj = kobject_create_and_add("dmi", firmware_kobj);
356948af1f0SMike Waychison 	if (!dmi_kobj)
357948af1f0SMike Waychison 		goto err;
358948af1f0SMike Waychison 
359948af1f0SMike Waychison 	dmi_kset = kset_create_and_add("entries", NULL, dmi_kobj);
360948af1f0SMike Waychison 	if (!dmi_kset)
361948af1f0SMike Waychison 		goto err;
362948af1f0SMike Waychison 
363948af1f0SMike Waychison 	val = 0;
364948af1f0SMike Waychison 	error = dmi_walk(dmi_sysfs_register_handle, &val);
365948af1f0SMike Waychison 	if (error)
366948af1f0SMike Waychison 		goto err;
367948af1f0SMike Waychison 	if (val) {
368948af1f0SMike Waychison 		error = val;
369948af1f0SMike Waychison 		goto err;
370948af1f0SMike Waychison 	}
371948af1f0SMike Waychison 
372948af1f0SMike Waychison 	pr_debug("dmi-sysfs: loaded.\n");
373948af1f0SMike Waychison 
374948af1f0SMike Waychison 	return 0;
375948af1f0SMike Waychison err:
376948af1f0SMike Waychison 	cleanup_entry_list();
377948af1f0SMike Waychison 	kset_unregister(dmi_kset);
378948af1f0SMike Waychison 	kobject_put(dmi_kobj);
379948af1f0SMike Waychison 	return error;
380948af1f0SMike Waychison }
381948af1f0SMike Waychison 
382948af1f0SMike Waychison /* clean up everything. */
383948af1f0SMike Waychison static void __exit dmi_sysfs_exit(void)
384948af1f0SMike Waychison {
385948af1f0SMike Waychison 	pr_debug("dmi-sysfs: unloading.\n");
386948af1f0SMike Waychison 	cleanup_entry_list();
387948af1f0SMike Waychison 	kset_unregister(dmi_kset);
388948af1f0SMike Waychison 	kobject_put(dmi_kobj);
389948af1f0SMike Waychison }
390948af1f0SMike Waychison 
391948af1f0SMike Waychison module_init(dmi_sysfs_init);
392948af1f0SMike Waychison module_exit(dmi_sysfs_exit);
393948af1f0SMike Waychison 
394948af1f0SMike Waychison MODULE_AUTHOR("Mike Waychison <mikew@google.com>");
395948af1f0SMike Waychison MODULE_DESCRIPTION("DMI sysfs support");
396948af1f0SMike Waychison MODULE_LICENSE("GPL");
397