summaryrefslogtreecommitdiffstats
path: root/drivers/ieee1394/highlevel.c
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>2000-02-16 01:07:24 +0000
committerRalf Baechle <ralf@linux-mips.org>2000-02-16 01:07:24 +0000
commit95db6b748fc86297827fbd9c9ef174d491c9ad89 (patch)
tree27a92a942821cde1edda9a1b088718d436b3efe4 /drivers/ieee1394/highlevel.c
parent45b27b0a0652331d104c953a5b192d843fff88f8 (diff)
Merge with Linux 2.3.40.
Diffstat (limited to 'drivers/ieee1394/highlevel.c')
-rw-r--r--drivers/ieee1394/highlevel.c402
1 files changed, 402 insertions, 0 deletions
diff --git a/drivers/ieee1394/highlevel.c b/drivers/ieee1394/highlevel.c
new file mode 100644
index 000000000..3b4ac40f5
--- /dev/null
+++ b/drivers/ieee1394/highlevel.c
@@ -0,0 +1,402 @@
+/*
+ * IEEE 1394 for Linux
+ *
+ * Copyright (C) 1999 Andreas E. Bombe
+ */
+
+#include <linux/config.h>
+#include <linux/slab.h>
+
+#include "ieee1394.h"
+#include "ieee1394_types.h"
+#include "hosts.h"
+#include "ieee1394_core.h"
+#include "highlevel.h"
+
+
+LIST_HEAD(hl_drivers);
+rwlock_t hl_drivers_lock = RW_LOCK_UNLOCKED;
+
+LIST_HEAD(addr_space);
+rwlock_t addr_space_lock = RW_LOCK_UNLOCKED;
+
+/* addr_space list will have zero and max already included as bounds */
+static struct hpsb_address_ops dummy_ops = { NULL, NULL, NULL, NULL };
+static struct hpsb_address_serve dummy_zero_addr, dummy_max_addr;
+
+struct hpsb_highlevel *hpsb_register_highlevel(const char *name,
+ struct hpsb_highlevel_ops *ops)
+{
+ struct hpsb_highlevel *hl;
+
+ hl = (struct hpsb_highlevel *)kmalloc(sizeof(struct hpsb_highlevel),
+ GFP_KERNEL);
+ if (hl == NULL) {
+ return NULL;
+ }
+
+ INIT_LIST_HEAD(&hl->hl_list);
+ INIT_LIST_HEAD(&hl->addr_list);
+ hl->name = name;
+ hl->op = ops;
+
+ write_lock_irq(&hl_drivers_lock);
+ hl_all_hosts(hl, 1);
+ list_add_tail(&hl->hl_list, &hl_drivers);
+ write_unlock_irq(&hl_drivers_lock);
+
+ return hl;
+}
+
+void hpsb_unregister_highlevel(struct hpsb_highlevel *hl)
+{
+ struct list_head *entry;
+ struct hpsb_address_serve *as;
+
+ if (hl == NULL) {
+ return;
+ }
+
+ write_lock_irq(&addr_space_lock);
+ entry = hl->addr_list.next;
+
+ while (entry != &hl->addr_list) {
+ as = list_entry(entry, struct hpsb_address_serve, addr_list);
+ list_del(&as->as_list);
+ entry = entry->next;
+ kfree(as);
+ }
+ write_unlock_irq(&addr_space_lock);
+
+ write_lock_irq(&hl_drivers_lock);
+ list_del(&hl->hl_list);
+ hl_all_hosts(hl, 0);
+ write_unlock_irq(&hl_drivers_lock);
+
+ kfree(hl);
+}
+
+int hpsb_register_addrspace(struct hpsb_highlevel *hl,
+ struct hpsb_address_ops *ops, u64 start, u64 end)
+{
+ struct hpsb_address_serve *as;
+ struct list_head *entry;
+ int retval = 0;
+
+ if (((start|end) & 3) || (start >= end) || (end > 0x1000000000000ULL)) {
+ HPSB_ERR(__FUNCTION__ " called with invalid addresses");
+ return 0;
+ }
+
+ as = (struct hpsb_address_serve *)
+ kmalloc(sizeof(struct hpsb_address_serve), GFP_KERNEL);
+ if (as == NULL) {
+ return 0;
+ }
+
+ INIT_LIST_HEAD(&as->as_list);
+ INIT_LIST_HEAD(&as->addr_list);
+ as->op = ops;
+ as->start = start;
+ as->end = end;
+
+ write_lock_irq(&addr_space_lock);
+ entry = addr_space.next;
+
+ while (list_entry(entry, struct hpsb_address_serve, as_list)->end
+ <= start) {
+ if (list_entry(entry->next, struct hpsb_address_serve, as_list)
+ ->start >= end) {
+ list_add(&as->as_list, entry);
+ list_add_tail(&as->addr_list, &hl->addr_list);
+ retval = 1;
+ break;
+ }
+ entry = entry->next;
+ }
+ write_unlock_irq(&addr_space_lock);
+
+ if (retval == 0) {
+ kfree(as);
+ }
+
+ return retval;
+}
+
+
+void hpsb_listen_channel(struct hpsb_highlevel *hl, struct hpsb_host *host,
+ unsigned int channel)
+{
+ if (channel > 63) {
+ HPSB_ERR(__FUNCTION__ " called with invalid channel");
+ return;
+ }
+
+ if (host->iso_listen_count[channel]++ == 0) {
+ host->template->devctl(host, ISO_LISTEN_CHANNEL, channel);
+ }
+}
+
+void hpsb_unlisten_channel(struct hpsb_highlevel *hl, struct hpsb_host *host,
+ unsigned int channel)
+{
+ if (channel > 63) {
+ HPSB_ERR(__FUNCTION__ " called with invalid channel");
+ return;
+ }
+
+ if (--host->iso_listen_count[channel] == 0) {
+ host->template->devctl(host, ISO_UNLISTEN_CHANNEL, channel);
+ }
+}
+
+
+#define DEFINE_MULTIPLEXER(Function) \
+void highlevel_##Function(struct hpsb_host *host) \
+{ \
+ struct list_head *entry; \
+ void (*funcptr)(struct hpsb_host*); \
+ read_lock(&hl_drivers_lock); \
+ entry = hl_drivers.next; \
+ while (entry != &hl_drivers) { \
+ funcptr = list_entry(entry, struct hpsb_highlevel, hl_list) \
+ ->op->Function; \
+ if (funcptr) funcptr(host); \
+ entry = entry->next; \
+ } \
+ read_unlock(&hl_drivers_lock); \
+}
+
+DEFINE_MULTIPLEXER(add_host)
+DEFINE_MULTIPLEXER(remove_host)
+DEFINE_MULTIPLEXER(host_reset)
+#undef DEFINE_MULTIPLEXER
+
+void highlevel_iso_receive(struct hpsb_host *host, quadlet_t *data,
+ unsigned int length)
+{
+ struct list_head *entry;
+ struct hpsb_highlevel *hl;
+ int channel = (data[0] >> 8) & 0x3f;
+
+ read_lock(&hl_drivers_lock);
+ entry = hl_drivers.next;
+
+ while (entry != &hl_drivers) {
+ hl = list_entry(entry, struct hpsb_highlevel, hl_list);
+ if (hl->op->iso_receive) {
+ hl->op->iso_receive(host, channel, data, length);
+ }
+ entry = entry->next;
+ }
+ read_unlock(&hl_drivers_lock);
+}
+
+
+int highlevel_read(struct hpsb_host *host, quadlet_t *buffer, u64 addr,
+ unsigned int length)
+{
+ struct hpsb_address_serve *as;
+ struct list_head *entry;
+ unsigned int partlength;
+ int rcode = RCODE_ADDRESS_ERROR;
+
+ if ((addr | length) & 0x3) {
+ /* Addresses or lengths not a multiple of a quadlet pose a big
+ * problem on little endian machines because we always do this
+ * in arch endian and swapping would mess it all up. So we
+ * simply don't allow this at all. */
+ return RCODE_TYPE_ERROR;
+ }
+
+ read_lock(&addr_space_lock);
+
+ entry = addr_space.next;
+ as = list_entry(entry, struct hpsb_address_serve, as_list);
+
+ while (as->start <= addr) {
+ if (as->end > addr) {
+ partlength = MIN((unsigned int)(as->end - addr),
+ length);
+
+ if (as->op->read != NULL) {
+ rcode = as->op->read(host, buffer, addr,
+ partlength);
+ } else {
+ rcode = RCODE_TYPE_ERROR;
+ }
+
+ length -= partlength;
+ addr += partlength;
+
+ if ((rcode != RCODE_COMPLETE) || !length) {
+ break;
+ }
+ }
+
+ entry = entry->next;
+ as = list_entry(entry, struct hpsb_address_serve, as_list);
+ }
+
+ read_unlock(&addr_space_lock);
+
+ if (length && (rcode == RCODE_COMPLETE)) {
+ rcode = RCODE_ADDRESS_ERROR;
+ }
+
+ return rcode;
+}
+
+int highlevel_write(struct hpsb_host *host, quadlet_t *data, u64 addr,
+ unsigned int length)
+{
+ struct hpsb_address_serve *as;
+ struct list_head *entry;
+ unsigned int partlength;
+ int rcode = RCODE_ADDRESS_ERROR;
+
+ if ((addr | length) & 0x3) {
+ return RCODE_TYPE_ERROR;
+ }
+
+ read_lock(&addr_space_lock);
+
+ entry = addr_space.next;
+ as = list_entry(entry, struct hpsb_address_serve, as_list);
+
+ while (as->start <= addr) {
+ if (as->end > addr) {
+ partlength = MIN((unsigned int)(as->end - addr),
+ length);
+
+ if (as->op->write != NULL) {
+ rcode = as->op->write(host, data, addr,
+ partlength);
+ } else {
+ rcode = RCODE_TYPE_ERROR;
+ }
+
+ length -= partlength;
+ addr += partlength;
+
+ if ((rcode != RCODE_COMPLETE) || !length) {
+ break;
+ }
+ }
+
+ entry = entry->next;
+ as = list_entry(entry, struct hpsb_address_serve, as_list);
+ }
+
+ read_unlock(&addr_space_lock);
+
+ if (length && (rcode == RCODE_COMPLETE)) {
+ rcode = RCODE_ADDRESS_ERROR;
+ }
+
+ return rcode;
+}
+
+
+int highlevel_lock(struct hpsb_host *host, quadlet_t *store, u64 addr,
+ quadlet_t data, quadlet_t arg, int ext_tcode)
+{
+ struct hpsb_address_serve *as;
+ struct list_head *entry;
+ int rcode = RCODE_ADDRESS_ERROR;
+
+ read_lock(&addr_space_lock);
+
+ entry = addr_space.next;
+ as = list_entry(entry, struct hpsb_address_serve, as_list);
+
+ while (as->start <= addr) {
+ if (as->end > addr) {
+ if (as->op->lock != NULL) {
+ rcode = as->op->lock(host, store, addr, data,
+ arg, ext_tcode);
+ } else {
+ rcode = RCODE_TYPE_ERROR;
+ }
+
+ break;
+ }
+
+ entry = entry->next;
+ as = list_entry(entry, struct hpsb_address_serve, as_list);
+ }
+
+ read_unlock(&addr_space_lock);
+
+ return rcode;
+}
+
+int highlevel_lock64(struct hpsb_host *host, octlet_t *store, u64 addr,
+ octlet_t data, octlet_t arg, int ext_tcode)
+{
+ struct hpsb_address_serve *as;
+ struct list_head *entry;
+ int rcode = RCODE_ADDRESS_ERROR;
+
+ read_lock(&addr_space_lock);
+
+ entry = addr_space.next;
+ as = list_entry(entry, struct hpsb_address_serve, as_list);
+
+ while (as->start <= addr) {
+ if (as->end > addr) {
+ if (as->op->lock64 != NULL) {
+ rcode = as->op->lock64(host, store, addr, data,
+ arg, ext_tcode);
+ } else {
+ rcode = RCODE_TYPE_ERROR;
+ }
+
+ break;
+ }
+
+ entry = entry->next;
+ as = list_entry(entry, struct hpsb_address_serve, as_list);
+ }
+
+ read_unlock(&addr_space_lock);
+
+ return rcode;
+}
+
+
+
+#ifndef MODULE
+
+void register_builtin_highlevels(void)
+{
+#ifdef CONFIG_IEEE1394_RAWIO
+ {
+ int init_raw1394(void);
+ init_raw1394();
+ }
+#endif
+}
+
+#endif /* !MODULE */
+
+
+void init_hpsb_highlevel(void)
+{
+ INIT_LIST_HEAD(&dummy_zero_addr.as_list);
+ INIT_LIST_HEAD(&dummy_zero_addr.addr_list);
+ INIT_LIST_HEAD(&dummy_max_addr.as_list);
+ INIT_LIST_HEAD(&dummy_max_addr.addr_list);
+
+ dummy_zero_addr.op = dummy_max_addr.op = &dummy_ops;
+
+ dummy_zero_addr.start = dummy_zero_addr.end = 0;
+ dummy_max_addr.start = dummy_max_addr.end = ((u64) 1) << 48;
+
+ list_add_tail(&dummy_zero_addr.as_list, &addr_space);
+ list_add_tail(&dummy_max_addr.as_list, &addr_space);
+
+#ifndef MODULE
+ register_builtin_highlevels();
+#endif
+}