/*
* IEEE 1394 for Linux
*
* Raw interface to the bus
*
* Copyright (C) 1999, 2000 Andreas E. Bombe
*
* This code is licensed under the GPL. See the file COPYING in the root
* directory of the kernel sources for details.
*/
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/smp_lock.h>
#include <asm/uaccess.h>
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,3,0)
#include <linux/devfs_fs_kernel.h>
#endif
#include "ieee1394.h"
#include "ieee1394_types.h"
#include "ieee1394_core.h"
#include "hosts.h"
#include "highlevel.h"
#include "ieee1394_transactions.h"
#include "raw1394.h"
static devfs_handle_t devfs_handle = NULL;
LIST_HEAD(host_info_list);
static int host_count = 0;
spinlock_t host_info_lock = SPIN_LOCK_UNLOCKED;
static struct hpsb_highlevel *hl_handle = NULL;
static atomic_t iso_buffer_size;
static const int iso_buffer_max = 4 * 1024 * 1024; /* 4 MB */
static void queue_complete_cb(struct pending_request *req);
static struct pending_request *__alloc_pending_request(int flags)
{
struct pending_request *req;
req = (struct pending_request *)kmalloc(sizeof(struct pending_request),
flags);
if (req != NULL) {
memset(req, 0, sizeof(struct pending_request));
INIT_LIST_HEAD(&req->list);
req->tq.routine = (void(*)(void*))queue_complete_cb;
}
return req;
}
inline static struct pending_request *alloc_pending_request(void)
{
return __alloc_pending_request(SLAB_KERNEL);
}
static void free_pending_request(struct pending_request *req)
{
if (req->ibs) {
if (atomic_dec_and_test(&req->ibs->refcount)) {
atomic_sub(req->ibs->data_size, &iso_buffer_size);
kfree(req->ibs);
}
} else if (req->free_data) {
kfree(req->data);
}
free_hpsb_packet(req->packet);
kfree(req);
}
static void queue_complete_req(struct pending_request *req)
{
unsigned long flags;
struct file_info *fi = req->file_info;
spin_lock_irqsave(&fi->reqlists_lock, flags);
list_del(&req->list);
list_add_tail(&req->list, &fi->req_complete);
spin_unlock_irqrestore(&fi->reqlists_lock, flags);
up(&fi->complete_sem);
wake_up_interruptible(&fi->poll_wait_complete);
}
static void queue_complete_cb(struct pending_request *req)
{
struct hpsb_packet *packet = req->packet;
int rcode = (packet->header[1] >> 12) & 0xf;
switch (packet->ack_code) {
case ACKX_NONE:
case ACKX_SEND_ERROR:
req->req.error = RAW1394_ERROR_SEND_ERROR;
break;
case ACKX_ABORTED:
req->req.error = RAW1394_ERROR_ABORTED;
break;
case ACKX_TIMEOUT:
req->req.error = RAW1394_ERROR_TIMEOUT;
break;
default:
req->req.error = (packet->ack_code << 16) | rcode;
break;
}
if (!((packet->ack_code == ACK_PENDING) && (rcode == RCODE_COMPLETE))) {
req->req.length = 0;
}
free_tlabel(packet->host, packet->node_id, packet->tlabel);
queue_complete_req(req);
}
static void add_host(struct hpsb_host *host)
{
struct host_info *hi;
hi = (struct host_info *)kmalloc(sizeof(struct host_info), SLAB_KERNEL);
if (hi != NULL) {
INIT_LIST_HEAD(&hi->list);
hi->host = host;
INIT_LIST_HEAD(&hi->file_info_list);
spin_lock_irq(&host_info_lock);
list_add_tail(&hi->list, &host_info_list);
host_count++;
spin_unlock_irq(&host_info_lock);
}
}
static struct host_info *find_host_info(struct hpsb_host *host)
{
struct list_head *lh;
struct host_info *hi;
lh = host_info_list.next;
while (lh != &host_info_list) {
hi = list_entry(lh, struct host_info, list);
if (hi->host == host) {
return hi;
}
lh = lh->next;
}
return NULL;
}
static void remove_host(struct hpsb_host *host)
{
struct host_info *hi;
spin_lock_irq(&host_info_lock);
hi = find_host_info(host);
if (hi != NULL) {
list_del(&hi->list);
host_count--;
}
spin_unlock_irq(&host_info_lock);
if (hi == NULL) {
printk(KERN_ERR "raw1394: attempt to remove unknown host "
"0x%p\n", host);
return;
}
kfree(hi);
}
static void host_reset(struct hpsb_host *host)
{
unsigned long flags;
struct list_head *lh;
struct host_info *hi;
struct file_info *fi;
struct pending_request *req;
spin_lock_irqsave(&host_info_lock, flags);
hi = find_host_info(host);
if (hi != NULL) {
lh = hi->file_info_list.next;
while (lh != &hi->file_info_list) {
fi = list_entry(lh, struct file_info, list);
req = __alloc_pending_request(SLAB_ATOMIC);
if (req != NULL) {
req->file_info = fi;
req->req.type = RAW1394_REQ_BUS_RESET;
req->req.generation = get_hpsb_generation();
req->req.misc = (host->node_id << 16)
| host->node_count;
queue_complete_req(req);
}
lh = lh->next;
}
}
spin_unlock_irqrestore(&host_info_lock, flags);
}
static void iso_receive(struct hpsb_host *host, int channel, quadlet_t *data,
unsigned int length)
{
unsigned long flags;
struct list_head *lh;
struct host_info *hi;
struct file_info *fi;
struct pending_request *req;
struct iso_block_store *ibs = NULL;
LIST_HEAD(reqs);
if ((atomic_read(&iso_buffer_size) + length) > iso_buffer_max) {
HPSB_INFO("dropped iso packet");
return;
}
spin_lock_irqsave(&host_info_lock, flags);
hi = find_host_info(host);
if (hi != NULL) {
for (lh = hi->file_info_list.next; lh != &hi->file_info_list;
lh = lh->next) {
fi = list_entry(lh, struct file_info, list);
if (!(fi->listen_channels & (1ULL << channel))) {
continue;
}
req = __alloc_pending_request(SLAB_ATOMIC);
if (!req) break;
if (!ibs) {
ibs = kmalloc(sizeof(struct iso_block_store)
+ length, SLAB_ATOMIC);
if (!ibs) {
kfree(req);
break;
}
atomic_add(length, &iso_buffer_size);
atomic_set(&ibs->refcount, 0);
ibs->data_size = length;
memcpy(ibs->data, data, length);
}
atomic_inc(&ibs->refcount);
req->file_info = fi;
req->ibs = ibs;
req->data = ibs->data;
req->req.type = RAW1394_REQ_ISO_RECEIVE;
req->req.generation = get_hpsb_generation();
req->req.misc = 0;
req->req.recvb = (u64)fi->iso_buffer;
req->req.length = MIN(length, fi->iso_buffer_length);
list_add_tail(&req->list, &reqs);
}
}
spin_unlock_irqrestore(&host_info_lock, flags);
lh = reqs.next;
while (lh != &reqs) {
req = list_entry(lh, struct pending_request, list);
lh = lh->next;
queue_complete_req(req);
}
}
static void fcp_request(struct hpsb_host *host, int nodeid, int direction,
int cts, u8 *data, unsigned int length)
{
unsigned long flags;
struct list_head *lh;
struct host_info *hi;
struct file_info *fi;
struct pending_request *req;
struct iso_block_store *ibs = NULL;
LIST_HEAD(reqs);
if ((atomic_read(&iso_buffer_size) + length) > iso_buffer_max) {
HPSB_INFO("dropped fcp request");
return;
}
spin_lock_irqsave(&host_info_lock, flags);
hi = find_host_info(host);
if (hi != NULL) {
for (lh = hi->file_info_list.next; lh != &hi->file_info_list;
lh = lh->next) {
fi = list_entry(lh, struct file_info, list);
if (!fi->fcp_buffer) {
continue;
}
req = __alloc_pending_request(SLAB_ATOMIC);
if (!req) break;
if (!ibs) {
ibs = kmalloc(sizeof(struct iso_block_store)
+ length, SLAB_ATOMIC);
if (!ibs) {
kfree(req);
break;
}
atomic_add(length, &iso_buffer_size);
atomic_set(&ibs->refcount, 0);
ibs->data_size = length;
memcpy(ibs->data, data, length);
}
atomic_inc(&ibs->refcount);
req->file_info = fi;
req->ibs = ibs;
req->data = ibs->data;
req->req.type = RAW1394_REQ_FCP_REQUEST;
req->req.generation = get_hpsb_generation();
req->req.misc = nodeid | (direction << 16);
req->req.recvb = (u64)fi->fcp_buffer;
req->req.length = length;
list_add_tail(&req->list, &reqs);
}
}
spin_unlock_irqrestore(&host_info_lock, flags);
lh = reqs.next;
while (lh != &reqs) {
req = list_entry(lh, struct pending_request, list);
lh = lh->next;
queue_complete_req(req);
}
}
static ssize_t dev_read(struct file *file, char *buffer, size_t count,
loff_t *offset_is_ignored)
{
struct file_info *fi = (struct file_info *)file->private_data;
struct list_head *lh;
struct pending_request *req;
if (count != sizeof(struct raw1394_request)) {
return -EINVAL;
}
if (!access_ok(VERIFY_WRITE, buffer, count)) {
return -EFAULT;
}
if (file->f_flags & O_NONBLOCK) {
if (down_trylock(&fi->complete_sem)) {
return -EAGAIN;
}
} else {
if (down_interruptible(&fi->complete_sem)) {
return -ERESTARTSYS;
}
}
spin_lock_irq(&fi->reqlists_lock);
lh = fi->req_complete.next;
list_del(lh);
spin_unlock_irq(&fi->reqlists_lock);
req = list_entry(lh, struct pending_request, list);
if (req->req.length) {
if (copy_to_user((void *)req->req.recvb, req->data,
req->req.length)) {
req->req.error = RAW1394_ERROR_MEMFAULT;
}
}
__copy_to_user(buffer, &req->req, sizeof(req->req));
free_pending_request(req);
return sizeof(struct raw1394_request);
}
static int state_opened(struct file_info *fi, struct pending_request *req)
{
if (req->req.type == RAW1394_REQ_INITIALIZE) {
if (req->req.misc == RAW1394_KERNELAPI_VERSION) {
fi->state = initialized;
req->req.error = RAW1394_ERROR_NONE;
req->req.generation = get_hpsb_generation();
} else {
req->req.error = RAW1394_ERROR_COMPAT;
req->req.misc = RAW1394_KERNELAPI_VERSION;
}
} else {
req->req.error = RAW1394_ERROR_STATE_ORDER;
}
req->req.length = 0;
queue_complete_req(req);
return sizeof(struct raw1394_request);
}
static int state_initialized(struct file_info *fi, struct pending_request *req)
{
struct list_head *lh;
struct host_info *hi;
struct raw1394_khost_list *khl;
if (req->req.generation != get_hpsb_generation()) {
req->req.error = RAW1394_ERROR_GENERATION;
req->req.generation = get_hpsb_generation();
req->req.length = 0;
queue_complete_req(req);
return sizeof(struct raw1394_request);
}
switch (req->req.type) {
case RAW1394_REQ_LIST_CARDS:
spin_lock_irq(&host_info_lock);
khl = kmalloc(sizeof(struct raw1394_khost_list) * host_count,
SLAB_ATOMIC);
if (khl != NULL) {
req->req.misc = host_count;
req->data = (quadlet_t *)khl;
lh = host_info_list.next;
while (lh != &host_info_list) {
hi = list_entry(lh, struct host_info, list);
khl->nodes = hi->host->node_count;
strcpy(khl->name, hi->host->template->name);
khl++;
lh = lh->next;
}
}
spin_unlock_irq(&host_info_lock);
if (khl != NULL) {
req->req.error = RAW1394_ERROR_NONE;
req->req.length = MIN(req->req.length,
sizeof(struct raw1394_khost_list)
* req->req.misc);
req->free_data = 1;
} else {
return -ENOMEM;
}
break;
case RAW1394_REQ_SET_CARD:
lh = NULL;
spin_lock_irq(&host_info_lock);
if (req->req.misc < host_count) {
lh = host_info_list.next;
while (req->req.misc--) {
lh = lh->next;
}
hi = list_entry(lh, struct host_info, list);
hpsb_inc_host_usage(hi->host);
list_add_tail(&fi->list, &hi->file_info_list);
fi->host = hi->host;
fi->state = connected;
}
spin_unlock_irq(&host_info_lock);
if (lh != NULL) {
req->req.error = RAW1394_ERROR_NONE;
req->req.misc = (fi->host->node_id << 16)
| fi->host->node_count;
} else {
req->req.error = RAW1394_ERROR_INVALID_ARG;
}
req->req.length = 0;
break;
default:
req->req.error = RAW1394_ERROR_STATE_ORDER;
req->req.length = 0;
break;
}
queue_complete_req(req);
return sizeof(struct raw1394_request);
}
static void handle_iso_listen(struct file_info *fi, struct pending_request *req)
{
int channel = req->req.misc;
spin_lock(&host_info_lock);
if ((channel > 63) || (channel < -64)) {
req->req.error = RAW1394_ERROR_INVALID_ARG;
} else if (channel >= 0) {
/* allocate channel req.misc */
if (fi->listen_channels & (1ULL << channel)) {
req->req.error = RAW1394_ERROR_ALREADY;
} else {
fi->listen_channels |= 1ULL << channel;
hpsb_listen_channel(hl_handle, fi->host, channel);
fi->iso_buffer = (void *)req->req.recvb;
fi->iso_buffer_length = req->req.length;
}
} else {
/* deallocate channel (one's complement neg) req.misc */
channel = ~channel;
if (fi->listen_channels & (1ULL << channel)) {
hpsb_unlisten_channel(hl_handle, fi->host, channel);
fi->listen_channels &= ~(1ULL << channel);
} else {
req->req.error = RAW1394_ERROR_INVALID_ARG;
}
}
req->req.length = 0;
queue_complete_req(req);
spin_unlock(&host_info_lock);
}
static void handle_fcp_listen(struct file_info *fi, struct pending_request *req)
{
if (req->req.misc) {
if (fi->fcp_buffer) {
req->req.error = RAW1394_ERROR_ALREADY;
} else {
fi->fcp_buffer = (u8 *)req->req.recvb;
}
} else {
if (!fi->fcp_buffer) {
req->req.error = RAW1394_ERROR_ALREADY;
} else {
fi->fcp_buffer = NULL;
}
}
req->req.length = 0;
queue_complete_req(req);
}
static int handle_local_request(struct file_info *fi,
struct pending_request *req, int node)
{
u64 addr = req->req.address & 0xffffffffffffULL;
req->data = kmalloc(req->req.length, SLAB_KERNEL);
if (!req->data) return -ENOMEM;
req->free_data = 1;
switch (req->req.type) {
case RAW1394_REQ_ASYNC_READ:
req->req.error = highlevel_read(fi->host, node, req->data, addr,
req->req.length);
break;
case RAW1394_REQ_ASYNC_WRITE:
if (copy_from_user(req->data, (void *)req->req.sendb,
req->req.length)) {
req->req.error = RAW1394_ERROR_MEMFAULT;
break;
}
req->req.error = highlevel_write(fi->host, node, req->data,
addr, req->req.length);
req->req.length = 0;
break;
case RAW1394_REQ_LOCK:
if ((req->req.misc == EXTCODE_FETCH_ADD)
|| (req->req.misc == EXTCODE_LITTLE_ADD)) {
if (req->req.length != 4) {
req->req.error = RAW1394_ERROR_INVALID_ARG;
break;
}
} else {
if (req->req.length != 8) {
req->req.error = RAW1394_ERROR_INVALID_ARG;
break;
}
}
if (copy_from_user(req->data, (void *)req->req.sendb,
req->req.length)) {
req->req.error = RAW1394_ERROR_MEMFAULT;
break;
}
if (req->req.length == 8) {
req->req.error = highlevel_lock(fi->host, node,
req->data, addr,
req->data[1],
req->data[0],
req->req.misc);
req->req.length = 4;
} else {
req->req.error = highlevel_lock(fi->host, node,
req->data, addr,
req->data[0], 0,
req->req.misc);
}
break;
case RAW1394_REQ_LOCK64:
default:
req->req.error = RAW1394_ERROR_STATE_ORDER;
}
if (req->req.error) req->req.length = 0;
req->req.error |= 0x00100000;
queue_complete_req(req);
return sizeof(struct raw1394_request);
}
static int handle_remote_request(struct file_info *fi,
struct pending_request *req, int node)
{
struct hpsb_packet *packet = NULL;
u64 addr = req->req.address & 0xffffffffffffULL;
switch (req->req.type) {
case RAW1394_REQ_ASYNC_READ:
if (req->req.length == 4) {
packet = hpsb_make_readqpacket(fi->host, node, addr);
if (!packet) return -ENOMEM;
req->data = &packet->header[3];
} else {
packet = hpsb_make_readbpacket(fi->host, node, addr,
req->req.length);
if (!packet) return -ENOMEM;
req->data = packet->data;
}
break;
case RAW1394_REQ_ASYNC_WRITE:
if (req->req.length == 4) {
quadlet_t x;
if (copy_from_user(&x, (void *)req->req.sendb, 4)) {
req->req.error = RAW1394_ERROR_MEMFAULT;
}
packet = hpsb_make_writeqpacket(fi->host, node, addr,
x);
if (!packet) return -ENOMEM;
} else {
packet = hpsb_make_writebpacket(fi->host, node, addr,
req->req.length);
if (!packet) return -ENOMEM;
if (copy_from_user(packet->data, (void *)req->req.sendb,
req->req.length)) {
req->req.error = RAW1394_ERROR_MEMFAULT;
}
}
req->req.length = 0;
break;
case RAW1394_REQ_LOCK:
if ((req->req.misc == EXTCODE_FETCH_ADD)
|| (req->req.misc == EXTCODE_LITTLE_ADD)) {
if (req->req.length != 4) {
req->req.error = RAW1394_ERROR_INVALID_ARG;
break;
}
} else {
if (req->req.length != 8) {
req->req.error = RAW1394_ERROR_INVALID_ARG;
break;
}
}
packet = hpsb_make_lockpacket(fi->host, node, addr,
req->req.misc);
if (!packet) return -ENOMEM;
if (copy_from_user(packet->data, (void *)req->req.sendb,
req->req.length)) {
req->req.error = RAW1394_ERROR_MEMFAULT;
break;
}
req->data = packet->data;
req->req.length = 4;
break;
case RAW1394_REQ_LOCK64:
default:
req->req.error = RAW1394_ERROR_STATE_ORDER;
}
req->packet = packet;
if (req->req.error) {
req->req.length = 0;
queue_complete_req(req);
return sizeof(struct raw1394_request);
}
req->tq.data = req;
queue_task(&req->tq, &packet->complete_tq);
spin_lock_irq(&fi->reqlists_lock);
list_add_tail(&req->list, &fi->req_pending);
spin_unlock_irq(&fi->reqlists_lock);
if (!hpsb_send_packet(packet)) {
req->req.error = RAW1394_ERROR_SEND_ERROR;
req->req.length = 0;
free_tlabel(packet->host, packet->node_id, packet->tlabel);
queue_complete_req(req);
}
return sizeof(struct raw1394_request);
}
static int state_connected(struct file_info *fi, struct pending_request *req)
{
int node = req->req.address >> 48;
req->req.error = RAW1394_ERROR_NONE;
if (req->req.generation != get_hpsb_generation()) {
req->req.error = RAW1394_ERROR_GENERATION;
req->req.generation = get_hpsb_generation();
req->req.length = 0;
queue_complete_req(req);
return sizeof(struct raw1394_request);
}
if (req->req.type == RAW1394_REQ_ISO_LISTEN) {
handle_iso_listen(fi, req);
return sizeof(struct raw1394_request);
}
if (req->req.type == RAW1394_REQ_FCP_LISTEN) {
handle_fcp_listen(fi, req);
return sizeof(struct raw1394_request);
}
if (req->req.length == 0) {
req->req.error = RAW1394_ERROR_INVALID_ARG;
queue_complete_req(req);
return sizeof(struct raw1394_request);
}
if (fi->host->node_id == node) {
return handle_local_request(fi, req, node);
}
return handle_remote_request(fi, req, node);
}
static ssize_t dev_write(struct file *file, const char *buffer, size_t count,
loff_t *offset_is_ignored)
{
struct file_info *fi = (struct file_info *)file->private_data;
struct pending_request *req;
ssize_t retval = 0;
if (count != sizeof(struct raw1394_request)) {
return -EINVAL;
}
req = alloc_pending_request();
if (req == NULL) {
return -ENOMEM;
}
req->file_info = fi;
if (copy_from_user(&req->req, buffer, sizeof(struct raw1394_request))) {
free_pending_request(req);
return -EFAULT;
}
switch (fi->state) {
case opened:
retval = state_opened(fi, req);
break;
case initialized:
retval = state_initialized(fi, req);
break;
case connected:
retval = state_connected(fi, req);
break;
}
if (retval < 0) {
free_pending_request(req);
}
return retval;
}
static unsigned int dev_poll(struct file *file, poll_table *pt)
{
struct file_info *fi = file->private_data;
unsigned int mask = POLLOUT | POLLWRNORM;
poll_wait(file, &fi->poll_wait_complete, pt);
spin_lock_irq(&fi->reqlists_lock);
if (!list_empty(&fi->req_complete)) {
mask |= POLLIN | POLLRDNORM;
}
spin_unlock_irq(&fi->reqlists_lock);
return mask;
}
static int dev_open(struct inode *inode, struct file *file)
{
struct file_info *fi;
if (MINOR(inode->i_rdev)) {
return -ENXIO;
}
V22_COMPAT_MOD_INC_USE_COUNT;
fi = kmalloc(sizeof(struct file_info), SLAB_KERNEL);
if (fi == NULL) {
V22_COMPAT_MOD_DEC_USE_COUNT;
return -ENOMEM;
}
memset(fi, 0, sizeof(struct file_info));
INIT_LIST_HEAD(&fi->list);
fi->state = opened;
INIT_LIST_HEAD(&fi->req_pending);
INIT_LIST_HEAD(&fi->req_complete);
sema_init(&fi->complete_sem, 0);
spin_lock_init(&fi->reqlists_lock);
init_waitqueue_head(&fi->poll_wait_complete);
file->private_data = fi;
return 0;
}
static int dev_release(struct inode *inode, struct file *file)
{
struct file_info *fi = file->private_data;
struct list_head *lh;
struct pending_request *req;
int done = 0, i;
lock_kernel();
for (i = 0; i < 64; i++) {
if (fi->listen_channels & (1ULL << i)) {
hpsb_unlisten_channel(hl_handle, fi->host, i);
}
}
spin_lock(&host_info_lock);
fi->listen_channels = 0;
spin_unlock(&host_info_lock);
while (!done) {
spin_lock_irq(&fi->reqlists_lock);
while (!list_empty(&fi->req_complete)) {
lh = fi->req_complete.next;
list_del(lh);
req = list_entry(lh, struct pending_request, list);
free_pending_request(req);
}
if (list_empty(&fi->req_pending)) {
done = 1;
}
spin_unlock_irq(&fi->reqlists_lock);
if (!done) {
down_interruptible(&fi->complete_sem);
}
}
if (fi->state == connected) {
spin_lock_irq(&host_info_lock);
list_del(&fi->list);
spin_unlock_irq(&host_info_lock);
hpsb_dec_host_usage(fi->host);
}
kfree(fi);
V22_COMPAT_MOD_DEC_USE_COUNT;
unlock_kernel();
return 0;
}
static struct hpsb_highlevel_ops hl_ops = {
add_host: add_host,
remove_host: remove_host,
host_reset: host_reset,
iso_receive: iso_receive,
fcp_request: fcp_request,
};
static struct file_operations file_ops = {
OWNER_THIS_MODULE
read: dev_read,
write: dev_write,
poll: dev_poll,
open: dev_open,
release: dev_release,
};
int init_raw1394(void)
{
hl_handle = hpsb_register_highlevel(RAW1394_DEVICE_NAME, &hl_ops);
if (hl_handle == NULL) {
HPSB_ERR("raw1394 failed to register with ieee1394 highlevel");
return -ENOMEM;
}
devfs_handle = devfs_register(NULL, RAW1394_DEVICE_NAME, DEVFS_FL_NONE,
RAW1394_DEVICE_MAJOR, 0,
S_IFCHR | S_IRUSR | S_IWUSR, &file_ops,
NULL);
if (devfs_register_chrdev(RAW1394_DEVICE_MAJOR, RAW1394_DEVICE_NAME,
&file_ops)) {
HPSB_ERR("raw1394 failed to register /dev/raw1394 device");
return -EBUSY;
}
printk(KERN_INFO "raw1394: /dev/%s device initialized\n", RAW1394_DEVICE_NAME);
return 0;
}
void cleanup_raw1394(void)
{
devfs_unregister_chrdev(RAW1394_DEVICE_MAJOR, RAW1394_DEVICE_NAME);
devfs_unregister(devfs_handle);
hpsb_unregister_highlevel(hl_handle);
}
#ifdef MODULE
int init_module(void)
{
return init_raw1394();
}
void cleanup_module(void)
{
return cleanup_raw1394();
}
#endif