blob: baa3e932f8dd32dc0ae422bf157d47bbfd55f7ea [file] [log] [blame]
/* -*- c-basic-offset: 8 -*-
*
* raw1394.c -- Emulation of the raw1394 API on the fw stack
*
* Copyright (C) 2007 Kristian Hoegsberg <krh@bitplanet.net>
*
* This library is licensed under the GNU Lesser General Public License (LGPL),
* version 2.1 or later. See the file COPYING.LIB in the distribution for
* details.
*/
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <dirent.h>
#include <sys/ioctl.h>
#include <sys/epoll.h>
#include <sys/inotify.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h> /* for ntohl and htonl */
#include "fw.h"
#include "raw1394_private.h"
/*
* ABI version history is documented in linux/firewire-cdev.h.
*/
#ifdef FW_CDEV_EVENT_REQUEST2
#define IMPLEMENTED_CDEV_ABI_VERSION 4
#else
#define IMPLEMENTED_CDEV_ABI_VERSION 2
#endif
int
fw_errcode_to_errno(raw1394_errcode_t errcode)
{
switch (errcode) {
case -RCODE_SEND_ERROR:
case -RCODE_CANCELLED:
case -RCODE_BUSY:
case -RCODE_GENERATION:
case -RCODE_NO_ACK:
return EAGAIN;
case raw1394_make_errcode(ACK_COMPLETE, RCODE_COMPLETE):
return 0;
case raw1394_make_errcode(ACK_COMPLETE, RCODE_CONFLICT_ERROR):
return EAGAIN;
case raw1394_make_errcode(ACK_COMPLETE, RCODE_DATA_ERROR):
return EREMOTEIO;
case raw1394_make_errcode(ACK_COMPLETE, RCODE_TYPE_ERROR):
return EPERM;
case raw1394_make_errcode(ACK_COMPLETE, RCODE_ADDRESS_ERROR):
return EINVAL;
default:
return EINVAL;
}
}
static int
fw_to_raw1394_errcode(int rcode)
{
/* Best effort matching fw extended rcodes to raw1394 err
* code. Since the raw1394 errcode decoding are macros we try
* to convert the fw rcodes to something that looks enough
* like the raw1394 errcodes that we retain ABI compatibility.
*
* Juju rcodes less than 0x10 are standard ieee1394 rcodes,
* which we map to a raw1394 errcode by or'ing in an
* ACK_COMPLETE ack code in the upper 16 bits. Errors
* internal to raw1394 are negative values, but fw encodes
* these errors as rcodes greater than or equal to 0x10. In
* this case, we just the negated value, which will look like
* an raw1394 internal error code. */
if (rcode < 0x10)
return raw1394_make_errcode(ACK_COMPLETE, rcode);
else
return -rcode;
}
static int
default_tag_handler(raw1394handle_t handle,
unsigned long tag, raw1394_errcode_t err)
{
struct raw1394_reqhandle *rh = (struct raw1394_reqhandle *) tag;
if (rh != NULL)
return rh->callback(handle, rh->data, err);
return -1;
}
static int
default_arm_tag_handler(raw1394handle_t handle, unsigned long arm_tag,
byte_t type, unsigned int length, void *data)
{
struct raw1394_arm_reqhandle *rh;
if (arm_tag == 0)
return -1;
rh = (struct raw1394_arm_reqhandle *) arm_tag;
return rh->arm_callback(handle, data, length, rh->pcontext, type);
}
static int
default_bus_reset_handler(raw1394handle_t handle, unsigned int gen)
{
handle->mode.fw->generation = gen;
return 0;
}
static int
is_fw_device_name(char *name)
{
return strncmp(name, FW_DEVICE_PREFIX, strlen(FW_DEVICE_PREFIX)) == 0
&& isdigit(name[strlen(FW_DEVICE_PREFIX)]);
}
static int
scan_devices(fw_handle_t handle)
{
DIR *dir;
struct dirent *de;
char filename[32];
struct fw_cdev_get_info get_info;
struct fw_cdev_event_bus_reset reset;
int fd, err, i, j, fname_str_sz;
struct port *ports;
ports = handle->ports;
memset(ports, 0, sizeof handle->ports);
for (i = 0; i < MAX_PORTS; i++)
ports[i].card = -1;
dir = opendir(FW_DEVICE_DIR);
if (dir == NULL)
return -1;
for (i = 0; i < MAX_PORTS; ) {
de = readdir(dir);
if (de == NULL)
break;
if (!is_fw_device_name(de->d_name))
continue;
snprintf(filename, sizeof filename, FW_DEVICE_DIR "/%s", de->d_name);
fd = open(filename, O_RDWR);
if (fd < 0)
continue;
memset(&get_info, 0, sizeof(get_info));
memset(&reset, 0, sizeof(reset));
get_info.version = IMPLEMENTED_CDEV_ABI_VERSION;
get_info.rom = 0;
get_info.rom_length = 0;
get_info.bus_reset = ptr_to_u64(&reset);
err = ioctl(fd, FW_CDEV_IOC_GET_INFO, &get_info);
close(fd);
if (err < 0)
continue;
for (j = 0; j < i; j++)
if (ports[j].card == get_info.card)
break;
if (j == i) {
fname_str_sz = sizeof(ports[i].device_file) - 1;
strncpy(ports[i].device_file, filename, fname_str_sz);
ports[i].device_file[fname_str_sz] = '\0';
ports[i].node_count = (reset.root_node_id & 0x3f) + 1;
ports[i].card = get_info.card;
i++;
}
}
closedir(dir);
handle->port_count = i;
return 0;
}
static int
handle_echo_pipe(raw1394handle_t handle,
struct epoll_closure *ec, __uint32_t events)
{
quadlet_t value;
if (read(handle->mode.fw->pipe_fds[0], &value, sizeof value) < 0)
return -1;
return value;
}
static int
handle_lost_device(fw_handle_t handle, int i)
{
int phy_id;
/* The device got unplugged, get rid of it. The fd is
* automatically dropped from the epoll context when we close it. */
close(handle->devices[i].fd);
phy_id = handle->devices[i].node_id & 0x3f;
if (handle->nodes[phy_id] == i)
handle->nodes[phy_id] = -1;
handle->devices[i].node_id = -1;
return 0;
}
struct address_closure {
int (*callback)(raw1394handle_t handle, struct address_closure *ac,
int tcode, unsigned long long offset,
int source_node_id, int card, unsigned kernel_handle,
size_t length, void *data);
};
static int
handle_fcp_request(raw1394handle_t handle, struct address_closure *ac,
int tcode, unsigned long long offset, int source_node_id,
int card, unsigned kernel_handle, size_t length, void *data)
{
struct fw_cdev_send_response response;
int is_response;
response.handle = kernel_handle;
response.rcode = RCODE_COMPLETE;
response.length = 0;
response.data = 0;
if (handle->mode.fw->fcp_handler == NULL)
response.rcode = RCODE_ADDRESS_ERROR;
if (tcode >= TCODE_WRITE_RESPONSE)
response.rcode = RCODE_CONFLICT_ERROR;
if (ioctl(handle->mode.fw->ioctl_fd,
FW_CDEV_IOC_SEND_RESPONSE, &response) < 0)
return -1;
if (card != handle->mode.fw->card)
return 0;
if (response.rcode != RCODE_COMPLETE)
return 0;
is_response = offset >= CSR_REGISTER_BASE + CSR_FCP_RESPONSE;
return handle->mode.fw->fcp_handler(handle, source_node_id,
is_response, length, data);
}
static int
handle_device_event(raw1394handle_t handle,
struct epoll_closure *ec, __uint32_t events)
{
fw_handle_t fwhandle = handle->mode.fw;
union fw_cdev_event *u;
struct device *device = (struct device *) ec;
struct address_closure *ac;
struct request_closure *rc;
unsigned long tag;
raw1394_errcode_t errcode;
int len, phy_id;
int i;
i = device - fwhandle->devices;
if (events & EPOLLHUP)
return handle_lost_device(fwhandle, i);
len = read(fwhandle->devices[i].fd,
fwhandle->buffer, sizeof fwhandle->buffer);
if (len < 0)
return -1;
u = (void *) fwhandle->buffer;
switch (u->common.type) {
case FW_CDEV_EVENT_BUS_RESET:
/* Clear old entry, unless it's been overwritten. */
phy_id = fwhandle->devices[i].node_id & 0x3f;
if (fwhandle->nodes[phy_id] == i)
fwhandle->nodes[phy_id] = -1;
fwhandle->nodes[u->bus_reset.node_id & 0x3f] = i;
fwhandle->devices[i].node_id = u->bus_reset.node_id;
fwhandle->devices[i].generation = u->bus_reset.generation;
if (u->bus_reset.node_id != u->bus_reset.local_node_id)
return 0;
memcpy(&fwhandle->reset, &u->bus_reset, sizeof fwhandle->reset);
return fwhandle->bus_reset_handler(handle,
u->bus_reset.generation);
case FW_CDEV_EVENT_RESPONSE:
rc = u64_to_ptr(u->response.closure);
/* Kernel ensures that u->response.length does not overflow. */
if (rc->data != NULL && u->response.rcode == RCODE_COMPLETE)
memcpy(rc->data, u->response.data, u->response.length);
errcode = fw_to_raw1394_errcode(u->response.rcode);
tag = rc->tag;
free(rc);
return fwhandle->tag_handler(handle, tag, errcode);
case FW_CDEV_EVENT_REQUEST:
ac = u64_to_ptr(u->request.closure);
return ac->callback(handle, ac, u->request.tcode,
u->request.offset,
/* wild guess, but can't do better */
fwhandle->devices[i].node_id,
fwhandle->card,
u->request.handle,
u->request.length, u->request.data);
#ifdef FW_CDEV_EVENT_REQUEST2 /* added in kernel 2.6.36 */
case FW_CDEV_EVENT_REQUEST2:
ac = u64_to_ptr(u->request.closure);
return ac->callback(handle, ac, u->request2.tcode,
u->request2.offset,
u->request2.source_node_id,
u->request2.card,
u->request2.handle,
u->request2.length, u->request2.data);
#endif
#ifdef FW_CDEV_EVENT_ISO_RESOURCE_ALLOCATED /* added in kernel 2.6.30 */
case FW_CDEV_EVENT_ISO_RESOURCE_ALLOCATED:
case FW_CDEV_EVENT_ISO_RESOURCE_DEALLOCATED:
memcpy(u64_to_ptr(u->iso_resource.closure), u,
sizeof u->iso_resource);
return 0;
#endif
#ifdef FW_CDEV_EVENT_PHY_PACKET_SENT /* added in kernel 2.6.36 */
case FW_CDEV_EVENT_PHY_PACKET_SENT:
rc = u64_to_ptr(u->phy_packet.closure);
errcode = fw_to_raw1394_errcode(u->phy_packet.rcode);
tag = rc->tag;
free(rc);
return fwhandle->tag_handler(handle, tag, errcode);
#endif
default:
case FW_CDEV_EVENT_ISO_INTERRUPT:
/* Never happens. */
return -1;
}
}
static int
handle_inotify(raw1394handle_t handle, struct epoll_closure *ec,
__uint32_t events)
{
fw_handle_t fwhandle = handle->mode.fw;
struct inotify_event *event;
char filename[32];
struct fw_cdev_get_info info;
struct fw_cdev_event_bus_reset reset;
struct epoll_event ep;
int i, len, fd, phy_id, fname_str_sz;
event = (struct inotify_event *) fwhandle->buffer;
len = read(fwhandle->inotify_fd, event, BUFFER_SIZE);
if (!(event->mask & IN_CREATE))
return -1;
if (!is_fw_device_name(event->name))
return 0;
snprintf(filename, sizeof filename, FW_DEVICE_DIR "/%s", event->name);
fd = open(filename, O_RDWR);
if (fd < 0) {
switch (errno) {
case ENOENT:
/* Huh, it disappeared before we could
* open it. */
return 0;
case EACCES:
/* We don't have permission to talk to
* this device, maybe it's a storage
* device. */
return 0;
default:
/* Anything else is bad news. */
return -1;
}
}
info.version = IMPLEMENTED_CDEV_ABI_VERSION;
info.rom = 0;
info.rom_length = 0;
info.bus_reset = ptr_to_u64(&reset);
if (ioctl(fd, FW_CDEV_IOC_GET_INFO, &info) < 0) {
close(fd);
return -1;
}
for (i = 0; i < MAX_DEVICES; i++)
if (fwhandle->devices[i].node_id == -1)
break;
if (i == MAX_DEVICES) {
close(fd);
return -1;
}
phy_id = reset.node_id & 0x3f;
fwhandle->nodes[phy_id] = i;
fwhandle->devices[i].node_id = reset.node_id;
fwhandle->devices[i].generation = reset.generation;
fwhandle->devices[i].fd = fd;
fname_str_sz = sizeof(fwhandle->devices[i].filename) - 1;
strncpy(fwhandle->devices[i].filename, filename, fname_str_sz);
fwhandle->devices[i].filename[fname_str_sz] = '\0';
fwhandle->devices[i].closure.func = handle_device_event;
ep.events = EPOLLIN;
ep.data.ptr = &fwhandle->devices[i].closure;
if (epoll_ctl(fwhandle->epoll_fd, EPOLL_CTL_ADD, fd, &ep) < 0) {
close(fd);
return -1;
}
return 0;
}
int fw_loop_iterate(raw1394handle_t handle)
{
int i, count, retval = 0;
struct epoll_closure *closure;
struct epoll_event ep[32];
count = epoll_wait(handle->mode.fw->epoll_fd, ep, ARRAY_LENGTH(ep), -1);
if (count < 0)
return -1;
for (i = 0; i < count; i++) {
closure = ep[i].data.ptr;
retval = closure->func(handle, closure, ep[i].events);
}
/* It looks like we have to add this work-around to get epoll
* to recompute the POLLIN status of the epoll_fd. */
epoll_wait(handle->mode.fw->epoll_fd, ep, ARRAY_LENGTH(ep), 0);
return retval;
}
fw_handle_t fw_new_handle(void)
{
fw_handle_t handle;
struct epoll_event ep;
int i;
handle = malloc(sizeof *handle);
if (handle == NULL) {
errno = ENOMEM;
return NULL;
}
memset(&ep, 0, sizeof(ep));
memset(handle, 0, sizeof(*handle));
handle->tag_handler = default_tag_handler;
handle->arm_tag_handler = default_arm_tag_handler;
handle->allocations = NULL;
handle->notify_bus_reset = RAW1394_NOTIFY_ON;
handle->bus_reset_handler = default_bus_reset_handler;
handle->iso.fd = -1;
handle->epoll_fd = epoll_create(16);
if (handle->epoll_fd < 0)
goto out_handle;
if (pipe(handle->pipe_fds) < 0)
goto out_epoll;
handle->inotify_fd = inotify_init();
if (handle->inotify_fd < 0)
goto out_pipe;
handle->inotify_watch =
inotify_add_watch(handle->inotify_fd, FW_DEVICE_DIR, IN_CREATE);
if (handle->inotify_watch < 0)
goto out_inotify;
handle->pipe_closure.func = handle_echo_pipe;
ep.events = EPOLLIN;
ep.data.ptr = &handle->pipe_closure;
if (epoll_ctl(handle->epoll_fd, EPOLL_CTL_ADD,
handle->pipe_fds[0], &ep) < 0)
goto out_inotify;
handle->inotify_closure.func = handle_inotify;
ep.events = EPOLLIN;
ep.data.ptr = &handle->inotify_closure;
if (epoll_ctl(handle->epoll_fd, EPOLL_CTL_ADD,
handle->inotify_fd, &ep) < 0)
goto out_inotify;
for (i = 0; i < MAX_DEVICES; i++) {
handle->nodes[i] = -1;
handle->devices[i].node_id = -1;
}
scan_devices(handle);
return handle;
out_inotify:
close(handle->inotify_fd);
out_pipe:
close(handle->pipe_fds[0]);
close(handle->pipe_fds[1]);
out_epoll:
close(handle->epoll_fd);
out_handle:
free(handle);
return NULL;
}
void fw_destroy_handle(fw_handle_t handle)
{
int i;
close(handle->inotify_fd);
close(handle->pipe_fds[0]);
close(handle->pipe_fds[1]);
for (i = 0; i < MAX_DEVICES; i++) {
if (handle->devices[i].node_id == -1)
continue;
close(handle->devices[i].fd);
}
fw_iso_shutdown(handle);
close(handle->epoll_fd);
free(handle);
return;
}
fw_handle_t fw_new_handle_on_port(int port)
{
fw_handle_t handle;
handle = fw_new_handle();
if (handle == NULL)
return NULL;
if (fw_set_port(handle, port) < 0) {
fw_destroy_handle(handle);
return NULL;
}
return handle;
}
int fw_busreset_notify (fw_handle_t handle, int off_on_switch)
{
handle->notify_bus_reset = off_on_switch;
return 0;
}
int fw_get_fd(fw_handle_t handle)
{
return handle->epoll_fd;
}
nodeid_t fw_get_local_id(fw_handle_t handle)
{
return handle->reset.local_node_id;
}
nodeid_t fw_get_irm_id(fw_handle_t handle)
{
return handle->reset.irm_node_id;
}
int fw_get_nodecount(fw_handle_t handle)
{
return (handle->reset.root_node_id & 0x3f) + 1;
}
int fw_get_port_info(fw_handle_t handle,
struct raw1394_portinfo *pinf,
int maxports)
{
int i, port_name_sz;
if (maxports >= handle->port_count)
maxports = handle->port_count;
for (i = 0; i < maxports; i++) {
pinf[i].nodes = handle->ports[i].node_count;
port_name_sz = sizeof(pinf[i].name) - 1;
strncpy(pinf[i].name, handle->ports[i].device_file,
port_name_sz);
pinf[i].name[port_name_sz] = '\0';
}
return handle->port_count;
}
int fw_set_port(fw_handle_t handle, int port)
{
struct fw_cdev_get_info get_info;
struct fw_cdev_event_bus_reset reset;
struct epoll_event ep;
struct dirent *de;
char filename[32];
DIR *dir;
int i, fd, phy_id, fname_str_sz;
if (port >= handle->port_count) {
errno = EINVAL;
return -1;
}
dir = opendir("/dev");
if (dir == NULL)
return -1;
for (i = 0; i < MAX_DEVICES; ) {
de = readdir(dir);
if (de == NULL)
break;
if (strncmp(de->d_name, "fw", 2) != 0)
continue;
snprintf(filename, sizeof filename, "/dev/%s", de->d_name);
fd = open(filename, O_RDWR);
if (fd < 0)
continue;
memset(&get_info, 0, sizeof(get_info));
memset(&reset, 0, sizeof(reset));
get_info.version = IMPLEMENTED_CDEV_ABI_VERSION;
get_info.rom = 0;
get_info.rom_length = 0;
get_info.bus_reset = ptr_to_u64(&reset);
if (ioctl(fd, FW_CDEV_IOC_GET_INFO, &get_info) < 0) {
close(fd);
continue;
}
if (get_info.card != handle->ports[port].card) {
close(fd);
continue;
}
phy_id = reset.node_id & 0x3f;
handle->nodes[phy_id] = i;
handle->devices[i].node_id = reset.node_id;
handle->devices[i].generation = reset.generation;
handle->devices[i].fd = fd;
fname_str_sz = sizeof(handle->devices[i].filename) -1;
strncpy(handle->devices[i].filename, filename, fname_str_sz);
handle->devices[i].filename[fname_str_sz] = '\0';
handle->devices[i].closure.func = handle_device_event;
memset(&ep, 0, sizeof(ep));
ep.events = EPOLLIN;
ep.data.ptr = &handle->devices[i].closure;
if (epoll_ctl(handle->epoll_fd, EPOLL_CTL_ADD, fd, &ep) < 0) {
close(fd);
closedir(dir);
return -1;
}
if (handle->iso.filename == NULL) {
memcpy(&handle->reset, &reset, sizeof handle->reset);
handle->iso.filename = handle->devices[i].filename;
handle->ioctl_fd = fd;
}
if (reset.node_id == reset.local_node_id)
handle->local_device = &handle->devices[i];
handle->card = get_info.card;
handle->generation = reset.generation;
handle->abi_version = get_info.version;
i++;
}
closedir(dir);
if (i == 0) {
errno = ENODEV;
return -1;
}
return 0;
}
int fw_reset_bus_new(fw_handle_t handle, int type)
{
struct fw_cdev_initiate_bus_reset initiate;
switch (type) {
case RAW1394_LONG_RESET:
initiate.type = FW_CDEV_LONG_RESET;
break;
case RAW1394_SHORT_RESET:
initiate.type = FW_CDEV_SHORT_RESET;
break;
}
return ioctl(handle->ioctl_fd,
FW_CDEV_IOC_INITIATE_BUS_RESET, &initiate);
}
struct request_response_block {
struct raw1394_arm_request_response request_response;
struct raw1394_arm_request request;
struct raw1394_arm_response response;
unsigned char data[0];
};
struct allocation {
struct address_closure closure; /* must be first member */
struct allocation *next;
__u32 handle;
byte_t *buffer;
octlet_t tag;
arm_options_t access_rights;
arm_options_t notification_options;
arm_options_t client_transactions;
nodeaddr_t offset;
size_t length;
unsigned char data[0];
};
static int
handle_arm_request(raw1394handle_t handle, struct address_closure *ac,
int tcode, unsigned long long offset, int source_node_id,
int card, unsigned kernel_handle, size_t length, void *data)
{
fw_handle_t fwhandle = handle->mode.fw;
struct allocation *allocation = (struct allocation *) ac;
struct request_response_block *rrb;
struct fw_cdev_send_response response;
arm_options_t type;
size_t in_length;
int pos;
pos = offset - allocation->offset;
response.handle = kernel_handle;
switch (tcode) {
case TCODE_WRITE_QUADLET_REQUEST:
case TCODE_WRITE_BLOCK_REQUEST:
type = RAW1394_ARM_WRITE;
in_length = length;
response.rcode = RCODE_COMPLETE;
response.length = 0;
response.data = 0;
break;
case TCODE_READ_QUADLET_REQUEST:
case TCODE_READ_BLOCK_REQUEST:
type = RAW1394_ARM_READ;
in_length = 0;
response.rcode = RCODE_COMPLETE;
response.length = length;
response.data = ptr_to_u64(allocation->data + pos);
break;
case TCODE_LOCK_REQUEST:
/*
* TCODE_LOCK_REQUEST is generated by ABI v3 and older, cannot
* be handled. Fall through for now. FIXME.
*/
case TCODE_LOCK_MASK_SWAP:
case TCODE_LOCK_COMPARE_SWAP:
case TCODE_LOCK_FETCH_ADD:
case TCODE_LOCK_LITTLE_ADD:
case TCODE_LOCK_BOUNDED_ADD:
case TCODE_LOCK_WRAP_ADD:
case TCODE_LOCK_VENDOR_DEPENDENT:
type = RAW1394_ARM_LOCK;
in_length = length;
response.length = 4;
break;
default:
in_length = 0;
type = 0;
break;
}
if (!(allocation->access_rights & type)) {
response.rcode = RCODE_TYPE_ERROR;
response.length = 0;
response.data = 0;
if (ioctl(fwhandle->ioctl_fd,
FW_CDEV_IOC_SEND_RESPONSE, &response) < 0)
return -1;
} else if (!(allocation->client_transactions & type)) {
if (type == RAW1394_ARM_WRITE)
memcpy(allocation->data + pos, data, length);
else if (type == RAW1394_ARM_LOCK)
/* FIXME: do lock ops here */;
if (ioctl(fwhandle->ioctl_fd,
FW_CDEV_IOC_SEND_RESPONSE, &response) < 0)
return -1;
}
/*
* libraw1394 clients do not expect requests from nodes on
* a card other than the one set by raw1394_set_port().
*/
if (card != fwhandle->card)
return 0;
if (!(allocation->notification_options & type))
return 0;
rrb = malloc(sizeof *rrb + in_length + response.length);
if (rrb == NULL) {
errno == ENOMEM;
return -1;
}
rrb->request_response.request = &rrb->request;
rrb->request_response.response = &rrb->response;
rrb->request.destination_nodeid = fwhandle->reset.local_node_id;
rrb->request.source_nodeid = source_node_id;
rrb->request.destination_offset = offset;
rrb->request.tlabel = 0;
if (tcode < 0x10) {
rrb->request.tcode = tcode;
rrb->request.extended_transaction_code = 0;
} else {
rrb->request.tcode = TCODE_LOCK_REQUEST;
rrb->request.extended_transaction_code = tcode - 0x10;
}
rrb->request.generation = fwhandle->reset.generation;
rrb->request.buffer_length = in_length;
rrb->request.buffer = rrb->data;
memcpy(rrb->request.buffer, data, in_length);
rrb->response.response_code = response.rcode;
rrb->response.buffer_length = response.length;
rrb->response.buffer = rrb->data + in_length;
memcpy(rrb->response.buffer, allocation->data + pos, response.length);
return fwhandle->arm_tag_handler(handle, allocation->tag, type,
length, &rrb->request_response);
}
int
fw_arm_register(fw_handle_t handle, nodeaddr_t start,
size_t length, byte_t *initial_value,
octlet_t arm_tag, arm_options_t access_rights,
arm_options_t notification_options,
arm_options_t client_transactions)
{
struct fw_cdev_allocate request;
struct allocation *allocation;
int retval;
allocation = malloc(sizeof *allocation + length);
if (allocation == NULL) {
errno = ENOMEM;
return -1;
}
allocation->closure.callback = handle_arm_request;
allocation->buffer = initial_value;
allocation->tag = arm_tag;
allocation->access_rights = access_rights;
allocation->notification_options = notification_options;
allocation->client_transactions = client_transactions;
allocation->offset = start;
allocation->length = length;
if (initial_value != NULL)
memcpy(allocation->data, initial_value, length);
request.offset = start;
request.length = length;
request.closure = ptr_to_u64(&allocation->closure);
#if IMPLEMENTED_CDEV_ABI_VERSION >= 4
request.region_end = start + length;
#endif
retval = ioctl(handle->ioctl_fd, FW_CDEV_IOC_ALLOCATE, &request);
if (retval < 0) {
free(allocation);
return -1;
}
allocation->handle = request.handle;
allocation->next = handle->allocations;
handle->allocations = allocation;
return 0;
}
static struct allocation *
lookup_allocation(fw_handle_t handle, nodeaddr_t start, int delete)
{
struct allocation *a, **prev;
prev = &handle->allocations;
for (a = handle->allocations; a != NULL; a = a->next) {
if (a->offset <= start && start < a->offset + a->length)
break;
prev = &a->next;
}
if (a != NULL && delete)
*prev = a->next;
return a;
}
int
fw_arm_unregister(fw_handle_t handle, nodeaddr_t start)
{
struct fw_cdev_deallocate request;
struct allocation *allocation;
allocation = lookup_allocation(handle, start, 1);
if (allocation == NULL) {
errno = EINVAL;
return -1;
}
request.handle = allocation->handle;
free(allocation);
return ioctl(handle->ioctl_fd, FW_CDEV_IOC_DEALLOCATE, &request);
}
int
fw_arm_set_buf(fw_handle_t handle, nodeaddr_t start,
size_t length, void *buf)
{
struct allocation *allocation;
allocation = lookup_allocation(handle, start, 0);
if (allocation == NULL) {
errno = ENOENT;
return -1;
}
memcpy(allocation->data + allocation->offset - start, buf, length);
return 0;
}
int
fw_arm_get_buf(fw_handle_t handle, nodeaddr_t start,
size_t length, void *buf)
{
struct allocation *allocation;
allocation = lookup_allocation(handle, start, 0);
if (allocation == NULL) {
errno = ENOENT;
return -1;
}
memcpy(buf, allocation->data + allocation->offset - start, length);
return 0;
}
int
fw_echo_request(fw_handle_t handle, quadlet_t data)
{
return write(handle->pipe_fds[1], &data, sizeof data);
}
int fw_wake_up(fw_handle_t handle)
{
return fw_echo_request(handle, 0);
}
static int
send_request(fw_handle_t handle, int tcode,
nodeid_t node, nodeaddr_t addr,
size_t in_length, void *in, size_t out_length, void *out,
unsigned long tag)
{
struct fw_cdev_send_request *request;
struct request_closure *closure;
int ioctl_nr = FW_CDEV_IOC_SEND_REQUEST;
int fd, i, retval;
#ifdef FW_CDEV_IOC_SEND_STREAM_PACKET /* added in kernel 2.6.30 */
if (tcode == TCODE_STREAM_DATA) {
ioctl_nr = FW_CDEV_IOC_SEND_STREAM_PACKET;
fd = handle->ioctl_fd;
}
#else
if (tcode == TCODE_STREAM_DATA) {
errno = ENOSYS;
return -1;
}
#endif
#ifdef FW_CDEV_IOC_SEND_BROADCAST_REQUEST /* added in kernel 2.6.30 */
if (node == 0xffff) {
ioctl_nr = FW_CDEV_IOC_SEND_BROADCAST_REQUEST;
fd = handle->ioctl_fd;
}
#endif
if (ioctl_nr != FW_CDEV_IOC_SEND_REQUEST)
goto node_id_ok;
if (node > handle->reset.root_node_id) {
handle->err = -RCODE_NO_ACK;
errno = fw_errcode_to_errno(handle->err);
return -1;
}
i = handle->nodes[node & 0x3f];
if (i == -1) {
handle->err = -RCODE_NO_ACK;
errno = fw_errcode_to_errno(handle->err);
return -1;
}
if (handle->generation != handle->devices[i].generation) {
handle->err = -RCODE_GENERATION;
errno = fw_errcode_to_errno(handle->err);
return -1;
}
fd = handle->devices[i].fd;
node_id_ok:
closure = malloc(sizeof *closure);
if (closure == NULL) {
handle->err = -RCODE_SEND_ERROR;
errno = fw_errcode_to_errno(handle->err);
return -1;
}
closure->data = out;
closure->tag = tag;
request = (struct fw_cdev_send_request *) handle->buffer;
request->tcode = tcode;
request->length = in_length > out_length ? in_length : out_length;
request->offset = addr;
request->closure = ptr_to_u64(closure);
request->data = ptr_to_u64(in);
request->generation = handle->generation;
#ifdef FW_CDEV_IOC_SEND_STREAM_PACKET
if (tcode == TCODE_STREAM_DATA) {
struct fw_cdev_send_stream_packet *p
= (struct fw_cdev_send_stream_packet *) request;
p->length = in_length;
p->tag = (addr >> 14) & 0x3;
p->channel = (addr >> 8) & 0x3f;
p->sy = addr & 0xf;
p->closure = ptr_to_u64(closure);
p->data = ptr_to_u64(in);
p->generation = handle->generation;
p->speed = (addr >> 4) & 0x7;
}
#endif
retval = ioctl(fd, ioctl_nr, request);
if (retval < 0)
free(closure);
return retval;
}
int
fw_start_read(fw_handle_t handle, nodeid_t node, nodeaddr_t addr,
size_t length, quadlet_t *buffer, unsigned long tag)
{
int tcode;
if (length == 4)
tcode = TCODE_READ_QUADLET_REQUEST;
else
tcode = TCODE_READ_BLOCK_REQUEST;
return send_request(handle, tcode,
node, addr, 0, NULL, length, buffer, tag);
}
int
fw_start_write(fw_handle_t handle, nodeid_t node, nodeaddr_t addr,
size_t length, quadlet_t *data, unsigned long tag)
{
int tcode;
if (length == 4)
tcode = TCODE_WRITE_QUADLET_REQUEST;
else
tcode = TCODE_WRITE_BLOCK_REQUEST;
return send_request(handle, tcode,
node, addr, length, data, 0, NULL, tag);
}
static int
setup_lock(int extcode, quadlet_t data, quadlet_t arg, quadlet_t *buffer)
{
switch (extcode) {
case RAW1394_EXTCODE_FETCH_ADD:
case RAW1394_EXTCODE_LITTLE_ADD:
buffer[0] = data;
return sizeof buffer[0];
case RAW1394_EXTCODE_MASK_SWAP:
case RAW1394_EXTCODE_COMPARE_SWAP:
case RAW1394_EXTCODE_BOUNDED_ADD:
case RAW1394_EXTCODE_WRAP_ADD:
buffer[0] = arg;
buffer[1] = data;
return 2 * sizeof buffer[0];
default:
errno = EINVAL;
return -1;
}
}
static int
setup_lock64(int extcode, octlet_t data, octlet_t arg, octlet_t *buffer)
{
switch (extcode) {
case RAW1394_EXTCODE_FETCH_ADD:
case RAW1394_EXTCODE_LITTLE_ADD:
buffer[0] = data;
return sizeof buffer[0];
case RAW1394_EXTCODE_MASK_SWAP:
case RAW1394_EXTCODE_COMPARE_SWAP:
case RAW1394_EXTCODE_BOUNDED_ADD:
case RAW1394_EXTCODE_WRAP_ADD:
buffer[0] = arg;
buffer[1] = data;
return 2 * sizeof buffer[0];
default:
errno = EINVAL;
return -1;
}
}
int
fw_start_lock(fw_handle_t handle, nodeid_t node, nodeaddr_t addr,
unsigned int extcode, quadlet_t data, quadlet_t arg,
quadlet_t *result, unsigned long tag)
{
quadlet_t buffer[2];
int length;
length = setup_lock(extcode, data, arg, buffer);
if (length < 0)
return length;
return send_request(handle, 16 + extcode,
node, addr, length, buffer,
sizeof *result, result, tag);
}
int
fw_start_lock64(fw_handle_t handle, nodeid_t node, nodeaddr_t addr,
unsigned int extcode, octlet_t data, octlet_t arg,
octlet_t *result, unsigned long tag)
{
octlet_t buffer[2];
int length;
length = setup_lock64(extcode, data, arg, buffer);
if (length < 0)
return length;
return send_request(handle, 16 + extcode,
node, addr, length, buffer,
sizeof *result, result, tag);
}
int
fw_start_async_stream(fw_handle_t handle, unsigned int channel,
unsigned int tag, unsigned int sy,
unsigned int speed, size_t length, quadlet_t *data,
unsigned long rawtag)
{
nodeaddr_t addr = (tag & 0x3) << 14 |
(channel & 0x3f) << 8 |
(speed & 0x7) << 4 |
(sy & 0xf);
return send_request(handle, TCODE_STREAM_DATA,
0, addr, length, data, 0, NULL, rawtag);
}
int
fw_start_phy_packet_write(fw_handle_t handle, quadlet_t data, unsigned long tag)
{
#ifdef FW_CDEV_IOC_SEND_PHY_PACKET /* added in kernel 2.6.36 */
struct fw_cdev_send_phy_packet send_phy_packet;
struct request_closure *closure;
int retval;
if (handle->local_device == NULL) {
handle->err = -EPERM;
errno = EPERM;
return -1;
}
closure = malloc(sizeof *closure);
if (closure == NULL) {
handle->err = -RCODE_SEND_ERROR;
errno = fw_errcode_to_errno(handle->err);
return -1;
}
closure->data = NULL;
closure->tag = tag;
send_phy_packet.closure = ptr_to_u64(closure);
send_phy_packet.data[0] = be32_to_cpu(data);
send_phy_packet.data[1] = ~be32_to_cpu(data);
send_phy_packet.generation = handle->local_device->generation;
retval = ioctl(handle->local_device->fd, FW_CDEV_IOC_SEND_PHY_PACKET,
&send_phy_packet);
if (retval < 0)
free(closure);
return retval;
#else
errno = ENOSYS;
handle->err = -errno;
return -1;
#endif
}
int
fw_start_async_send(fw_handle_t handle,
size_t length, size_t header_length,
unsigned int expect_response,
quadlet_t *data, unsigned long rawtag)
{
/* FIXME: implement this? */
errno = ENOSYS;
return -1;
}
struct sync_data {
raw1394_errcode_t err;
int done;
};
static int
sync_callback(raw1394handle_t handle, void *data, raw1394_errcode_t err)
{
struct sync_data *sd = data;
sd->err = err;
sd->done = 1;
return 0;
}
static int
send_request_sync(raw1394handle_t handle, int tcode,
nodeid_t node, nodeaddr_t addr,
size_t in_length, void *in, size_t out_length, void *out)
{
fw_handle_t fwhandle = handle->mode.fw;
struct raw1394_reqhandle reqhandle;
struct sync_data sd = { 0, 0 };
int err;
reqhandle.callback = sync_callback;
reqhandle.data = &sd;
err = send_request(fwhandle, tcode, node, addr,
in_length, in, out_length, out,
(unsigned long) &reqhandle);
while (!sd.done) {
if (err < 0)
return err;
err = fw_loop_iterate(handle);
}
fwhandle->err = sd.err;
errno = fw_errcode_to_errno(sd.err);
return (errno ? -1 : 0);
}
int
fw_read(raw1394handle_t handle, nodeid_t node, nodeaddr_t addr,
size_t length, quadlet_t *buffer)
{
int tcode;
if (length == 4)
tcode = TCODE_READ_QUADLET_REQUEST;
else
tcode = TCODE_READ_BLOCK_REQUEST;
return send_request_sync(handle, tcode,
node, addr, 0, NULL, length, buffer);
}
int
fw_write(raw1394handle_t handle, nodeid_t node, nodeaddr_t addr,
size_t length, quadlet_t *data)
{
int tcode;
if (length == 4)
tcode = TCODE_WRITE_QUADLET_REQUEST;
else
tcode = TCODE_WRITE_BLOCK_REQUEST;
return send_request_sync(handle, tcode,
node, addr, length, data, 0, NULL);
}
int
fw_lock(raw1394handle_t handle, nodeid_t node, nodeaddr_t addr,
unsigned int extcode, quadlet_t data, quadlet_t arg,
quadlet_t *result)
{
quadlet_t buffer[2];
ssize_t length;
length = setup_lock(extcode, data, arg, buffer);
if (length < 0)
return length;
return send_request_sync(handle, 16 + extcode, node, addr,
(size_t) length, buffer,
sizeof *result, result);
}
int
fw_lock64(raw1394handle_t handle, nodeid_t node, nodeaddr_t addr,
unsigned int extcode, octlet_t data, octlet_t arg,
octlet_t *result)
{
octlet_t buffer[2];
ssize_t length;
length = setup_lock64(extcode, data, arg, buffer);
if (length < 0)
return length;
return send_request_sync(handle, 16 + extcode, node, addr,
(size_t) length, buffer,
sizeof *result, result);
}
int
fw_async_stream(raw1394handle_t handle, unsigned int channel,
unsigned int tag, unsigned int sy, unsigned int speed,
size_t length, quadlet_t *data)
{
nodeaddr_t addr = (tag & 0x3) << 14 |
(channel & 0x3f) << 8 |
(speed & 0x7) << 4 |
(sy & 0xf);
return send_request_sync(handle, TCODE_STREAM_DATA,
0, addr, length, data, 0, NULL);
}
int
fw_phy_packet_write(raw1394handle_t handle, quadlet_t data)
{
fw_handle_t fwhandle = handle->mode.fw;
struct raw1394_reqhandle reqhandle;
struct sync_data sd = { 0, 0 };
int err;
reqhandle.callback = sync_callback;
reqhandle.data = &sd;
err = fw_start_phy_packet_write(fwhandle, data,
(unsigned long) &reqhandle);
while (!sd.done) {
if (err < 0)
return err;
err = fw_loop_iterate(handle);
}
fwhandle->err = sd.err;
errno = fw_errcode_to_errno(sd.err);
return (errno ? -1 : 0);
}
int
fw_async_send(fw_handle_t handle,
size_t length, size_t header_length,
unsigned int expect_response,
quadlet_t *data)
{
/* FIXME: implement this? */
errno = ENOSYS;
return -1;
}
int
fw_start_fcp_listen(fw_handle_t handle)
{
struct fw_cdev_allocate request;
struct address_closure *closure;
closure = malloc(sizeof *closure);
if (closure == NULL) {
errno = ENOMEM;
return -1;
}
closure->callback = handle_fcp_request;
memset(&request, 0, sizeof(request));
request.offset = CSR_REGISTER_BASE + CSR_FCP_COMMAND;
request.length = CSR_FCP_END - CSR_FCP_COMMAND;
request.closure = ptr_to_u64(closure);
#if IMPLEMENTED_CDEV_ABI_VERSION >= 4
request.region_end = CSR_REGISTER_BASE + CSR_FCP_END;
#endif
if (ioctl(handle->ioctl_fd, FW_CDEV_IOC_ALLOCATE, &request) < 0)
return -1;
handle->fcp_allocation_handle = request.handle;
return 0;
}
int
fw_stop_fcp_listen(fw_handle_t handle)
{
struct fw_cdev_deallocate request;
request.handle = handle->fcp_allocation_handle;
return ioctl(handle->ioctl_fd, FW_CDEV_IOC_DEALLOCATE, &request);
}
int
fw_update_config_rom(fw_handle_t handle, const quadlet_t *new_rom,
size_t size, unsigned char rom_version)
{
errno = ENOSYS;
return -1;
}
int
fw_get_config_rom(fw_handle_t handle, quadlet_t *buffer,
size_t buffersize, size_t *rom_size,
unsigned char *rom_version)
{
struct fw_cdev_get_info get_info;
int err;
if (handle->local_device == NULL) {
errno = EPERM;
return -1;
}
memset(&get_info, 0, sizeof(get_info));
get_info.version = IMPLEMENTED_CDEV_ABI_VERSION;
get_info.rom = ptr_to_u64(buffer);
get_info.rom_length = buffersize;
get_info.bus_reset = 0;
err = ioctl(handle->local_device->fd, FW_CDEV_IOC_GET_INFO, &get_info);
if (err)
return err;
*rom_size = get_info.rom_length;
*rom_version = 0;
return 0;
}
#ifdef FW_CDEV_IOC_ALLOCATE_ISO_RESOURCE_ONCE /* added in kernel 2.6.30 */
static int
iso_resource_modify(raw1394handle_t handle, unsigned int bandwidth,
int channel, enum raw1394_modify_mode mode)
{
fw_handle_t fwhandle = handle->mode.fw;
struct fw_cdev_allocate_iso_resource resource;
struct fw_cdev_event_iso_resource event;
int ioctl_nr;
int err;
if (channel > 63) {
errno = EINVAL;
return -1;
}
event.closure = 0;
event.channel = -1;
event.bandwidth = 0;
resource.closure = ptr_to_u64(&event);
resource.channels = channel >= 0 ? 1ULL << channel : 0;
resource.bandwidth = bandwidth;
resource.handle = 0; /* unused */
ioctl_nr = mode == RAW1394_MODIFY_ALLOC ?
FW_CDEV_IOC_ALLOCATE_ISO_RESOURCE_ONCE :
FW_CDEV_IOC_DEALLOCATE_ISO_RESOURCE_ONCE;
err = ioctl(fwhandle->ioctl_fd, ioctl_nr, &resource);
while (err >= 0 && event.closure != resource.closure)
err = fw_loop_iterate(handle);
if (err < 0)
return err;
if ((channel >= 0 && event.channel < 0) ||
(bandwidth > 0 && event.bandwidth == 0)) {
errno = EIO;
return -1;
}
return 0;
}
static inline int abi_has_iso_resource_management(raw1394handle_t handle)
{
return handle->mode.fw->abi_version >= 2;
}
#else
static inline int
iso_resource_modify(raw1394handle_t handle, unsigned int bandwidth,
int channel, enum raw1394_modify_mode mode)
{
return -1;
}
static inline int abi_has_iso_resource_management(raw1394handle_t handle)
{
return 0;
}
#endif /* defined(FW_CDEV_IOC_ALLOCATE_ISO_RESOURCE_ONCE) */
int
fw_bandwidth_modify(raw1394handle_t handle, unsigned int bandwidth,
enum raw1394_modify_mode mode)
{
if (abi_has_iso_resource_management(handle))
return iso_resource_modify(handle, bandwidth, -1, mode);
else
return ieee1394_bandwidth_modify(handle, bandwidth, mode);
}
int
fw_channel_modify(raw1394handle_t handle, unsigned int channel,
enum raw1394_modify_mode mode)
{
if (abi_has_iso_resource_management(handle))
return iso_resource_modify(handle, 0, channel, mode);
else
return ieee1394_channel_modify(handle, channel, mode);
}