/*
* IEEE 1394 for Linux
*
* Raw interface to the bus
*
* Copyright (C) 1999, 2000 Andreas E. Bombe
* 2001, 2002 Manfred Weihs <weihs@ict.tuwien.ac.at>
* 2002 Christian Toegel <christian.toegel@gmx.at>
*
* This code is licensed under the GPL. See the file COPYING in the root
* directory of the kernel sources for details.
*
*
* Contributions:
*
* Manfred Weihs <weihs@ict.tuwien.ac.at>
* configuration ROM manipulation
* address range mapping
* adaptation for new (transparent) loopback mechanism
* sending of arbitrary async packets
* Christian Toegel <christian.toegel@gmx.at>
* address range mapping
* lock64 request
* transmit physical packet
* busreset notification control (switch on/off)
* busreset with selection of type (short/long)
* request_reply
*/
#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/mutex.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/vmalloc.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <linux/compat.h>
#include "csr1212.h"
#include "highlevel.h"
#include "hosts.h"
#include "ieee1394.h"
#include "ieee1394_core.h"
#include "ieee1394_hotplug.h"
#include "ieee1394_transactions.h"
#include "ieee1394_types.h"
#include "iso.h"
#include "nodemgr.h"
#include "raw1394.h"
#include "raw1394-private.h"
#define int2ptr(x) ((void __user *)(unsigned long)x)
#define ptr2int(x) ((u64)(unsigned long)(void __user *)x)
#ifdef CONFIG_IEEE1394_VERBOSEDEBUG
#define RAW1394_DEBUG
#endif
#ifdef RAW1394_DEBUG
#define DBGMSG(fmt, args...) \
printk(KERN_INFO "raw1394:" fmt "\n" , ## args)
#else
#define DBGMSG(fmt, args...) do {} while (0)
#endif
static LIST_HEAD(host_info_list);
static int host_count;
static DEFINE_SPINLOCK(host_info_lock);
static atomic_t internal_generation = ATOMIC_INIT(0);
static atomic_t iso_buffer_size;
static const int iso_buffer_max = 4 * 1024 * 1024; /* 4 MB */
static struct hpsb_highlevel raw1394_highlevel;
static int arm_read(struct hpsb_host *host, int nodeid, quadlet_t * buffer,
u64 addr, size_t length, u16 flags);
static int arm_write(struct hpsb_host *host, int nodeid, int destid,
quadlet_t * data, u64 addr, size_t length, u16 flags);
static int arm_lock(struct hpsb_host *host, int nodeid, quadlet_t * store,
u64 addr, quadlet_t data, quadlet_t arg, int ext_tcode,
u16 flags);
static int arm_lock64(struct hpsb_host *host, int nodeid, octlet_t * store,
u64 addr, octlet_t data, octlet_t arg, int ext_tcode,
u16 flags);
static const struct hpsb_address_ops arm_ops = {
.read = arm_read,
.write = arm_write,
.lock = arm_lock,
.lock64 = arm_lock64,
};
static void queue_complete_cb(struct pending_request *req);
static struct pending_request *__alloc_pending_request(gfp_t flags)
{
struct pending_request *req;
req = kzalloc(sizeof(*req), flags);
if (req)
INIT_LIST_HEAD(&req->list);
return req;
}
static inline struct pending_request *alloc_pending_request(void)
{
return __alloc_pending_request(GFP_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);
}
hpsb_free_packet(req->packet);
kfree(req);
}
/* fi->reqlists_lock must be taken */
static void __queue_complete_req(struct pending_request *req)
{
struct file_info *fi = req->file_info;
list_move_tail(&req->list, &fi->req_complete);
wake_up(&fi->wait_complete);
}
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);
__queue_complete_req(req);
spin_unlock_irqrestore(&fi->reqlists_lock, flags);
}
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;
}
if ((req->req.type == RAW1394_REQ_ASYNC_READ) ||
(req->req.type == RAW1394_REQ_ASYNC_WRITE) ||
(req->req.type == RAW1394_REQ_ASYNC_STREAM) ||
(req->req.type == RAW1394_REQ_LOCK) ||
(req->req.type == RAW1394_REQ_LOCK64))
hpsb_free_tlabel(packet);
queue_complete_req(req);
}
static void add_host(struct hpsb_host *host)
{
struct host_info *hi;
unsigned long flags;
hi = kmalloc(sizeof(*hi), GFP_KERNEL);
if (hi) {
INIT_LIST_HEAD(&hi->list);
hi->host = host;
INIT_LIST_HEAD(&hi->file_info_list);
spin_lock_irqsave(&host_info_lock, flags);
list_add_tail(&hi->list, &host_info_list);
host_count++;
spin_unlock_irqrestore(&host_info_lock, flags);
}
atomic_inc(&internal_generation);
}
static struct host_info *find_host_info(struct hpsb_host *host)
{
struct host_info *hi;
list_for_each_entry(hi, &host_info_list, list)
if (hi->host == host)
return hi;
return NULL;
}
static void remove_host(struct hpsb_host *host)
{
struct host_info *hi;
unsigned long flags;
spin_lock_irqsave(&host_info_lock, flags);
hi = find_host_info(host);
if (hi != NULL) {
list_del(&hi->list);
host_count--;
/*
FIXME: address ranges should be removed
and fileinfo states should be initialized
(including setting generation to
internal-generation ...)
*/
}
spin_unlock_irqrestore(&host_info_lock, flags);
if (hi == NULL) {
printk(KERN_ERR "raw1394: attempt to remove unknown host "
"0x%p\n", host);
return;
}
kfree(hi);
atomic_inc(&internal_generation);
}
static void host_reset(struct hpsb_host *host)
{
unsigned long flags;
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) {
list_for_each_entry(fi, &hi->file_info_list, list) {
if (fi->notification == RAW1394_NOTIFY_ON) {
req = __alloc_pending_request(GFP_ATOMIC);
if (req != NULL) {
req->file_info = fi;
req->req.type = RAW1394_REQ_BUS_RESET;
req->req.generation =
get_hpsb_generation(host);
req->req.misc = (host->node_id << 16)
| host->node_count;
if (fi->protocol_version > 3) {
req->req.misc |=
(NODEID_TO_NODE
(host->irm_id)
<< 8);
}
queue_complete_req(req);
}
}
}
}
spin_unlock_irqrestore(&host_info_lock, flags);
}
static void fcp_request(struct hpsb_host *host, int nodeid, int direction,
int cts, u8 * data, size_t length)
{
unsigned long flags;
struct host_info *hi;
struct file_info *fi;
struct pending_request *req, *req_next;
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) {
list_for_each_entry(fi, &hi->file_info_list, list) {
if (!fi->fcp_buffer)
continue;
req = __alloc_pending_request(GFP_ATOMIC);
if (!req)
break;
if (!ibs) {
ibs = kmalloc(sizeof(*ibs) + length,
GFP_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(host);
req->req.misc = nodeid | (direction << 16);
req->req.recvb = ptr2int(fi->fcp_buffer);
req->req.length = length;
list_add_tail(&req->list, &reqs);
}
}
spin_unlock_irqrestore(&host_info_lock, flags);
list_for_each_entry_safe(req, req_next, &reqs, list)
queue_complete_req(req);
}
#ifdef CONFIG_COMPAT
struct compat_raw1394_req {
__u32 type;
__s32 error;
__u32 misc;
__u32 generation;
__u32 length;
__u64 address;
__u64 tag;
__u64 sendb;
__u64 recvb;
}
#if defined(CONFIG_X86_64) || defined(CONFIG_IA64)
__attribute__((packed))
#endif
;
static const char __user *raw1394_compat_write(const char __user *buf)
{
struct compat_raw1394_req __user *cr = (typeof(cr)) buf;
struct raw1394_request __user *r;
r = compat_alloc_user_space(sizeof(struct raw1394_request));
#define C(x) __copy_in_user(&r->x, &cr->x, sizeof(r->x))
if (copy_in_user(r, cr, sizeof(struct compat_raw1394_req)) ||
C(address) ||
C(tag) ||
C(sendb) ||
C(recvb))
return (__force const char __user *)ERR_PTR(-EFAULT);
return (const char __user *)r;
}
#undef C
#define P(x) __put_user(r->x, &cr->x)
static int
raw1394_compat_read(const char __user *buf, struct raw1394_request *r)
{
struct compat_raw1394_req __user *cr = (typeof(cr)) buf;
if (!access_ok(VERIFY_WRITE, cr, sizeof(struct compat_raw1394_req)) ||
P(type) ||
P(error) ||
P(misc) ||
P(generation) ||
P(length) ||
P(address) ||
P(tag) ||
P(sendb) ||
P(recvb))
return -EFAULT;
return sizeof(struct compat_raw1394_req);
}
#undef P
#endif
/* get next completed request (caller must hold fi->reqlists_lock) */
static inline struct pending_request *__next_complete_req(struct file_info *fi)
{
struct list_head *lh;
struct pending_request *req = NULL;
if (!list_empty(&fi->req_complete)) {
lh = fi->req_complete.next;
list_del(lh);
req = list_entry(lh, struct pending_request, list);
}
return req;
}
/* atomically get next completed request */
static struct pending_request *next_complete_req(struct file_info *fi)
{
unsigned long flags;
struct pending_request *req;
spin_lock_irqsave(&fi->reqlists_lock, flags);
req = __next_complete_req(fi);
spin_unlock_irqrestore(&fi->reqlists_lock, flags);
return req;
}
static ssize_t raw1394_read(struct file *file, char __user * 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 ret;
#ifdef CONFIG_COMPAT
if (count == sizeof(struct compat_raw1394_req)) {
/* ok */
} else
#endif
if (count != sizeof(struct raw1394_request)) {
return -EINVAL;
}
if (!access_ok(VERIFY_WRITE, buffer, count)) {
return -EFAULT;
}
if (file->f_flags & O_NONBLOCK) {
if (!(req = next_complete_req(fi)))
return -EAGAIN;
} else {
/*
* NB: We call the macro wait_event_interruptible() with a
* condition argument with side effect. This is only possible
* because the side effect does not occur until the condition
* became true, and wait_event_interruptible() won't evaluate
* the condition again after that.
*/
if (wait_event_interruptible(fi->wait_complete,
(req = next_complete_req(fi))))
return -ERESTARTSYS;
}
if (req->req.length) {
if (copy_to_user(int2ptr(req->req.recvb), req->data,
req->req.length)) {
req->req.error = RAW1394_ERROR_MEMFAULT;
}
}
#ifdef CONFIG_COMPAT
if (count == sizeof(struct compat_raw1394_req) &&
sizeof(struct compat_raw1394_req) !=
sizeof(struct raw1394_request)) {
ret = raw1394_compat_read(buffer, &req->req);
} else
#endif
{
if (copy_to_user(buffer, &req->req, sizeof(req->req))) {
ret = -EFAULT;
goto out;
}
ret = (ssize_t) sizeof(struct raw1394_request);
}
out:
free_pending_request(req);
return ret;
}
static int state_opened(struct file_info *fi, struct pending_request *req)
{
if (req->req.type == RAW1394_REQ_INITIALIZE) {
switch (req->req.misc) {
case RAW1394_KERNELAPI_VERSION:
case 3:
fi->state = initialized;
fi->protocol_version = req->req.misc;
req->req.error = RAW1394_ERROR_NONE;
req->req.generation = atomic_read(&internal_generation);
break;
default:
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 0;
}
static int state_initialized(struct file_info *fi, struct pending_request *req)
{
unsigned long flags;
struct host_info *hi;
struct raw1394_khost_list *khl;
if (req->req.generation != atomic_read(&internal_generation)) {
req->req.error = RAW1394_ERROR_GENERATION;
req->req.generation = atomic_read(&internal_generation);
req->req.length = 0;
queue_complete_req(req);
return 0;
}
switch (req->req.type) {
case RAW1394_REQ_LIST_CARDS:
spin_lock_irqsave(&host_info_lock, flags);
khl = kmalloc(sizeof(*khl) * host_count, GFP_ATOMIC);
if (khl) {
req->req.misc = host_count;
req->data = (quadlet_t *) khl;
list_for_each_entry(hi, &host_info_list, list) {
khl->nodes = hi->host->node_count;
strcpy(khl->name, hi->host->driver->name);
khl++;
}
}
spin_unlock_irqrestore(&host_info_lock, flags);
if (khl) {
req->req.error = RAW1394_ERROR_NONE;
req->req.length = min(req->req.length,
(u32) (sizeof
(struct raw1394_khost_list)
* req->req.misc));
req->free_data = 1;
} else {
return -ENOMEM;
}
break;
case RAW1394_REQ_SET_CARD:
spin_lock_irqsave(&host_info_lock, flags);
if (req->req.misc >= host_count) {
req->req.error = RAW1394_ERROR_INVALID_ARG;
goto out_set_card;
}
list_for_each_entry(hi, &host_info_list, list)
if (!req->req.misc--)
break;
get_device(&hi->host->device); /* FIXME handle failure case */
list_add_tail(&fi->list, &hi->file_info_list);
/* prevent unloading of the host's low-level driver */
if (!try_module_get(hi->host->driver->owner)) {
req->req.error = RAW1394_ERROR_ABORTED;
goto out_set_card;
}
WARN_ON(fi->host);
fi->host = hi->host;
fi->state = connected;
req->req.error = RAW1394_ERROR_NONE;
req->req.generation = get_hpsb_generation(fi->host);
req->req.misc = (fi->host->node_id << 16)
| fi->host->node_count;
if (fi->protocol_version > 3)
req->req.misc |= NODEID_TO_NODE(fi->host->irm_id) << 8;
out_set_card:
spin_unlock_irqrestore(&host_info_lock, flags);
req->req.length = 0;
break;
default:
req->req.error = RAW1394_ERROR_STATE_ORDER;
req->req.length = 0;
break;
}
queue_complete_req(req);
return 0;
}
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 {