diff options
Diffstat (limited to 'drivers/ieee1394')
43 files changed, 29531 insertions, 0 deletions
diff --git a/drivers/ieee1394/Kconfig b/drivers/ieee1394/Kconfig new file mode 100644 index 0000000..95f45f9 --- /dev/null +++ b/drivers/ieee1394/Kconfig @@ -0,0 +1,164 @@ +menu "IEEE 1394 (FireWire) support" + depends on PCI || BROKEN + +source "drivers/firewire/Kconfig" + +config IEEE1394 + tristate "Stable FireWire stack" + depends on PCI || BROKEN + help + IEEE 1394 describes a high performance serial bus, which is also + known as FireWire(tm) or i.Link(tm) and is used for connecting all + sorts of devices (most notably digital video cameras) to your + computer. + + If you have FireWire hardware and want to use it, say Y here. This + is the core support only, you will also need to select a driver for + your IEEE 1394 adapter. + + To compile this driver as a module, say M here: the + module will be called ieee1394. + +config IEEE1394_OHCI1394 + tristate "OHCI-1394 controllers" + depends on PCI && IEEE1394 + help + Enable this driver if you have an IEEE 1394 controller based on the + OHCI-1394 specification. The current driver is only tested with OHCI + chipsets made by Texas Instruments and NEC. Most third-party vendors + use one of these chipsets. It should work with any OHCI-1394 + compliant card, however. + + To compile this driver as a module, say M here: the + module will be called ohci1394. + + NOTE: + + You should only build either ohci1394 or the new firewire-ohci driver, + but not both. If you nevertheless want to install both, you should + configure them only as modules and blacklist the driver(s) which you + don't want to have auto-loaded. Add either + + blacklist firewire-ohci + or + blacklist ohci1394 + blacklist video1394 + blacklist dv1394 + + to /etc/modprobe.conf or /etc/modprobe.d/* and update modprobe.conf + depending on your distribution. The latter two modules should be + blacklisted together with ohci1394 because they depend on ohci1394. + + If you have an old modprobe which doesn't implement the blacklist + directive, use "install modulename /bin/true" for the modules to be + blacklisted. + +comment "PCILynx controller requires I2C" + depends on IEEE1394 && I2C=n + +config IEEE1394_PCILYNX + tristate "PCILynx controller" + depends on PCI && IEEE1394 && I2C + select I2C_ALGOBIT + help + Say Y here if you have an IEEE-1394 controller with the Texas + Instruments PCILynx chip. Note: this driver is written for revision + 2 of this chip and may not work with revision 0. + + To compile this driver as a module, say M here: the + module will be called pcilynx. + + Only some old and now very rare PCI and CardBus cards and + PowerMacs G3 B&W contain the PCILynx controller. Therefore + almost everybody can say N here. + +comment "SBP-2 support (for storage devices) requires SCSI" + depends on IEEE1394 && SCSI=n + +config IEEE1394_SBP2 + tristate "Storage devices (SBP-2 protocol)" + depends on IEEE1394 && SCSI + help + This option enables you to use SBP-2 devices connected to an IEEE + 1394 bus. SBP-2 devices include storage devices like harddisks and + DVD drives, also some other FireWire devices like scanners. + + You should also enable support for disks, CD-ROMs, etc. in the SCSI + configuration section. + +config IEEE1394_SBP2_PHYS_DMA + bool "Enable replacement for physical DMA in SBP2" + depends on IEEE1394_SBP2 && VIRT_TO_BUS && EXPERIMENTAL + help + This builds sbp2 for use with non-OHCI host adapters which do not + support physical DMA or for when ohci1394 is run with phys_dma=0. + Physical DMA is data movement without assistance of the drivers' + interrupt handlers. This option includes the interrupt handlers + that are required in absence of this hardware feature. + + This option is buggy and currently broken on some architectures. + If unsure, say N. + +config IEEE1394_ETH1394_ROM_ENTRY + depends on IEEE1394 + bool + default n + +config IEEE1394_ETH1394 + tristate "IP over 1394" + depends on IEEE1394 && EXPERIMENTAL && INET + select IEEE1394_ETH1394_ROM_ENTRY + help + This driver implements a functional majority of RFC 2734: IPv4 over + 1394. It will provide IP connectivity with implementations of RFC + 2734 found on other operating systems. It will not communicate with + older versions of this driver found in stock kernels prior to 2.6.3. + This driver is still considered experimental. It does not yet support + MCAP, therefore multicast support is significantly limited. + + The module is called eth1394 although it does not emulate Ethernet. + +config IEEE1394_RAWIO + tristate "raw1394 userspace interface" + depends on IEEE1394 + help + This option adds support for the raw1394 device file which enables + direct communication of user programs with IEEE 1394 devices + (isochronous and asynchronous). Almost all application programs + which access FireWire require this option. + + To compile this driver as a module, say M here: the module will be + called raw1394. + +config IEEE1394_VIDEO1394 + tristate "video1394 userspace interface" + depends on IEEE1394 && IEEE1394_OHCI1394 + help + This option adds support for the video1394 device files which enable + isochronous communication of user programs with IEEE 1394 devices, + especially video capture or export. This interface is used by all + libdc1394 based programs and by several other programs, in addition to + the raw1394 interface. It is generally not required for DV capture. + + To compile this driver as a module, say M here: the module will be + called video1394. + +config IEEE1394_DV1394 + tristate "dv1394 userspace interface (deprecated)" + depends on IEEE1394 && IEEE1394_OHCI1394 + help + The dv1394 driver is unsupported and may be removed from Linux in a + future release. Its functionality is now provided by raw1394 together + with libraries such as libiec61883. + +config IEEE1394_VERBOSEDEBUG + bool "Excessive debugging output" + depends on IEEE1394 + help + If you say Y here, you will get very verbose debugging logs from the + ieee1394 drivers, including sent and received packet headers. This + will quickly result in large amounts of data sent to the system log. + + Say Y if you really need the debugging output. Everyone else says N. + +endmenu diff --git a/drivers/ieee1394/Makefile b/drivers/ieee1394/Makefile new file mode 100644 index 0000000..1f8153b --- /dev/null +++ b/drivers/ieee1394/Makefile @@ -0,0 +1,18 @@ +# +# Makefile for the Linux IEEE 1394 implementation +# + +ieee1394-objs := ieee1394_core.o ieee1394_transactions.o hosts.o \ + highlevel.o csr.o nodemgr.o dma.o iso.o \ + csr1212.o config_roms.o + +obj-$(CONFIG_IEEE1394) += ieee1394.o +obj-$(CONFIG_IEEE1394_PCILYNX) += pcilynx.o +obj-$(CONFIG_IEEE1394_OHCI1394) += ohci1394.o +obj-$(CONFIG_IEEE1394_VIDEO1394) += video1394.o +obj-$(CONFIG_IEEE1394_RAWIO) += raw1394.o +obj-$(CONFIG_IEEE1394_SBP2) += sbp2.o +obj-$(CONFIG_IEEE1394_DV1394) += dv1394.o +obj-$(CONFIG_IEEE1394_ETH1394) += eth1394.o + +obj-$(CONFIG_PROVIDE_OHCI1394_DMA_INIT) += init_ohci1394_dma.o diff --git a/drivers/ieee1394/config_roms.c b/drivers/ieee1394/config_roms.c new file mode 100644 index 0000000..1b98120 --- /dev/null +++ b/drivers/ieee1394/config_roms.c @@ -0,0 +1,194 @@ +/* + * IEEE 1394 for Linux + * + * ConfigROM entries + * + * Copyright (C) 2004 Ben Collins + * + * This code is licensed under the GPL. See the file COPYING in the root + * directory of the kernel sources for details. + */ + +#include <linux/types.h> + +#include "csr1212.h" +#include "ieee1394.h" +#include "ieee1394_types.h" +#include "hosts.h" +#include "ieee1394_core.h" +#include "highlevel.h" +#include "csr.h" +#include "config_roms.h" + +struct hpsb_config_rom_entry { + const char *name; + + /* Base initialization, called at module load */ + int (*init)(void); + + /* Cleanup called at module exit */ + void (*cleanup)(void); + + /* The flag added to host->config_roms */ + unsigned int flag; +}; + +/* The default host entry. This must succeed. */ +int hpsb_default_host_entry(struct hpsb_host *host) +{ + struct csr1212_keyval *root; + struct csr1212_keyval *vend_id = NULL; + struct csr1212_keyval *text = NULL; + char csr_name[128]; + int ret; + + sprintf(csr_name, "Linux - %s", host->driver->name); + root = host->csr.rom->root_kv; + + vend_id = csr1212_new_immediate(CSR1212_KV_ID_VENDOR, host->csr.guid_hi >> 8); + text = csr1212_new_string_descriptor_leaf(csr_name); + + if (!vend_id || !text) { + if (vend_id) + csr1212_release_keyval(vend_id); + if (text) + csr1212_release_keyval(text); + csr1212_destroy_csr(host->csr.rom); + return -ENOMEM; + } + + csr1212_associate_keyval(vend_id, text); + csr1212_release_keyval(text); + ret = csr1212_attach_keyval_to_directory(root, vend_id); + csr1212_release_keyval(vend_id); + if (ret != CSR1212_SUCCESS) { + csr1212_destroy_csr(host->csr.rom); + return -ENOMEM; + } + + host->update_config_rom = 1; + + return 0; +} + + +#ifdef CONFIG_IEEE1394_ETH1394_ROM_ENTRY +#include "eth1394.h" + +static struct csr1212_keyval *ip1394_ud; + +static int config_rom_ip1394_init(void) +{ + struct csr1212_keyval *spec_id = NULL; + struct csr1212_keyval *spec_desc = NULL; + struct csr1212_keyval *ver = NULL; + struct csr1212_keyval *ver_desc = NULL; + int ret = -ENOMEM; + + ip1394_ud = csr1212_new_directory(CSR1212_KV_ID_UNIT); + + spec_id = csr1212_new_immediate(CSR1212_KV_ID_SPECIFIER_ID, + ETHER1394_GASP_SPECIFIER_ID); + spec_desc = csr1212_new_string_descriptor_leaf("IANA"); + ver = csr1212_new_immediate(CSR1212_KV_ID_VERSION, + ETHER1394_GASP_VERSION); + ver_desc = csr1212_new_string_descriptor_leaf("IPv4"); + + if (!ip1394_ud || !spec_id || !spec_desc || !ver || !ver_desc) + goto ip1394_fail; + + csr1212_associate_keyval(spec_id, spec_desc); + csr1212_associate_keyval(ver, ver_desc); + if (csr1212_attach_keyval_to_directory(ip1394_ud, spec_id) + == CSR1212_SUCCESS && + csr1212_attach_keyval_to_directory(ip1394_ud, ver) + == CSR1212_SUCCESS) + ret = 0; + +ip1394_fail: + if (ret && ip1394_ud) { + csr1212_release_keyval(ip1394_ud); + ip1394_ud = NULL; + } + + if (spec_id) + csr1212_release_keyval(spec_id); + if (spec_desc) + csr1212_release_keyval(spec_desc); + if (ver) + csr1212_release_keyval(ver); + if (ver_desc) + csr1212_release_keyval(ver_desc); + + return ret; +} + +static void config_rom_ip1394_cleanup(void) +{ + if (ip1394_ud) { + csr1212_release_keyval(ip1394_ud); + ip1394_ud = NULL; + } +} + +int hpsb_config_rom_ip1394_add(struct hpsb_host *host) +{ + if (!ip1394_ud) + return -ENODEV; + + if (csr1212_attach_keyval_to_directory(host->csr.rom->root_kv, + ip1394_ud) != CSR1212_SUCCESS) + return -ENOMEM; + + host->config_roms |= HPSB_CONFIG_ROM_ENTRY_IP1394; + host->update_config_rom = 1; + return 0; +} +EXPORT_SYMBOL_GPL(hpsb_config_rom_ip1394_add); + +void hpsb_config_rom_ip1394_remove(struct hpsb_host *host) +{ + csr1212_detach_keyval_from_directory(host->csr.rom->root_kv, ip1394_ud); + host->config_roms &= ~HPSB_CONFIG_ROM_ENTRY_IP1394; + host->update_config_rom = 1; +} +EXPORT_SYMBOL_GPL(hpsb_config_rom_ip1394_remove); + +static struct hpsb_config_rom_entry ip1394_entry = { + .name = "ip1394", + .init = config_rom_ip1394_init, + .cleanup = config_rom_ip1394_cleanup, + .flag = HPSB_CONFIG_ROM_ENTRY_IP1394, +}; + +#endif /* CONFIG_IEEE1394_ETH1394_ROM_ENTRY */ + +static struct hpsb_config_rom_entry *const config_rom_entries[] = { +#ifdef CONFIG_IEEE1394_ETH1394_ROM_ENTRY + &ip1394_entry, +#endif +}; + +/* Initialize all config roms */ +int hpsb_init_config_roms(void) +{ + int i, error = 0; + + for (i = 0; i < ARRAY_SIZE(config_rom_entries); i++) + if (config_rom_entries[i]->init()) { + HPSB_ERR("Failed to initialize config rom entry `%s'", + config_rom_entries[i]->name); + error = -1; + } + + return error; +} + +/* Cleanup all config roms */ +void hpsb_cleanup_config_roms(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(config_rom_entries); i++) + config_rom_entries[i]->cleanup(); +} diff --git a/drivers/ieee1394/config_roms.h b/drivers/ieee1394/config_roms.h new file mode 100644 index 0000000..1f5cd1f --- /dev/null +++ b/drivers/ieee1394/config_roms.h @@ -0,0 +1,19 @@ +#ifndef _IEEE1394_CONFIG_ROMS_H +#define _IEEE1394_CONFIG_ROMS_H + +struct hpsb_host; + +int hpsb_default_host_entry(struct hpsb_host *host); +int hpsb_init_config_roms(void); +void hpsb_cleanup_config_roms(void); + +/* List of flags to check if a host contains a certain extra config rom + * entry. Available in the host->config_roms member. */ +#define HPSB_CONFIG_ROM_ENTRY_IP1394 0x00000001 + +#ifdef CONFIG_IEEE1394_ETH1394_ROM_ENTRY +int hpsb_config_rom_ip1394_add(struct hpsb_host *host); +void hpsb_config_rom_ip1394_remove(struct hpsb_host *host); +#endif + +#endif /* _IEEE1394_CONFIG_ROMS_H */ diff --git a/drivers/ieee1394/csr.c b/drivers/ieee1394/csr.c new file mode 100644 index 0000000..c90be40 --- /dev/null +++ b/drivers/ieee1394/csr.c @@ -0,0 +1,843 @@ +/* + * IEEE 1394 for Linux + * + * CSR implementation, iso/bus manager implementation. + * + * Copyright (C) 1999 Andreas E. Bombe + * 2002 Manfred Weihs <weihs@ict.tuwien.ac.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 + * + */ + +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/param.h> +#include <linux/spinlock.h> +#include <linux/string.h> + +#include "csr1212.h" +#include "ieee1394_types.h" +#include "hosts.h" +#include "ieee1394.h" +#include "highlevel.h" +#include "ieee1394_core.h" + +/* Module Parameters */ +/* this module parameter can be used to disable mapping of the FCP registers */ + +static int fcp = 1; +module_param(fcp, int, 0444); +MODULE_PARM_DESC(fcp, "Map FCP registers (default = 1, disable = 0)."); + +static struct csr1212_keyval *node_cap = NULL; + +static void add_host(struct hpsb_host *host); +static void remove_host(struct hpsb_host *host); +static void host_reset(struct hpsb_host *host); +static int read_maps(struct hpsb_host *host, int nodeid, quadlet_t *buffer, + u64 addr, size_t length, u16 fl); +static int write_fcp(struct hpsb_host *host, int nodeid, int dest, + quadlet_t *data, u64 addr, size_t length, u16 flags); +static int read_regs(struct hpsb_host *host, int nodeid, quadlet_t *buf, + u64 addr, size_t length, u16 flags); +static int write_regs(struct hpsb_host *host, int nodeid, int destid, + quadlet_t *data, u64 addr, size_t length, u16 flags); +static int lock_regs(struct hpsb_host *host, int nodeid, quadlet_t *store, + u64 addr, quadlet_t data, quadlet_t arg, int extcode, u16 fl); +static int lock64_regs(struct hpsb_host *host, int nodeid, octlet_t * store, + u64 addr, octlet_t data, octlet_t arg, int extcode, u16 fl); +static int read_config_rom(struct hpsb_host *host, int nodeid, quadlet_t *buffer, + u64 addr, size_t length, u16 fl); +static u64 allocate_addr_range(u64 size, u32 alignment, void *__host); +static void release_addr_range(u64 addr, void *__host); + +static struct hpsb_highlevel csr_highlevel = { + .name = "standard registers", + .add_host = add_host, + .remove_host = remove_host, + .host_reset = host_reset, +}; + +static struct hpsb_address_ops map_ops = { + .read = read_maps, +}; + +static struct hpsb_address_ops fcp_ops = { + .write = write_fcp, +}; + +static struct hpsb_address_ops reg_ops = { + .read = read_regs, + .write = write_regs, + .lock = lock_regs, + .lock64 = lock64_regs, +}; + +static struct hpsb_address_ops config_rom_ops = { + .read = read_config_rom, +}; + +struct csr1212_bus_ops csr_bus_ops = { + .allocate_addr_range = allocate_addr_range, + .release_addr = release_addr_range, +}; + + +static u16 csr_crc16(unsigned *data, int length) +{ + int check=0, i; + int shift, sum, next=0; + + for (i = length; i; i--) { + for (next = check, shift = 28; shift >= 0; shift -= 4 ) { + sum = ((next >> 12) ^ (be32_to_cpu(*data) >> shift)) & 0xf; + next = (next << 4) ^ (sum << 12) ^ (sum << 5) ^ (sum); + } + check = next & 0xffff; + data++; + } + + return check; +} + +static void host_reset(struct hpsb_host *host) +{ + host->csr.state &= 0x300; + + host->csr.bus_manager_id = 0x3f; + host->csr.bandwidth_available = 4915; + host->csr.channels_available_hi = 0xfffffffe; /* pre-alloc ch 31 per 1394a-2000 */ + host->csr.channels_available_lo = ~0; + host->csr.broadcast_channel = 0x80000000 | 31; + + if (host->is_irm) { + if (host->driver->hw_csr_reg) { + host->driver->hw_csr_reg(host, 2, 0xfffffffe, ~0); + } + } + + host->csr.node_ids = host->node_id << 16; + + if (!host->is_root) { + /* clear cmstr bit */ + host->csr.state &= ~0x100; + } + + be32_add_cpu(&host->csr.topology_map[1], 1); + host->csr.topology_map[2] = cpu_to_be32(host->node_count << 16 + | host->selfid_count); + host->csr.topology_map[0] = + cpu_to_be32((host->selfid_count + 2) << 16 + | csr_crc16(host->csr.topology_map + 1, + host->selfid_count + 2)); + + be32_add_cpu(&host->csr.speed_map[1], 1); + host->csr.speed_map[0] = cpu_to_be32(0x3f1 << 16 + | csr_crc16(host->csr.speed_map+1, + 0x3f1)); +} + +/* + * HI == seconds (bits 0:2) + * LO == fractions of a second in units of 125usec (bits 19:31) + * + * Convert SPLIT_TIMEOUT to jiffies. + * The default and minimum as per 1394a-2000 clause 8.3.2.2.6 is 100ms. + */ +static inline void calculate_expire(struct csr_control *csr) +{ + unsigned int usecs = (csr->split_timeout_hi & 7) * 1000000 + + (csr->split_timeout_lo >> 19) * 125; + + csr->expire = usecs_to_jiffies(usecs > 100000 ? usecs : 100000); + HPSB_VERBOSE("CSR: setting expire to %lu, HZ=%u", csr->expire, HZ); +} + + +static void add_host(struct hpsb_host *host) +{ + struct csr1212_keyval *root; + quadlet_t bus_info[CSR_BUS_INFO_SIZE]; + + hpsb_register_addrspace(&csr_highlevel, host, ®_ops, + CSR_REGISTER_BASE, + CSR_REGISTER_BASE + CSR_CONFIG_ROM); + hpsb_register_addrspace(&csr_highlevel, host, &config_rom_ops, + CSR_REGISTER_BASE + CSR_CONFIG_ROM, + CSR_REGISTER_BASE + CSR_CONFIG_ROM_END); + if (fcp) { + hpsb_register_addrspace(&csr_highlevel, host, &fcp_ops, + CSR_REGISTER_BASE + CSR_FCP_COMMAND, + CSR_REGISTER_BASE + CSR_FCP_END); + } + hpsb_register_addrspace(&csr_highlevel, host, &map_ops, + CSR_REGISTER_BASE + CSR_TOPOLOGY_MAP, + CSR_REGISTER_BASE + CSR_TOPOLOGY_MAP_END); + hpsb_register_addrspace(&csr_highlevel, host, &map_ops, + CSR_REGISTER_BASE + CSR_SPEED_MAP, + CSR_REGISTER_BASE + CSR_SPEED_MAP_END); + + spin_lock_init(&host->csr.lock); + + host->csr.state = 0; + host->csr.node_ids = 0; + host->csr.split_timeout_hi = 0; + host->csr.split_timeout_lo = 800 << 19; + calculate_expire(&host->csr); + host->csr.cycle_time = 0; + host->csr.bus_time = 0; + host->csr.bus_manager_id = 0x3f; + host->csr.bandwidth_available = 4915; + host->csr.channels_available_hi = 0xfffffffe; /* pre-alloc ch 31 per 1394a-2000 */ + host->csr.channels_available_lo = ~0; + host->csr.broadcast_channel = 0x80000000 | 31; + + if (host->is_irm) { + if (host->driver->hw_csr_reg) { + host->driver->hw_csr_reg(host, 2, 0xfffffffe, ~0); + } + } + + if (host->csr.max_rec >= 9) + host->csr.max_rom = 2; + else if (host->csr.max_rec >= 5) + host->csr.max_rom = 1; + else + host->csr.max_rom = 0; + + host->csr.generation = 2; + + bus_info[1] = __constant_cpu_to_be32(0x31333934); + bus_info[2] = cpu_to_be32((hpsb_disable_irm ? 0 : 1 << CSR_IRMC_SHIFT) | + (1 << CSR_CMC_SHIFT) | + (1 << CSR_ISC_SHIFT) | + (0 << CSR_BMC_SHIFT) | + (0 << CSR_PMC_SHIFT) | + (host->csr.cyc_clk_acc << CSR_CYC_CLK_ACC_SHIFT) | + (host->csr.max_rec << CSR_MAX_REC_SHIFT) | + (host->csr.max_rom << CSR_MAX_ROM_SHIFT) | + (host->csr.generation << CSR_GENERATION_SHIFT) | + host->csr.lnk_spd); + + bus_info[3] = cpu_to_be32(host->csr.guid_hi); + bus_info[4] = cpu_to_be32(host->csr.guid_lo); + + /* The hardware copy of the bus info block will be set later when a + * bus reset is issued. */ + + csr1212_init_local_csr(host->csr.rom, bus_info, host->csr.max_rom); + + root = host->csr.rom->root_kv; + + if(csr1212_attach_keyval_to_directory(root, node_cap) != CSR1212_SUCCESS) { + HPSB_ERR("Failed to attach Node Capabilities to root directory"); + } + + host->update_config_rom = 1; +} + +static void remove_host(struct hpsb_host *host) +{ + quadlet_t bus_info[CSR_BUS_INFO_SIZE]; + + bus_info[1] = __constant_cpu_to_be32(0x31333934); + bus_info[2] = cpu_to_be32((0 << CSR_IRMC_SHIFT) | + (0 << CSR_CMC_SHIFT) | + (0 << CSR_ISC_SHIFT) | + (0 << CSR_BMC_SHIFT) | + (0 << CSR_PMC_SHIFT) | + (host->csr.cyc_clk_acc << CSR_CYC_CLK_ACC_SHIFT) | + (host->csr.max_rec << CSR_MAX_REC_SHIFT) | + (0 << CSR_MAX_ROM_SHIFT) | + (0 << CSR_GENERATION_SHIFT) | + host->csr.lnk_spd); + + bus_info[3] = cpu_to_be32(host->csr.guid_hi); + bus_info[4] = cpu_to_be32(host->csr.guid_lo); + + csr1212_detach_keyval_from_directory(host->csr.rom->root_kv, node_cap); + + csr1212_init_local_csr(host->csr.rom, bus_info, 0); + host->update_config_rom = 1; +} + + +int hpsb_update_config_rom(struct hpsb_host *host, const quadlet_t *new_rom, + size_t buffersize, unsigned char rom_version) +{ + unsigned long flags; + int ret; + + HPSB_NOTICE("hpsb_update_config_rom() is deprecated"); + + spin_lock_irqsave(&host->csr.lock, flags); + if (rom_version != host->csr.generation) + ret = -1; + else if (buffersize > host->csr.rom->cache_head->size) + ret = -2; + else { + /* Just overwrite the generated ConfigROM image with new data, + * it can be regenerated later. */ + memcpy(host->csr.rom->cache_head->data, new_rom, buffersize); + host->csr.rom->cache_head->len = buffersize; + + if (host->driver->set_hw_config_rom) + host->driver->set_hw_config_rom(host, host->csr.rom->bus_info_data); + /* Increment the generation number to keep some sort of sync + * with the newer ConfigROM manipulation method. */ + host->csr.generation++; + if (host->csr.generation > 0xf || host->csr.generation < 2) + host->csr.generation = 2; + ret=0; + } + spin_unlock_irqrestore(&host->csr.lock, flags); + return ret; +} + + +/* Read topology / speed maps and configuration ROM */ +static int read_maps(struct hpsb_host *host, int nodeid, quadlet_t *buffer, + u64 addr, size_t length, u16 fl) +{ + unsigned long flags; + int csraddr = addr - CSR_REGISTER_BASE; + const char *src; + + spin_lock_irqsave(&host->csr.lock, flags); + + if (csraddr < CSR_SPEED_MAP) { + src = ((char *)host->csr.topology_map) + csraddr + - CSR_TOPOLOGY_MAP; + } else { + src = ((char *)host->csr.speed_map) + csraddr - CSR_SPEED_MAP; + } + + memcpy(buffer, src, length); + spin_unlock_irqrestore(&host->csr.lock, flags); + return RCODE_COMPLETE; +} + + +#define out if (--length == 0) break + +static int read_regs(struct hpsb_host *host, int nodeid, quadlet_t *buf, + u64 addr, size_t length, u16 flags) +{ + int csraddr = addr - CSR_REGISTER_BASE; + int oldcycle; + quadlet_t ret; + + if ((csraddr | length) & 0x3) + return RCODE_TYPE_ERROR; + + length /= 4; + + switch (csraddr) { + case CSR_STATE_CLEAR: + *(buf++) = cpu_to_be32(host->csr.state); + out; + case CSR_STATE_SET: + *(buf++) = cpu_to_be32(host->csr.state); + out; + case CSR_NODE_IDS: + *(buf++) = cpu_to_be32(host->csr.node_ids); + out; + + case CSR_RESET_START: + return RCODE_TYPE_ERROR; + + /* address gap - handled by default below */ + + case CSR_SPLIT_TIMEOUT_HI: + *(buf++) = cpu_to_be32(host->csr.split_timeout_hi); + out; + case CSR_SPLIT_TIMEOUT_LO: + *(buf++) = cpu_to_be32(host->csr.split_timeout_lo); + out; + + /* address gap */ + return RCODE_ADDRESS_ERROR; + + case CSR_CYCLE_TIME: + oldcycle = host->csr.cycle_time; + host->csr.cycle_time = + host->driver->devctl(host, GET_CYCLE_COUNTER, 0); + + if (oldcycle > host->csr.cycle_time) { + /* cycle time wrapped around */ + host->csr.bus_time += 1 << 7; + } + *(buf++) = cpu_to_be32(host->csr.cycle_time); + out; + case CSR_BUS_TIME: + oldcycle = host->csr.cycle_time; + host->csr.cycle_time = + host->driver->devctl(host, GET_CYCLE_COUNTER, 0); + + if (oldcycle > host->csr.cycle_time) { + /* cycle time wrapped around */ + host->csr.bus_time += (1 << 7); + } + *(buf++) = cpu_to_be32(host->csr.bus_time + | (host->csr.cycle_time >> 25)); + out; + + /* address gap */ + return RCODE_ADDRESS_ERROR; + + case CSR_BUSY_TIMEOUT: + /* not yet implemented */ + return RCODE_ADDRESS_ERROR; + + case CSR_BUS_MANAGER_ID: + if (host->driver->hw_csr_reg) + ret = host->driver->hw_csr_reg(host, 0, 0, 0); + else + ret = host->csr.bus_manager_id; + + *(buf++) = cpu_to_be32(ret); + out; + case CSR_BANDWIDTH_AVAILABLE: + if (host->driver->hw_csr_reg) + ret = host->driver->hw_csr_reg(host, 1, 0, 0); + else + ret = host->csr.bandwidth_available; + + *(buf++) = cpu_to_be32(ret); + out; + case CSR_CHANNELS_AVAILABLE_HI: + if (host->driver->hw_csr_reg) + ret = host->driver->hw_csr_reg(host, 2, 0, 0); + else + ret = host->csr.channels_available_hi; + + *(buf++) = cpu_to_be32(ret); + out; + case CSR_CHANNELS_AVAILABLE_LO: + if (host->driver->hw_csr_reg) + ret = host->driver->hw_csr_reg(host, 3, 0, 0); + else + ret = host->csr.channels_available_lo; + + *(buf++) = cpu_to_be32(ret); + out; + + case CSR_BROADCAST_CHANNEL: + *(buf++) = cpu_to_be32(host->csr.broadcast_channel); + out; + + /* address gap to end - fall through to default */ + default: + return RCODE_ADDRESS_ERROR; + } + + return RCODE_COMPLETE; +} + +static int write_regs(struct hpsb_host *host, int nodeid, int destid, + quadlet_t *data, u64 addr, size_t length, u16 flags) +{ + int csraddr = addr - CSR_REGISTER_BASE; + + if ((csraddr | length) & 0x3) + return RCODE_TYPE_ERROR; + + length /= 4; + + switch (csraddr) { + case CSR_STATE_CLEAR: + /* FIXME FIXME FIXME */ + printk("doh, someone wants to mess with state clear\n"); + out; + case CSR_STATE_SET: + printk("doh, someone wants to mess with state set\n"); + out; + + case CSR_NODE_IDS: + host->csr.node_ids &= NODE_MASK << 16; + host->csr.node_ids |= be32_to_cpu(*(data++)) & (BUS_MASK << 16); + host->node_id = host->csr.node_ids >> 16; + host->driver->devctl(host, SET_BUS_ID, host->node_id >> 6); + out; + + case CSR_RESET_START: + /* FIXME - perform command reset */ + out; + + /* address gap */ + return RCODE_ADDRESS_ERROR; + + case CSR_SPLIT_TIMEOUT_HI: + host->csr.split_timeout_hi = + be32_to_cpu(*(data++)) & 0x00000007; + calculate_expire(&host->csr); + out; + case CSR_SPLIT_TIMEOUT_LO: + host->csr.split_timeout_lo = + be32_to_cpu(*(data++)) & 0xfff80000; + calculate_expire(&host->csr); + out; + + /* address gap */ + return RCODE_ADDRESS_ERROR; + + case CSR_CYCLE_TIME: + /* should only be set by cycle start packet, automatically */ + host->csr.cycle_time = be32_to_cpu(*data); + host->driver->devctl(host, SET_CYCLE_COUNTER, + be32_to_cpu(*(data++))); + out; + case CSR_BUS_TIME: + host->csr.bus_time = be32_to_cpu(*(data++)) & 0xffffff80; + out; + + /* address gap */ + return RCODE_ADDRESS_ERROR; + + case CSR_BUSY_TIMEOUT: + /* not yet implemented */ + return RCODE_ADDRESS_ERROR; + + case CSR_BUS_MANAGER_ID: + case CSR_BANDWIDTH_AVAILABLE: + case CSR_CHANNELS_AVAILABLE_HI: + case CSR_CHANNELS_AVAILABLE_LO: + /* these are not writable, only lockable */ + return RCODE_TYPE_ERROR; + + case CSR_BROADCAST_CHANNEL: + /* only the valid bit can be written */ + host->csr.broadcast_channel = (host->csr.broadcast_channel & ~0x40000000) + | (be32_to_cpu(*data) & 0x40000000); + out; + + /* address gap to end - fall through */ + default: + return RCODE_ADDRESS_ERROR; + } + + return RCODE_COMPLETE; +} + +#undef out + + +static int lock_regs(struct hpsb_host *host, int nodeid, quadlet_t *store, + u64 addr, quadlet_t data, quadlet_t arg, int extcode, u16 fl) +{ + int csraddr = addr - CSR_REGISTER_BASE; + unsigned long flags; + quadlet_t *regptr = NULL; + + if (csraddr & 0x3) + return RCODE_TYPE_ERROR; + + if (csraddr < CSR_BUS_MANAGER_ID || csraddr > CSR_CHANNELS_AVAILABLE_LO + || extcode != EXTCODE_COMPARE_SWAP) + goto unsupported_lockreq; + + data = be32_to_cpu(data); + arg = be32_to_cpu(arg); + + /* Is somebody releasing the broadcast_channel on us? */ + if (csraddr == CSR_CHANNELS_AVAILABLE_HI && (data & 0x1)) { + /* Note: this is may not be the right way to handle + * the problem, so we should look into the proper way + * eventually. */ + HPSB_WARN("Node [" NODE_BUS_FMT "] wants to release " + "broadcast channel 31. Ignoring.", + NODE_BUS_ARGS(host, nodeid)); + + data &= ~0x1; /* keep broadcast channel allocated */ + } + + if (host->driver->hw_csr_reg) { + quadlet_t old; + + old = host->driver-> + hw_csr_reg(host, (csraddr - CSR_BUS_MANAGER_ID) >> 2, + data, arg); + + *store = cpu_to_be32(old); + return RCODE_COMPLETE; + } + + spin_lock_irqsave(&host->csr.lock, flags); + + switch (csraddr) { + case CSR_BUS_MANAGER_ID: + regptr = &host->csr.bus_manager_id; + *store = cpu_to_be32(*regptr); + if (*regptr == arg) + *regptr = data; + break; + + case CSR_BANDWIDTH_AVAILABLE: + { + quadlet_t bandwidth; + quadlet_t old; + quadlet_t new; + + regptr = &host->csr.bandwidth_available; + old = *regptr; + + /* bandwidth available algorithm adapted from IEEE 1394a-2000 spec */ + if (arg > 0x1fff) { + *store = cpu_to_be32(old); /* change nothing */ + break; + } + data &= 0x1fff; + if (arg >= data) { + /* allocate bandwidth */ + bandwidth = arg - data; + if (old >= bandwidth) { + new = old - bandwidth; + *store = cpu_to_be32(arg); + *regptr = new; + } else { + *store = cpu_to_be32(old); + } + } else { + /* deallocate bandwidth */ + bandwidth = data - arg; + if (old + bandwidth < 0x2000) { + new = old + bandwidth; + *store = cpu_to_be32(arg); + *regptr = new; + } else { + *store = cpu_to_be32(old); + } + } + break; + } + + case CSR_CHANNELS_AVAILABLE_HI: + { + /* Lock algorithm for CHANNELS_AVAILABLE as recommended by 1394a-2000 */ + quadlet_t affected_channels = arg ^ data; + + regptr = &host->csr.channels_available_hi; + + if ((arg & affected_channels) == (*regptr & affected_channels)) { + *regptr ^= affected_channels; + *store = cpu_to_be32(arg); + } else { + *store = cpu_to_be32(*regptr); + } + + break; + } + + case CSR_CHANNELS_AVAILABLE_LO: + { + /* Lock algorithm for CHANNELS_AVAILABLE as recommended by 1394a-2000 */ + quadlet_t affected_channels = arg ^ data; + + regptr = &host->csr.channels_available_lo; + + if ((arg & affected_channels) == (*regptr & affected_channels)) { + *regptr ^= affected_channels; + *store = cpu_to_be32(arg); + } else { + *store = cpu_to_be32(*regptr); + } + break; + } + } + + spin_unlock_irqrestore(&host->csr.lock, flags); + + return RCODE_COMPLETE; + + unsupported_lockreq: + switch (csraddr) { + case CSR_STATE_CLEAR: + case CSR_STATE_SET: + case CSR_RESET_START: + case CSR_NODE_IDS: + case CSR_SPLIT_TIMEOUT_HI: + case CSR_SPLIT_TIMEOUT_LO: + case CSR_CYCLE_TIME: + case CSR_BUS_TIME: + case CSR_BROADCAST_CHANNEL: + return RCODE_TYPE_ERROR; + + case CSR_BUSY_TIMEOUT: + /* not yet implemented - fall through */ + default: + return RCODE_ADDRESS_ERROR; + } +} + +static int lock64_regs(struct hpsb_host *host, int nodeid, octlet_t * store, + u64 addr, octlet_t data, octlet_t arg, int extcode, u16 fl) +{ + int csraddr = addr - CSR_REGISTER_BASE; + unsigned long flags; + + data = be64_to_cpu(data); + arg = be64_to_cpu(arg); + + if (csraddr & 0x3) + return RCODE_TYPE_ERROR; + + if (csraddr != CSR_CHANNELS_AVAILABLE + || extcode != EXTCODE_COMPARE_SWAP) + goto unsupported_lock64req; + + /* Is somebody releasing the broadcast_channel on us? */ + if (csraddr == CSR_CHANNELS_AVAILABLE_HI && (data & 0x100000000ULL)) { + /* Note: this is may not be the right way to handle + * the problem, so we should look into the proper way + * eventually. */ + HPSB_WARN("Node [" NODE_BUS_FMT "] wants to release " + "broadcast channel 31. Ignoring.", + NODE_BUS_ARGS(host, nodeid)); + + data &= ~0x100000000ULL; /* keep broadcast channel allocated */ + } + + if (host->driver->hw_csr_reg) { + quadlet_t data_hi, data_lo; + quadlet_t arg_hi, arg_lo; + quadlet_t old_hi, old_lo; + + data_hi = data >> 32; + data_lo = data & 0xFFFFFFFF; + arg_hi = arg >> 32; + arg_lo = arg & 0xFFFFFFFF; + + old_hi = host->driver->hw_csr_reg(host, (csraddr - CSR_BUS_MANAGER_ID) >> 2, + data_hi, arg_hi); + + old_lo = host->driver->hw_csr_reg(host, ((csraddr + 4) - CSR_BUS_MANAGER_ID) >> 2, + data_lo, arg_lo); + + *store = cpu_to_be64(((octlet_t)old_hi << 32) | old_lo); + } else { + octlet_t old; + octlet_t affected_channels = arg ^ data; + + spin_lock_irqsave(&host->csr.lock, flags); + + old = ((octlet_t)host->csr.channels_available_hi << 32) | host->csr.channels_available_lo; + + if ((arg & affected_channels) == (old & affected_channels)) { + host->csr.channels_available_hi ^= (affected_channels >> 32); + host->csr.channels_available_lo ^= (affected_channels & 0xffffffff); + *store = cpu_to_be64(arg); + } else { + *store = cpu_to_be64(old); + } + + spin_unlock_irqrestore(&host->csr.lock, flags); + } + + /* Is somebody erroneously releasing the broadcast_channel on us? */ + if (host->csr.channels_available_hi & 0x1) + host->csr.channels_available_hi &= ~0x1; + + return RCODE_COMPLETE; + + unsupported_lock64req: + switch (csraddr) { + case CSR_STATE_CLEAR: + case CSR_STATE_SET: + case CSR_RESET_START: + case CSR_NODE_IDS: + case CSR_SPLIT_TIMEOUT_HI: + case CSR_SPLIT_TIMEOUT_LO: + case CSR_CYCLE_TIME: + case CSR_BUS_TIME: + case CSR_BUS_MANAGER_ID: + case CSR_BROADCAST_CHANNEL: + case CSR_BUSY_TIMEOUT: + case CSR_BANDWIDTH_AVAILABLE: + return RCODE_TYPE_ERROR; + + default: + return RCODE_ADDRESS_ERROR; + } +} + +static int write_fcp(struct hpsb_host *host, int nodeid, int dest, + quadlet_t *data, u64 addr, size_t length, u16 flags) +{ + int csraddr = addr - CSR_REGISTER_BASE; + + if (length > 512) + return RCODE_TYPE_ERROR; + + switch (csraddr) { + case CSR_FCP_COMMAND: + highlevel_fcp_request(host, nodeid, 0, (u8 *)data, length); + break; + case CSR_FCP_RESPONSE: + highlevel_fcp_request(host, nodeid, 1, (u8 *)data, length); + break; + default: + return RCODE_TYPE_ERROR; + } + + return RCODE_COMPLETE; +} + +static int read_config_rom(struct hpsb_host *host, int nodeid, quadlet_t *buffer, + u64 addr, size_t length, u16 fl) +{ + u32 offset = addr - CSR1212_REGISTER_SPACE_BASE; + + if (csr1212_read(host->csr.rom, offset, buffer, length) == CSR1212_SUCCESS) + return RCODE_COMPLETE; + else + return RCODE_ADDRESS_ERROR; +} + +static u64 allocate_addr_range(u64 size, u32 alignment, void *__host) +{ + struct hpsb_host *host = (struct hpsb_host*)__host; + + return hpsb_allocate_and_register_addrspace(&csr_highlevel, + host, + &config_rom_ops, + size, alignment, + CSR1212_UNITS_SPACE_BASE, + CSR1212_UNITS_SPACE_END); +} + +static void release_addr_range(u64 addr, void *__host) +{ + struct hpsb_host *host = (struct hpsb_host*)__host; + hpsb_unregister_addrspace(&csr_highlevel, host, addr); +} + + +int init_csr(void) +{ + node_cap = csr1212_new_immediate(CSR1212_KV_ID_NODE_CAPABILITIES, 0x0083c0); + if (!node_cap) { + HPSB_ERR("Failed to allocate memory for Node Capabilties ConfigROM entry!"); + return -ENOMEM; + } + + hpsb_register_highlevel(&csr_highlevel); + + return 0; +} + +void cleanup_csr(void) +{ + if (node_cap) + csr1212_release_keyval(node_cap); + hpsb_unregister_highlevel(&csr_highlevel); +} diff --git a/drivers/ieee1394/csr.h b/drivers/ieee1394/csr.h new file mode 100644 index 0000000..f115465 --- /dev/null +++ b/drivers/ieee1394/csr.h @@ -0,0 +1,99 @@ +#ifndef _IEEE1394_CSR_H +#define _IEEE1394_CSR_H + +#include <linux/spinlock_types.h> + +#include "csr1212.h" +#include "ieee1394_types.h" + +#define CSR_REGISTER_BASE 0xfffff0000000ULL + +/* register offsets relative to CSR_REGISTER_BASE */ +#define CSR_STATE_CLEAR 0x0 +#define CSR_STATE_SET 0x4 +#define CSR_NODE_IDS 0x8 +#define CSR_RESET_START 0xc +#define CSR_SPLIT_TIMEOUT_HI 0x18 +#define CSR_SPLIT_TIMEOUT_LO 0x1c +#define CSR_CYCLE_TIME 0x200 +#define CSR_BUS_TIME 0x204 +#define CSR_BUSY_TIMEOUT 0x210 +#define CSR_BUS_MANAGER_ID 0x21c +#define CSR_BANDWIDTH_AVAILABLE 0x220 +#define CSR_CHANNELS_AVAILABLE 0x224 +#define CSR_CHANNELS_AVAILABLE_HI 0x224 +#define CSR_CHANNELS_AVAILABLE_LO 0x228 +#define CSR_BROADCAST_CHANNEL 0x234 +#define CSR_CONFIG_ROM 0x400 +#define CSR_CONFIG_ROM_END 0x800 +#define CSR_FCP_COMMAND 0xB00 +#define CSR_FCP_RESPONSE 0xD00 +#define CSR_FCP_END 0xF00 +#define CSR_TOPOLOGY_MAP 0x1000 +#define CSR_TOPOLOGY_MAP_END 0x1400 +#define CSR_SPEED_MAP 0x2000 +#define CSR_SPEED_MAP_END 0x3000 + +/* IEEE 1394 bus specific Configuration ROM Key IDs */ +#define IEEE1394_KV_ID_POWER_REQUIREMENTS (0x30) + +/* IEEE 1394 Bus Information Block specifics */ +#define CSR_BUS_INFO_SIZE (5 * sizeof(quadlet_t)) + +#define CSR_IRMC_SHIFT 31 +#define CSR_CMC_SHIFT 30 +#define CSR_ISC_SHIFT 29 +#define CSR_BMC_SHIFT 28 +#define CSR_PMC_SHIFT 27 +#define CSR_CYC_CLK_ACC_SHIFT 16 +#define CSR_MAX_REC_SHIFT 12 +#define CSR_MAX_ROM_SHIFT 8 +#define CSR_GENERATION_SHIFT 4 + +#define CSR_SET_BUS_INFO_GENERATION(csr, gen) \ + ((csr)->bus_info_data[2] = \ + cpu_to_be32((be32_to_cpu((csr)->bus_info_data[2]) & \ + ~(0xf << CSR_GENERATION_SHIFT)) | \ + (gen) << CSR_GENERATION_SHIFT)) + +struct csr_control { + spinlock_t lock; + + quadlet_t state; + quadlet_t node_ids; + quadlet_t split_timeout_hi, split_timeout_lo; + unsigned long expire; /* Calculated from split_timeout */ + quadlet_t cycle_time; + quadlet_t bus_time; + quadlet_t bus_manager_id; + quadlet_t bandwidth_available; + quadlet_t channels_available_hi, channels_available_lo; + quadlet_t broadcast_channel; + + /* Bus Info */ + quadlet_t guid_hi, guid_lo; + u8 cyc_clk_acc; + u8 max_rec; + u8 max_rom; + u8 generation; /* Only use values between 0x2 and 0xf */ + u8 lnk_spd; + + unsigned long gen_timestamp[16]; + + struct csr1212_csr *rom; + + quadlet_t topology_map[256]; + quadlet_t speed_map[1024]; +}; + +extern struct csr1212_bus_ops csr_bus_ops; + +int init_csr(void); +void cleanup_csr(void); + +/* hpsb_update_config_rom() is deprecated */ +struct hpsb_host; +int hpsb_update_config_rom(struct hpsb_host *host, const quadlet_t *new_rom, + size_t size, unsigned char rom_version); + +#endif /* _IEEE1394_CSR_H */ diff --git a/drivers/ieee1394/csr1212.c b/drivers/ieee1394/csr1212.c new file mode 100644 index 0000000..5e38a68 --- /dev/null +++ b/drivers/ieee1394/csr1212.c @@ -0,0 +1,1482 @@ +/* + * csr1212.c -- IEEE 1212 Control and Status Register support for Linux + * + * Copyright (C) 2003 Francois Retief <fgretief@sun.ac.za> + * Steve Kinneberg <kinnebergsteve@acmsystems.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +/* TODO List: + * - Verify interface consistency: i.e., public functions that take a size + * parameter expect size to be in bytes. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <asm/bug.h> +#include <asm/byteorder.h> + +#include "csr1212.h" + + +/* Permitted key type for each key id */ +#define __I (1 << CSR1212_KV_TYPE_IMMEDIATE) +#define __C (1 << CSR1212_KV_TYPE_CSR_OFFSET) +#define __D (1 << CSR1212_KV_TYPE_DIRECTORY) +#define __L (1 << CSR1212_KV_TYPE_LEAF) +static const u8 csr1212_key_id_type_map[0x30] = { + __C, /* used by Apple iSight */ + __D | __L, /* Descriptor */ + __I | __D | __L, /* Bus_Dependent_Info */ + __I | __D | __L, /* Vendor */ + __I, /* Hardware_Version */ + 0, 0, /* Reserved */ + __D | __L | __I, /* Module */ + __I, 0, 0, 0, /* used by Apple iSight, Reserved */ + __I, /* Node_Capabilities */ + __L, /* EUI_64 */ + 0, 0, 0, /* Reserved */ + __D, /* Unit */ + __I, /* Specifier_ID */ + __I, /* Version */ + __I | __C | __D | __L, /* Dependent_Info */ + __L, /* Unit_Location */ + 0, /* Reserved */ + __I, /* Model */ + __D, /* Instance */ + __L, /* Keyword */ + __D, /* Feature */ + __L, /* Extended_ROM */ + __I, /* Extended_Key_Specifier_ID */ + __I, /* Extended_Key */ + __I | __C | __D | __L, /* Extended_Data */ + __L, /* Modifiable_Descriptor */ + __I, /* Directory_ID */ + __I, /* Revision */ +}; +#undef __I +#undef __C +#undef __D +#undef __L + + +#define quads_to_bytes(_q) ((_q) * sizeof(u32)) +#define bytes_to_quads(_b) DIV_ROUND_UP(_b, sizeof(u32)) + +static void free_keyval(struct csr1212_keyval *kv) +{ + if ((kv->key.type == CSR1212_KV_TYPE_LEAF) && + (kv->key.id != CSR1212_KV_ID_EXTENDED_ROM)) + CSR1212_FREE(kv->value.leaf.data); + + CSR1212_FREE(kv); +} + +static u16 csr1212_crc16(const u32 *buffer, size_t length) +{ + int shift; + u32 data; + u16 sum, crc = 0; + + for (; length; length--) { + data = be32_to_cpu(*buffer); + buffer++; + for (shift = 28; shift >= 0; shift -= 4 ) { + sum = ((crc >> 12) ^ (data >> shift)) & 0xf; + crc = (crc << 4) ^ (sum << 12) ^ (sum << 5) ^ (sum); + } + crc &= 0xffff; + } + + return cpu_to_be16(crc); +} + +/* Microsoft computes the CRC with the bytes in reverse order. */ +static u16 csr1212_msft_crc16(const u32 *buffer, size_t length) +{ + int shift; + u32 data; + u16 sum, crc = 0; + + for (; length; length--) { + data = le32_to_cpu(*buffer); + buffer++; + for (shift = 28; shift >= 0; shift -= 4 ) { + sum = ((crc >> 12) ^ (data >> shift)) & 0xf; + crc = (crc << 4) ^ (sum << 12) ^ (sum << 5) ^ (sum); + } + crc &= 0xffff; + } + + return cpu_to_be16(crc); +} + +static struct csr1212_dentry * +csr1212_find_keyval(struct csr1212_keyval *dir, struct csr1212_keyval *kv) +{ + struct csr1212_dentry *pos; + + for (pos = dir->value.directory.dentries_head; + pos != NULL; pos = pos->next) + if (pos->kv == kv) + return pos; + return NULL; +} + +static struct csr1212_keyval * +csr1212_find_keyval_offset(struct csr1212_keyval *kv_list, u32 offset) +{ + struct csr1212_keyval *kv; + + for (kv = kv_list->next; kv && (kv != kv_list); kv = kv->next) + if (kv->offset == offset) + return kv; + return NULL; +} + + +/* Creation Routines */ + +struct csr1212_csr *csr1212_create_csr(struct csr1212_bus_ops *ops, + size_t bus_info_size, void *private) +{ + struct csr1212_csr *csr; + + csr = CSR1212_MALLOC(sizeof(*csr)); + if (!csr) + return NULL; + + csr->cache_head = + csr1212_rom_cache_malloc(CSR1212_CONFIG_ROM_SPACE_OFFSET, + CSR1212_CONFIG_ROM_SPACE_SIZE); + if (!csr->cache_head) { + CSR1212_FREE(csr); + return NULL; + } + + /* The keyval key id is not used for the root node, but a valid key id + * that can be used for a directory needs to be passed to + * csr1212_new_directory(). */ + csr->root_kv = csr1212_new_directory(CSR1212_KV_ID_VENDOR); + if (!csr->root_kv) { + CSR1212_FREE(csr->cache_head); + CSR1212_FREE(csr); + return NULL; + } + + csr->bus_info_data = csr->cache_head->data; + csr->bus_info_len = bus_info_size; + csr->crc_len = bus_info_size; + csr->ops = ops; + csr->private = private; + csr->cache_tail = csr->cache_head; + + return csr; +} + +void csr1212_init_local_csr(struct csr1212_csr *csr, + const u32 *bus_info_data, int max_rom) +{ + static const int mr_map[] = { 4, 64, 1024, 0 }; + + BUG_ON(max_rom & ~0x3); + csr->max_rom = mr_map[max_rom]; + memcpy(csr->bus_info_data, bus_info_data, csr->bus_info_len); +} + +static struct csr1212_keyval *csr1212_new_keyval(u8 type, u8 key) +{ + struct csr1212_keyval *kv; + + if (key < 0x30 && ((csr1212_key_id_type_map[key] & (1 << type)) == 0)) + return NULL; + + kv = CSR1212_MALLOC(sizeof(*kv)); + if (!kv) + return NULL; + + atomic_set(&kv->refcnt, 1); + kv->key.type = type; + kv->key.id = key; + kv->associate = NULL; + kv->next = NULL; + kv->prev = NULL; + kv->offset = 0; + kv->valid = 0; + return kv; +} + +struct csr1212_keyval *csr1212_new_immediate(u8 key, u32 value) +{ + struct csr1212_keyval *kv; + + kv = csr1212_new_keyval(CSR1212_KV_TYPE_IMMEDIATE, key); + if (!kv) + return NULL; + + kv->value.immediate = value; + kv->valid = 1; + return kv; +} + +static struct csr1212_keyval * +csr1212_new_leaf(u8 key, const void *data, size_t data_len) +{ + struct csr1212_keyval *kv; + + kv = csr1212_new_keyval(CSR1212_KV_TYPE_LEAF, key); + if (!kv) + return NULL; + + if (data_len > 0) { + kv->value.leaf.data = CSR1212_MALLOC(data_len); + if (!kv->value.leaf.data) { + CSR1212_FREE(kv); + return NULL; + } + + if (data) + memcpy(kv->value.leaf.data, data, data_len); + } else { + kv->value.leaf.data = NULL; + } + + kv->value.leaf.len = bytes_to_quads(data_len); + kv->offset = 0; + kv->valid = 1; + + return kv; +} + +static struct csr1212_keyval * +csr1212_new_csr_offset(u8 key, u32 csr_offset) +{ + struct csr1212_keyval *kv; + + kv = csr1212_new_keyval(CSR1212_KV_TYPE_CSR_OFFSET, key); + if (!kv) + return NULL; + + kv->value.csr_offset = csr_offset; + + kv->offset = 0; + kv->valid = 1; + return kv; +} + +struct csr1212_keyval *csr1212_new_directory(u8 key) +{ + struct csr1212_keyval *kv; + + kv = csr1212_new_keyval(CSR1212_KV_TYPE_DIRECTORY, key); + if (!kv) + return NULL; + + kv->value.directory.len = 0; + kv->offset = 0; + kv->value.directory.dentries_head = NULL; + kv->value.directory.dentries_tail = NULL; + kv->valid = 1; + return kv; +} + +void csr1212_associate_keyval(struct csr1212_keyval *kv, + struct csr1212_keyval *associate) +{ + BUG_ON(!kv || !associate || kv->key.id == CSR1212_KV_ID_DESCRIPTOR || + (associate->key.id != CSR1212_KV_ID_DESCRIPTOR && + associate->key.id != CSR1212_KV_ID_DEPENDENT_INFO && + associate->key.id != CSR1212_KV_ID_EXTENDED_KEY && + associate->key.id != CSR1212_KV_ID_EXTENDED_DATA && + associate->key.id < 0x30) || + (kv->key.id == CSR1212_KV_ID_EXTENDED_KEY_SPECIFIER_ID && + associate->key.id != CSR1212_KV_ID_EXTENDED_KEY) || + (kv->key.id == CSR1212_KV_ID_EXTENDED_KEY && + associate->key.id != CSR1212_KV_ID_EXTENDED_DATA) || + (associate->key.id == CSR1212_KV_ID_EXTENDED_KEY && + kv->key.id != CSR1212_KV_ID_EXTENDED_KEY_SPECIFIER_ID) || + (associate->key.id == CSR1212_KV_ID_EXTENDED_DATA && + kv->key.id != CSR1212_KV_ID_EXTENDED_KEY)); + + if (kv->associate) + csr1212_release_keyval(kv->associate); + + csr1212_keep_keyval(associate); + kv->associate = associate; +} + +static int __csr1212_attach_keyval_to_directory(struct csr1212_keyval *dir, + struct csr1212_keyval *kv, + bool keep_keyval) +{ + struct csr1212_dentry *dentry; + + BUG_ON(!kv || !dir || dir->key.type != CSR1212_KV_TYPE_DIRECTORY); + + dentry = CSR1212_MALLOC(sizeof(*dentry)); + if (!dentry) + return -ENOMEM; + + if (keep_keyval) + csr1212_keep_keyval(kv); + dentry->kv = kv; + + dentry->next = NULL; + dentry->prev = dir->value.directory.dentries_tail; + + if (!dir->value.directory.dentries_head) + dir->value.directory.dentries_head = dentry; + + if (dir->value.directory.dentries_tail) + dir->value.directory.dentries_tail->next = dentry; + dir->value.directory.dentries_tail = dentry; + + return CSR1212_SUCCESS; +} + +int csr1212_attach_keyval_to_directory(struct csr1212_keyval *dir, + struct csr1212_keyval *kv) +{ + return __csr1212_attach_keyval_to_directory(dir, kv, true); +} + +#define CSR1212_DESCRIPTOR_LEAF_DATA(kv) \ + (&((kv)->value.leaf.data[1])) + +#define CSR1212_DESCRIPTOR_LEAF_SET_TYPE(kv, type) \ + ((kv)->value.leaf.data[0] = \ + cpu_to_be32(CSR1212_DESCRIPTOR_LEAF_SPECIFIER_ID(kv) | \ + ((type) << CSR1212_DESCRIPTOR_LEAF_TYPE_SHIFT))) +#define CSR1212_DESCRIPTOR_LEAF_SET_SPECIFIER_ID(kv, spec_id) \ + ((kv)->value.leaf.data[0] = \ + cpu_to_be32((CSR1212_DESCRIPTOR_LEAF_TYPE(kv) << \ + CSR1212_DESCRIPTOR_LEAF_TYPE_SHIFT) | \ + ((spec_id) & CSR1212_DESCRIPTOR_LEAF_SPECIFIER_ID_MASK))) + +static struct csr1212_keyval * +csr1212_new_descriptor_leaf(u8 dtype, u32 specifier_id, + const void *data, size_t data_len) +{ + struct csr1212_keyval *kv; + + kv = csr1212_new_leaf(CSR1212_KV_ID_DESCRIPTOR, NULL, + data_len + CSR1212_DESCRIPTOR_LEAF_OVERHEAD); + if (!kv) + return NULL; + + CSR1212_DESCRIPTOR_LEAF_SET_TYPE(kv, dtype); + CSR1212_DESCRIPTOR_LEAF_SET_SPECIFIER_ID(kv, specifier_id); + + if (data) + memcpy(CSR1212_DESCRIPTOR_LEAF_DATA(kv), data, data_len); + + return kv; +} + +/* Check if string conforms to minimal ASCII as per IEEE 1212 clause 7.4 */ +static int csr1212_check_minimal_ascii(const char *s) +{ + static const char minimal_ascii_table[] = { + /* 1 2 4 8 16 32 64 128 */ + 128, /* --, --, --, --, --, --, --, 07, */ + 4 + 16 + 32, /* --, --, 0a, --, 0C, 0D, --, --, */ + 0, /* --, --, --, --, --, --, --, --, */ + 0, /* --, --, --, --, --, --, --, --, */ + 255 - 8 - 16, /* 20, 21, 22, --, --, 25, 26, 27, */ + 255, /* 28, 29, 2a, 2b, 2c, 2d, 2e, 2f, */ + 255, /* 30, 31, 32, 33, 34, 35, 36, 37, */ + 255, /* 38, 39, 3a, 3b, 3c, 3d, 3e, 3f, */ + 255, /* 40, 41, 42, 43, 44, 45, 46, 47, */ + 255, /* 48, 49, 4a, 4b, 4c, 4d, 4e, 4f, */ + 255, /* 50, 51, 52, 53, 54, 55, 56, 57, */ + 1 + 2 + 4 + 128, /* 58, 59, 5a, --, --, --, --, 5f, */ + 255 - 1, /* --, 61, 62, 63, 64, 65, 66, 67, */ + 255, /* 68, 69, 6a, 6b, 6c, 6d, 6e, 6f, */ + 255, /* 70, 71, 72, 73, 74, 75, 76, 77, */ + 1 + 2 + 4, /* 78, 79, 7a, --, --, --, --, --, */ + }; + int i, j; + + for (; *s; s++) { + i = *s >> 3; /* i = *s / 8; */ + j = 1 << (*s & 3); /* j = 1 << (*s % 8); */ + + if (i >= ARRAY_SIZE(minimal_ascii_table) || + !(minimal_ascii_table[i] & j)) + return -EINVAL; + } + return 0; +} + +/* IEEE 1212 clause 7.5.4.1 textual descriptors (English, minimal ASCII) */ +struct csr1212_keyval *csr1212_new_string_descriptor_leaf(const char *s) +{ + struct csr1212_keyval *kv; + u32 *text; + size_t str_len, quads; + + if (!s || !*s || csr1212_check_minimal_ascii(s)) + return NULL; + + str_len = strlen(s); + quads = bytes_to_quads(str_len); + kv = csr1212_new_descriptor_leaf(0, 0, NULL, quads_to_bytes(quads) + + CSR1212_TEXTUAL_DESCRIPTOR_LEAF_OVERHEAD); + if (!kv) + return NULL; + + kv->value.leaf.data[1] = 0; /* width, character_set, language */ + text = CSR1212_TEXTUAL_DESCRIPTOR_LEAF_DATA(kv); + text[quads - 1] = 0; /* padding */ + memcpy(text, s, str_len); + + return kv; +} + + +/* Destruction Routines */ + +void csr1212_detach_keyval_from_directory(struct csr1212_keyval *dir, + struct csr1212_keyval *kv) +{ + struct csr1212_dentry *dentry; + + if (!kv || !dir || dir->key.type != CSR1212_KV_TYPE_DIRECTORY) + return; + + dentry = csr1212_find_keyval(dir, kv); + + if (!dentry) + return; + + if (dentry->prev) + dentry->prev->next = dentry->next; + if (dentry->next) + dentry->next->prev = dentry->prev; + if (dir->value.directory.dentries_head == dentry) + dir->value.directory.dentries_head = dentry->next; + if (dir->value.directory.dentries_tail == dentry) + dir->value.directory.dentries_tail = dentry->prev; + + CSR1212_FREE(dentry); + + csr1212_release_keyval(kv); +} + +/* This function is used to free the memory taken by a keyval. If the given + * keyval is a directory type, then any keyvals contained in that directory + * will be destroyed as well if noone holds a reference on them. By means of + * list manipulation, this routine will descend a directory structure in a + * non-recursive manner. */ +void csr1212_release_keyval(struct csr1212_keyval *kv) +{ + struct csr1212_keyval *k, *a; + struct csr1212_dentry dentry; + struct csr1212_dentry *head, *tail; + + if (!atomic_dec_and_test(&kv->refcnt)) + return; + + dentry.kv = kv; + dentry.next = NULL; + dentry.prev = NULL; + + head = &dentry; + tail = head; + + while (head) { + k = head->kv; + + while (k) { + /* must not dec_and_test kv->refcnt again */ + if (k != kv && !atomic_dec_and_test(&k->refcnt)) + break; + + a = k->associate; + + if (k->key.type == CSR1212_KV_TYPE_DIRECTORY) { + /* If the current entry is a directory, move all + * the entries to the destruction list. */ + if (k->value.directory.dentries_head) { + tail->next = + k->value.directory.dentries_head; + k->value.directory.dentries_head->prev = + tail; + tail = k->value.directory.dentries_tail; + } + } + free_keyval(k); + k = a; + } + + head = head->next; + if (head) { + if (head->prev && head->prev != &dentry) + CSR1212_FREE(head->prev); + head->prev = NULL; + } else if (tail != &dentry) { + CSR1212_FREE(tail); + } + } +} + +void csr1212_destroy_csr(struct csr1212_csr *csr) +{ + struct csr1212_csr_rom_cache *c, *oc; + struct csr1212_cache_region *cr, *ocr; + + csr1212_release_keyval(csr->root_kv); + + c = csr->cache_head; + while (c) { + oc = c; + cr = c->filled_head; + while (cr) { + ocr = cr; + cr = cr->next; + CSR1212_FREE(ocr); + } + c = c->next; + CSR1212_FREE(oc); + } + + CSR1212_FREE(csr); +} + + +/* CSR Image Creation */ + +static int csr1212_append_new_cache(struct csr1212_csr *csr, size_t romsize) +{ + struct csr1212_csr_rom_cache *cache; + u64 csr_addr; + + BUG_ON(!csr || !csr->ops || !csr->ops->allocate_addr_range || + !csr->ops->release_addr || csr->max_rom < 1); + + /* ROM size must be a multiple of csr->max_rom */ + romsize = (romsize + (csr->max_rom - 1)) & ~(csr->max_rom - 1); + + csr_addr = csr->ops->allocate_addr_range(romsize, csr->max_rom, + csr->private); + if (csr_addr == CSR1212_INVALID_ADDR_SPACE) + return -ENOMEM; + + if (csr_addr < CSR1212_REGISTER_SPACE_BASE) { + /* Invalid address returned from allocate_addr_range(). */ + csr->ops->release_addr(csr_addr, csr->private); + return -ENOMEM; + } + + cache = csr1212_rom_cache_malloc(csr_addr - CSR1212_REGISTER_SPACE_BASE, + romsize); + if (!cache) { + csr->ops->release_addr(csr_addr, csr->private); + return -ENOMEM; + } + + cache->ext_rom = csr1212_new_keyval(CSR1212_KV_TYPE_LEAF, + CSR1212_KV_ID_EXTENDED_ROM); + if (!cache->ext_rom) { + csr->ops->release_addr(csr_addr, csr->private); + CSR1212_FREE(cache); + return -ENOMEM; + } + + if (csr1212_attach_keyval_to_directory(csr->root_kv, cache->ext_rom) != + CSR1212_SUCCESS) { + csr1212_release_keyval(cache->ext_rom); + csr->ops->release_addr(csr_addr, csr->private); + CSR1212_FREE(cache); + return -ENOMEM; + } + cache->ext_rom->offset = csr_addr - CSR1212_REGISTER_SPACE_BASE; + cache->ext_rom->value.leaf.len = -1; + cache->ext_rom->value.leaf.data = cache->data; + + /* Add cache to tail of cache list */ + cache->prev = csr->cache_tail; + csr->cache_tail->next = cache; + csr->cache_tail = cache; + return CSR1212_SUCCESS; +} + +static void csr1212_remove_cache(struct csr1212_csr *csr, + struct csr1212_csr_rom_cache *cache) +{ + if (csr->cache_head == cache) + csr->cache_head = cache->next; + if (csr->cache_tail == cache) + csr->cache_tail = cache->prev; + + if (cache->prev) + cache->prev->next = cache->next; + if (cache->next) + cache->next->prev = cache->prev; + + if (cache->ext_rom) { + csr1212_detach_keyval_from_directory(csr->root_kv, + cache->ext_rom); + csr1212_release_keyval(cache->ext_rom); + } + + CSR1212_FREE(cache); +} + +static int csr1212_generate_layout_subdir(struct csr1212_keyval *dir, + struct csr1212_keyval **layout_tail) +{ + struct csr1212_dentry *dentry; + struct csr1212_keyval *dkv; + struct csr1212_keyval *last_extkey_spec = NULL; + struct csr1212_keyval *last_extkey = NULL; + int num_entries = 0; + + for (dentry = dir->value.directory.dentries_head; dentry; + dentry = dentry->next) { + for (dkv = dentry->kv; dkv; dkv = dkv->associate) { + /* Special Case: Extended Key Specifier_ID */ + if (dkv->key.id == + CSR1212_KV_ID_EXTENDED_KEY_SPECIFIER_ID) { + if (last_extkey_spec == NULL) + last_extkey_spec = dkv; + else if (dkv->value.immediate != + last_extkey_spec->value.immediate) + last_extkey_spec = dkv; + else + continue; + /* Special Case: Extended Key */ + } else if (dkv->key.id == CSR1212_KV_ID_EXTENDED_KEY) { + if (last_extkey == NULL) + last_extkey = dkv; + else if (dkv->value.immediate != + last_extkey->value.immediate) + last_extkey = dkv; + else + continue; + } + + num_entries += 1; + + switch (dkv->key.type) { + default: + case CSR1212_KV_TYPE_IMMEDIATE: + case CSR1212_KV_TYPE_CSR_OFFSET: + break; + case CSR1212_KV_TYPE_LEAF: + case CSR1212_KV_TYPE_DIRECTORY: + /* Remove from list */ + if (dkv->prev && (dkv->prev->next == dkv)) + dkv->prev->next = dkv->next; + if (dkv->next && (dkv->next->prev == dkv)) + dkv->next->prev = dkv->prev; + //if (dkv == *layout_tail) + // *layout_tail = dkv->prev; + + /* Special case: Extended ROM leafs */ + if (dkv->key.id == CSR1212_KV_ID_EXTENDED_ROM) { + dkv->value.leaf.len = -1; + /* Don't add Extended ROM leafs in the + * layout list, they are handled + * differently. */ + break; + } + + /* Add to tail of list */ + dkv->next = NULL; + dkv->prev = *layout_tail; + (*layout_tail)->next = dkv; + *layout_tail = dkv; + break; + } + } + } + return num_entries; +} + +static size_t csr1212_generate_layout_order(struct csr1212_keyval *kv) +{ + struct csr1212_keyval *ltail = kv; + size_t agg_size = 0; + + while (kv) { + switch (kv->key.type) { + case CSR1212_KV_TYPE_LEAF: + /* Add 1 quadlet for crc/len field */ + agg_size += kv->value.leaf.len + 1; + break; + + case CSR1212_KV_TYPE_DIRECTORY: + kv->value.directory.len = + csr1212_generate_layout_subdir(kv, <ail); + /* Add 1 quadlet for crc/len field */ + agg_size += kv->value.directory.len + 1; + break; + } + kv = kv->next; + } + return quads_to_bytes(agg_size); +} + +static struct csr1212_keyval * +csr1212_generate_positions(struct csr1212_csr_rom_cache *cache, + struct csr1212_keyval *start_kv, int start_pos) +{ + struct csr1212_keyval *kv = start_kv; + struct csr1212_keyval *okv = start_kv; + int pos = start_pos; + int kv_len = 0, okv_len = 0; + + cache->layout_head = kv; + + while (kv && pos < cache->size) { + /* Special case: Extended ROM leafs */ + if (kv->key.id != CSR1212_KV_ID_EXTENDED_ROM) + kv->offset = cache->offset + pos; + + switch (kv->key.type) { + case CSR1212_KV_TYPE_LEAF: + kv_len = kv->value.leaf.len; + break; + + case CSR1212_KV_TYPE_DIRECTORY: + kv_len = kv->value.directory.len; + break; + + default: + /* Should never get here */ + WARN_ON(1); + break; + } + + pos += quads_to_bytes(kv_len + 1); + + if (pos <= cache->size) { + okv = kv; + okv_len = kv_len; + kv = kv->next; + } + } + + cache->layout_tail = okv; + cache->len = okv->offset - cache->offset + quads_to_bytes(okv_len + 1); + + return kv; +} + +#define CSR1212_KV_KEY_SHIFT 24 +#define CSR1212_KV_KEY_TYPE_SHIFT 6 +#define CSR1212_KV_KEY_ID_MASK 0x3f +#define CSR1212_KV_KEY_TYPE_MASK 0x3 /* after shift */ + +static void +csr1212_generate_tree_subdir(struct csr1212_keyval *dir, u32 *data_buffer) +{ + struct csr1212_dentry *dentry; + struct csr1212_keyval *last_extkey_spec = NULL; + struct csr1212_keyval *last_extkey = NULL; + int index = 0; + + for (dentry = dir->value.directory.dentries_head; + dentry; + dentry = dentry->next) { + struct csr1212_keyval *a; + + for (a = dentry->kv; a; a = a->associate) { + u32 value = 0; + + /* Special Case: Extended Key Specifier_ID */ + if (a->key.id == + CSR1212_KV_ID_EXTENDED_KEY_SPECIFIER_ID) { + if (last_extkey_spec == NULL) + last_extkey_spec = a; + else if (a->value.immediate != + last_extkey_spec->value.immediate) + last_extkey_spec = a; + else + continue; + + /* Special Case: Extended Key */ + } else if (a->key.id == CSR1212_KV_ID_EXTENDED_KEY) { + if (last_extkey == NULL) + last_extkey = a; + else if (a->value.immediate != + last_extkey->value.immediate) + last_extkey = a; + else + continue; + } + + switch (a->key.type) { + case CSR1212_KV_TYPE_IMMEDIATE: + value = a->value.immediate; + break; + case CSR1212_KV_TYPE_CSR_OFFSET: + value = a->value.csr_offset; + break; + case CSR1212_KV_TYPE_LEAF: + value = a->offset; + value -= dir->offset + quads_to_bytes(1+index); + value = bytes_to_quads(value); + break; + case CSR1212_KV_TYPE_DIRECTORY: + value = a->offset; + value -= dir->offset + quads_to_bytes(1+index); + value = bytes_to_quads(value); + break; + default: + /* Should never get here */ + WARN_ON(1); + break; + } + + value |= (a->key.id & CSR1212_KV_KEY_ID_MASK) << + CSR1212_KV_KEY_SHIFT; + value |= (a->key.type & CSR1212_KV_KEY_TYPE_MASK) << + (CSR1212_KV_KEY_SHIFT + + CSR1212_KV_KEY_TYPE_SHIFT); + data_buffer[index] = cpu_to_be32(value); + index++; + } + } +} + +struct csr1212_keyval_img { + u16 length; + u16 crc; + + /* Must be last */ + u32 data[0]; /* older gcc can't handle [] which is standard */ +}; + +static void csr1212_fill_cache(struct csr1212_csr_rom_cache *cache) +{ + struct csr1212_keyval *kv, *nkv; + struct csr1212_keyval_img *kvi; + + for (kv = cache->layout_head; + kv != cache->layout_tail->next; + kv = nkv) { + kvi = (struct csr1212_keyval_img *)(cache->data + + bytes_to_quads(kv->offset - cache->offset)); + switch (kv->key.type) { + default: + case CSR1212_KV_TYPE_IMMEDIATE: + case CSR1212_KV_TYPE_CSR_OFFSET: + /* Should never get here */ + WARN_ON(1); + break; + + case CSR1212_KV_TYPE_LEAF: + /* Don't copy over Extended ROM areas, they are + * already filled out! */ + if (kv->key.id != CSR1212_KV_ID_EXTENDED_ROM) + memcpy(kvi->data, kv->value.leaf.data, + quads_to_bytes(kv->value.leaf.len)); + + kvi->length = cpu_to_be16(kv->value.leaf.len); + kvi->crc = csr1212_crc16(kvi->data, kv->value.leaf.len); + break; + + case CSR1212_KV_TYPE_DIRECTORY: + csr1212_generate_tree_subdir(kv, kvi->data); + + kvi->length = cpu_to_be16(kv->value.directory.len); + kvi->crc = csr1212_crc16(kvi->data, + kv->value.directory.len); + break; + } + + nkv = kv->next; + if (kv->prev) + kv->prev->next = NULL; + if (kv->next) + kv->next->prev = NULL; + kv->prev = NULL; + kv->next = NULL; + } +} + +/* This size is arbitrarily chosen. + * The struct overhead is subtracted for more economic allocations. */ +#define CSR1212_EXTENDED_ROM_SIZE (2048 - sizeof(struct csr1212_csr_rom_cache)) + +int csr1212_generate_csr_image(struct csr1212_csr *csr) +{ + struct csr1212_bus_info_block_img *bi; + struct csr1212_csr_rom_cache *cache; + struct csr1212_keyval *kv; + size_t agg_size; + int ret; + int init_offset; + + BUG_ON(!csr); + + cache = csr->cache_head; + + bi = (struct csr1212_bus_info_block_img*)cache->data; + + bi->length = bytes_to_quads(csr->bus_info_len) - 1; + bi->crc_length = bi->length; + bi->crc = csr1212_crc16(bi->data, bi->crc_length); + + csr->root_kv->next = NULL; + csr->root_kv->prev = NULL; + + agg_size = csr1212_generate_layout_order(csr->root_kv); + + init_offset = csr->bus_info_len; + + for (kv = csr->root_kv, cache = csr->cache_head; + kv; + cache = cache->next) { + if (!cache) { + /* Estimate approximate number of additional cache + * regions needed (it assumes that the cache holding + * the first 1K Config ROM space always exists). */ + int est_c = agg_size / (CSR1212_EXTENDED_ROM_SIZE - + (2 * sizeof(u32))) + 1; + + /* Add additional cache regions, extras will be + * removed later */ + for (; est_c; est_c--) { + ret = csr1212_append_new_cache(csr, + CSR1212_EXTENDED_ROM_SIZE); + if (ret != CSR1212_SUCCESS) + return ret; + } + /* Need to re-layout for additional cache regions */ + agg_size = csr1212_generate_layout_order(csr->root_kv); + kv = csr->root_kv; + cache = csr->cache_head; + init_offset = csr->bus_info_len; + } + kv = csr1212_generate_positions(cache, kv, init_offset); + agg_size -= cache->len; + init_offset = sizeof(u32); + } + + /* Remove unused, excess cache regions */ + while (cache) { + struct csr1212_csr_rom_cache *oc = cache; + + cache = cache->next; + csr1212_remove_cache(csr, oc); + } + + /* Go through the list backward so that when done, the correct CRC + * will be calculated for the Extended ROM areas. */ + for (cache = csr->cache_tail; cache; cache = cache->prev) { + /* Only Extended ROM caches should have this set. */ + if (cache->ext_rom) { + int leaf_size; + + /* Make sure the Extended ROM leaf is a multiple of + * max_rom in size. */ + BUG_ON(csr->max_rom < 1); + leaf_size = (cache->len + (csr->max_rom - 1)) & + ~(csr->max_rom - 1); + + /* Zero out the unused ROM region */ + memset(cache->data + bytes_to_quads(cache->len), 0x00, + leaf_size - cache->len); + + /* Subtract leaf header */ + leaf_size -= sizeof(u32); + + /* Update the Extended ROM leaf length */ + cache->ext_rom->value.leaf.len = + bytes_to_quads(leaf_size); + } else { + /* Zero out the unused ROM region */ + memset(cache->data + bytes_to_quads(cache->len), 0x00, + cache->size - cache->len); + } + + /* Copy the data into the cache buffer */ + csr1212_fill_cache(cache); + + if (cache != csr->cache_head) { + /* Set the length and CRC of the extended ROM. */ + struct csr1212_keyval_img *kvi = + (struct csr1212_keyval_img*)cache->data; + u16 len = bytes_to_quads(cache->len) - 1; + + kvi->length = cpu_to_be16(len); + kvi->crc = csr1212_crc16(kvi->data, len); + } + } + + return CSR1212_SUCCESS; +} + +int csr1212_read(struct csr1212_csr *csr, u32 offset, void *buffer, u32 len) +{ + struct csr1212_csr_rom_cache *cache; + + for (cache = csr->cache_head; cache; cache = cache->next) + if (offset >= cache->offset && + (offset + len) <= (cache->offset + cache->size)) { + memcpy(buffer, &cache->data[ + bytes_to_quads(offset - cache->offset)], + len); + return CSR1212_SUCCESS; + } + + return -ENOENT; +} + +/* + * Apparently there are many different wrong implementations of the CRC + * algorithm. We don't fail, we just warn... approximately once per GUID. + */ +static void +csr1212_check_crc(const u32 *buffer, size_t length, u16 crc, __be32 *guid) +{ + static u64 last_bad_eui64; + u64 eui64 = ((u64)be32_to_cpu(guid[0]) << 32) | be32_to_cpu(guid[1]); + + if (csr1212_crc16(buffer, length) == crc || + csr1212_msft_crc16(buffer, length) == crc || + eui64 == last_bad_eui64) + return; + + printk(KERN_DEBUG "ieee1394: config ROM CRC error\n"); + last_bad_eui64 = eui64; +} + +/* Parse a chunk of data as a Config ROM */ + +static int csr1212_parse_bus_info_block(struct csr1212_csr *csr) +{ + struct csr1212_bus_info_block_img *bi; + struct csr1212_cache_region *cr; + int i; + int ret; + + /* IEEE 1212 says that the entire bus info block should be readable in + * a single transaction regardless of the max_rom value. + * Unfortunately, many IEEE 1394 devices do not abide by that, so the + * bus info block will be read 1 quadlet at a time. The rest of the + * ConfigROM will be read according to the max_rom field. */ + for (i = 0; i < csr->bus_info_len; i += sizeof(u32)) { + ret = csr->ops->bus_read(csr, CSR1212_CONFIG_ROM_SPACE_BASE + i, + sizeof(u32), &csr->cache_head->data[bytes_to_quads(i)], + csr->private); + if (ret != CSR1212_SUCCESS) + return ret; + + /* check ROM header's info_length */ + if (i == 0 && + be32_to_cpu(csr->cache_head->data[0]) >> 24 != + bytes_to_quads(csr->bus_info_len) - 1) + return -EINVAL; + } + + bi = (struct csr1212_bus_info_block_img*)csr->cache_head->data; + csr->crc_len = quads_to_bytes(bi->crc_length); + + /* IEEE 1212 recommends that crc_len be equal to bus_info_len, but that + * is not always the case, so read the rest of the crc area 1 quadlet at + * a time. */ + for (i = csr->bus_info_len; i <= csr->crc_len; i += sizeof(u32)) { + ret = csr->ops->bus_read(csr, CSR1212_CONFIG_ROM_SPACE_BASE + i, + sizeof(u32), &csr->cache_head->data[bytes_to_quads(i)], + csr->private); + if (ret != CSR1212_SUCCESS) + return ret; + } + + csr1212_check_crc(bi->data, bi->crc_length, bi->crc, + &csr->bus_info_data[3]); + + cr = CSR1212_MALLOC(sizeof(*cr)); + if (!cr) + return -ENOMEM; + + cr->next = NULL; + cr->prev = NULL; + cr->offset_start = 0; + cr->offset_end = csr->crc_len + 4; + + csr->cache_head->filled_head = cr; + csr->cache_head->filled_tail = cr; + + return CSR1212_SUCCESS; +} + +#define CSR1212_KV_KEY(q) (be32_to_cpu(q) >> CSR1212_KV_KEY_SHIFT) +#define CSR1212_KV_KEY_TYPE(q) (CSR1212_KV_KEY(q) >> CSR1212_KV_KEY_TYPE_SHIFT) +#define CSR1212_KV_KEY_ID(q) (CSR1212_KV_KEY(q) & CSR1212_KV_KEY_ID_MASK) +#define CSR1212_KV_VAL_MASK 0xffffff +#define CSR1212_KV_VAL(q) (be32_to_cpu(q) & CSR1212_KV_VAL_MASK) + +static int +csr1212_parse_dir_entry(struct csr1212_keyval *dir, u32 ki, u32 kv_pos) +{ + int ret = CSR1212_SUCCESS; + struct csr1212_keyval *k = NULL; + u32 offset; + bool keep_keyval = true; + + switch (CSR1212_KV_KEY_TYPE(ki)) { + case CSR1212_KV_TYPE_IMMEDIATE: + k = csr1212_new_immediate(CSR1212_KV_KEY_ID(ki), + CSR1212_KV_VAL(ki)); + if (!k) { + ret = -ENOMEM; + goto out; + } + /* Don't keep local reference when parsing. */ + keep_keyval = false; + break; + + case CSR1212_KV_TYPE_CSR_OFFSET: + k = csr1212_new_csr_offset(CSR1212_KV_KEY_ID(ki), + CSR1212_KV_VAL(ki)); + if (!k) { + ret = -ENOMEM; + goto out; + } + /* Don't keep local reference when parsing. */ + keep_keyval = false; + break; + + default: + /* Compute the offset from 0xffff f000 0000. */ + offset = quads_to_bytes(CSR1212_KV_VAL(ki)) + kv_pos; + if (offset == kv_pos) { + /* Uh-oh. Can't have a relative offset of 0 for Leaves + * or Directories. The Config ROM image is most likely + * messed up, so we'll just abort here. */ + ret = -EIO; + goto out; + } + + k = csr1212_find_keyval_offset(dir, offset); + + if (k) + break; /* Found it. */ + + if (CSR1212_KV_KEY_TYPE(ki) == CSR1212_KV_TYPE_DIRECTORY) + k = csr1212_new_directory(CSR1212_KV_KEY_ID(ki)); + else + k = csr1212_new_leaf(CSR1212_KV_KEY_ID(ki), NULL, 0); + + if (!k) { + ret = -ENOMEM; + goto out; + } + /* Don't keep local reference when parsing. */ + keep_keyval = false; + /* Contents not read yet so it's not valid. */ + k->valid = 0; + k->offset = offset; + + k->prev = dir; + k->next = dir->next; + dir->next->prev = k; + dir->next = k; + } + ret = __csr1212_attach_keyval_to_directory(dir, k, keep_keyval); +out: + if (ret != CSR1212_SUCCESS && k != NULL) + free_keyval(k); + return ret; +} + +int csr1212_parse_keyval(struct csr1212_keyval *kv, + struct csr1212_csr_rom_cache *cache) +{ + struct csr1212_keyval_img *kvi; + int i; + int ret = CSR1212_SUCCESS; + int kvi_len; + + kvi = (struct csr1212_keyval_img*) + &cache->data[bytes_to_quads(kv->offset - cache->offset)]; + kvi_len = be16_to_cpu(kvi->length); + + /* GUID is wrong in here in case of extended ROM. We don't care. */ + csr1212_check_crc(kvi->data, kvi_len, kvi->crc, &cache->data[3]); + + switch (kv->key.type) { + case CSR1212_KV_TYPE_DIRECTORY: + for (i = 0; i < kvi_len; i++) { + u32 ki = kvi->data[i]; + + /* Some devices put null entries in their unit + * directories. If we come across such an entry, + * then skip it. */ + if (ki == 0x0) + continue; + ret = csr1212_parse_dir_entry(kv, ki, + kv->offset + quads_to_bytes(i + 1)); + } + kv->value.directory.len = kvi_len; + break; + + case CSR1212_KV_TYPE_LEAF: + if (kv->key.id != CSR1212_KV_ID_EXTENDED_ROM) { + size_t size = quads_to_bytes(kvi_len); + + kv->value.leaf.data = CSR1212_MALLOC(size); + if (!kv->value.leaf.data) { + ret = -ENOMEM; + goto out; + } + + kv->value.leaf.len = kvi_len; + memcpy(kv->value.leaf.data, kvi->data, size); + } + break; + } + + kv->valid = 1; +out: + return ret; +} + +static int +csr1212_read_keyval(struct csr1212_csr *csr, struct csr1212_keyval *kv) +{ + struct csr1212_cache_region *cr, *ncr, *newcr = NULL; + struct csr1212_keyval_img *kvi = NULL; + struct csr1212_csr_rom_cache *cache; + int cache_index; + u64 addr; + u32 *cache_ptr; + u16 kv_len = 0; + + BUG_ON(!csr || !kv || csr->max_rom < 1); + + /* First find which cache the data should be in (or go in if not read + * yet). */ + for (cache = csr->cache_head; cache; cache = cache->next) + if (kv->offset >= cache->offset && + kv->offset < (cache->offset + cache->size)) + break; + + if (!cache) { + u32 q, cache_size; + + /* Only create a new cache for Extended ROM leaves. */ + if (kv->key.id != CSR1212_KV_ID_EXTENDED_ROM) + return -EINVAL; + + if (csr->ops->bus_read(csr, + CSR1212_REGISTER_SPACE_BASE + kv->offset, + sizeof(u32), &q, csr->private)) + return -EIO; + + kv->value.leaf.len = be32_to_cpu(q) >> 16; + + cache_size = (quads_to_bytes(kv->value.leaf.len + 1) + + (csr->max_rom - 1)) & ~(csr->max_rom - 1); + + cache = csr1212_rom_cache_malloc(kv->offset, cache_size); + if (!cache) + return -ENOMEM; + + kv->value.leaf.data = &cache->data[1]; + csr->cache_tail->next = cache; + cache->prev = csr->cache_tail; + cache->next = NULL; + csr->cache_tail = cache; + cache->filled_head = + CSR1212_MALLOC(sizeof(*cache->filled_head)); + if (!cache->filled_head) + return -ENOMEM; + + cache->filled_head->offset_start = 0; + cache->filled_head->offset_end = sizeof(u32); + cache->filled_tail = cache->filled_head; + cache->filled_head->next = NULL; + cache->filled_head->prev = NULL; + cache->data[0] = q; + + /* Don't read the entire extended ROM now. Pieces of it will + * be read when entries inside it are read. */ + return csr1212_parse_keyval(kv, cache); + } + + cache_index = kv->offset - cache->offset; + + /* Now seach read portions of the cache to see if it is there. */ + for (cr = cache->filled_head; cr; cr = cr->next) { + if (cache_index < cr->offset_start) { + newcr = CSR1212_MALLOC(sizeof(*newcr)); + if (!newcr) + return -ENOMEM; + + newcr->offset_start = cache_index & ~(csr->max_rom - 1); + newcr->offset_end = newcr->offset_start; + newcr->next = cr; + newcr->prev = cr->prev; + cr->prev = newcr; + cr = newcr; + break; + } else if ((cache_index >= cr->offset_start) && + (cache_index < cr->offset_end)) { + kvi = (struct csr1212_keyval_img*) + (&cache->data[bytes_to_quads(cache_index)]); + kv_len = quads_to_bytes(be16_to_cpu(kvi->length) + 1); + break; + } else if (cache_index == cr->offset_end) { + break; + } + } + + if (!cr) { + cr = cache->filled_tail; + newcr = CSR1212_MALLOC(sizeof(*newcr)); + if (!newcr) + return -ENOMEM; + + newcr->offset_start = cache_index & ~(csr->max_rom - 1); + newcr->offset_end = newcr->offset_start; + newcr->prev = cr; + newcr->next = cr->next; + cr->next = newcr; + cr = newcr; + cache->filled_tail = newcr; + } + + while(!kvi || cr->offset_end < cache_index + kv_len) { + cache_ptr = &cache->data[bytes_to_quads(cr->offset_end & + ~(csr->max_rom - 1))]; + + addr = (CSR1212_CSR_ARCH_REG_SPACE_BASE + cache->offset + + cr->offset_end) & ~(csr->max_rom - 1); + + if (csr->ops->bus_read(csr, addr, csr->max_rom, cache_ptr, + csr->private)) { + if (csr->max_rom == 4) + /* We've got problems! */ + return -EIO; + + /* Apperently the max_rom value was a lie, set it to + * do quadlet reads and try again. */ + csr->max_rom = 4; + continue; + } + + cr->offset_end += csr->max_rom - (cr->offset_end & + (csr->max_rom - 1)); + + if (!kvi && (cr->offset_end > cache_index)) { + kvi = (struct csr1212_keyval_img*) + (&cache->data[bytes_to_quads(cache_index)]); + kv_len = quads_to_bytes(be16_to_cpu(kvi->length) + 1); + } + + if ((kv_len + (kv->offset - cache->offset)) > cache->size) { + /* The Leaf or Directory claims its length extends + * beyond the ConfigROM image region and thus beyond the + * end of our cache region. Therefore, we abort now + * rather than seg faulting later. */ + return -EIO; + } + + ncr = cr->next; + + if (ncr && (cr->offset_end >= ncr->offset_start)) { + /* consolidate region entries */ + ncr->offset_start = cr->offset_start; + + if (cr->prev) + cr->prev->next = cr->next; + ncr->prev = cr->prev; + if (cache->filled_head == cr) + cache->filled_head = ncr; + CSR1212_FREE(cr); + cr = ncr; + } + } + + return csr1212_parse_keyval(kv, cache); +} + +struct csr1212_keyval * +csr1212_get_keyval(struct csr1212_csr *csr, struct csr1212_keyval *kv) +{ + if (!kv) + return NULL; + if (!kv->valid) + if (csr1212_read_keyval(csr, kv) != CSR1212_SUCCESS) + return NULL; + return kv; +} + +int csr1212_parse_csr(struct csr1212_csr *csr) +{ + static const int mr_map[] = { 4, 64, 1024, 0 }; + struct csr1212_dentry *dentry; + int ret; + + BUG_ON(!csr || !csr->ops || !csr->ops->bus_read); + + ret = csr1212_parse_bus_info_block(csr); + if (ret != CSR1212_SUCCESS) + return ret; + + if (!csr->ops->get_max_rom) { + csr->max_rom = mr_map[0]; /* default value */ + } else { + int i = csr->ops->get_max_rom(csr->bus_info_data, + csr->private); + if (i & ~0x3) + return -EINVAL; + csr->max_rom = mr_map[i]; + } + + csr->cache_head->layout_head = csr->root_kv; + csr->cache_head->layout_tail = csr->root_kv; + + csr->root_kv->offset = (CSR1212_CONFIG_ROM_SPACE_BASE & 0xffff) + + csr->bus_info_len; + + csr->root_kv->valid = 0; + csr->root_kv->next = csr->root_kv; + csr->root_kv->prev = csr->root_kv; + ret = csr1212_read_keyval(csr, csr->root_kv); + if (ret != CSR1212_SUCCESS) + return ret; + + /* Scan through the Root directory finding all extended ROM regions + * and make cache regions for them */ + for (dentry = csr->root_kv->value.directory.dentries_head; + dentry; dentry = dentry->next) { + if (dentry->kv->key.id == CSR1212_KV_ID_EXTENDED_ROM && + !dentry->kv->valid) { + ret = csr1212_read_keyval(csr, dentry->kv); + if (ret != CSR1212_SUCCESS) + return ret; + } + } + + return CSR1212_SUCCESS; +} diff --git a/drivers/ieee1394/csr1212.h b/drivers/ieee1394/csr1212.h new file mode 100644 index 0000000..043039f --- /dev/null +++ b/drivers/ieee1394/csr1212.h @@ -0,0 +1,388 @@ +/* + * csr1212.h -- IEEE 1212 Control and Status Register support for Linux + * + * Copyright (C) 2003 Francois Retief <fgretief@sun.ac.za> + * Steve Kinneberg <kinnebergsteve@acmsystems.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __CSR1212_H__ +#define __CSR1212_H__ + +#include <linux/types.h> +#include <linux/slab.h> +#include <asm/atomic.h> + +#define CSR1212_MALLOC(size) kmalloc((size), GFP_KERNEL) +#define CSR1212_FREE(ptr) kfree(ptr) + +#define CSR1212_SUCCESS (0) + + +/* CSR 1212 key types */ +#define CSR1212_KV_TYPE_IMMEDIATE 0 +#define CSR1212_KV_TYPE_CSR_OFFSET 1 +#define CSR1212_KV_TYPE_LEAF 2 +#define CSR1212_KV_TYPE_DIRECTORY 3 + + +/* CSR 1212 key ids */ +#define CSR1212_KV_ID_DESCRIPTOR 0x01 +#define CSR1212_KV_ID_BUS_DEPENDENT_INFO 0x02 +#define CSR1212_KV_ID_VENDOR 0x03 +#define CSR1212_KV_ID_HARDWARE_VERSION 0x04 +#define CSR1212_KV_ID_MODULE 0x07 +#define CSR1212_KV_ID_NODE_CAPABILITIES 0x0C +#define CSR1212_KV_ID_EUI_64 0x0D +#define CSR1212_KV_ID_UNIT 0x11 +#define CSR1212_KV_ID_SPECIFIER_ID 0x12 +#define CSR1212_KV_ID_VERSION 0x13 +#define CSR1212_KV_ID_DEPENDENT_INFO 0x14 +#define CSR1212_KV_ID_UNIT_LOCATION 0x15 +#define CSR1212_KV_ID_MODEL 0x17 +#define CSR1212_KV_ID_INSTANCE 0x18 +#define CSR1212_KV_ID_KEYWORD 0x19 +#define CSR1212_KV_ID_FEATURE 0x1A +#define CSR1212_KV_ID_EXTENDED_ROM 0x1B +#define CSR1212_KV_ID_EXTENDED_KEY_SPECIFIER_ID 0x1C +#define CSR1212_KV_ID_EXTENDED_KEY 0x1D +#define CSR1212_KV_ID_EXTENDED_DATA 0x1E +#define CSR1212_KV_ID_MODIFIABLE_DESCRIPTOR 0x1F +#define CSR1212_KV_ID_DIRECTORY_ID 0x20 +#define CSR1212_KV_ID_REVISION 0x21 + + +/* IEEE 1212 Address space map */ +#define CSR1212_ALL_SPACE_BASE (0x000000000000ULL) +#define CSR1212_ALL_SPACE_SIZE (1ULL << 48) +#define CSR1212_ALL_SPACE_END (CSR1212_ALL_SPACE_BASE + CSR1212_ALL_SPACE_SIZE) + +#define CSR1212_MEMORY_SPACE_BASE (0x000000000000ULL) +#define CSR1212_MEMORY_SPACE_SIZE ((256ULL * (1ULL << 40)) - (512ULL * (1ULL << 20))) +#define CSR1212_MEMORY_SPACE_END (CSR1212_MEMORY_SPACE_BASE + CSR1212_MEMORY_SPACE_SIZE) + +#define CSR1212_PRIVATE_SPACE_BASE (0xffffe0000000ULL) +#define CSR1212_PRIVATE_SPACE_SIZE (256ULL * (1ULL << 20)) +#define CSR1212_PRIVATE_SPACE_END (CSR1212_PRIVATE_SPACE_BASE + CSR1212_PRIVATE_SPACE_SIZE) + +#define CSR1212_REGISTER_SPACE_BASE (0xfffff0000000ULL) +#define CSR1212_REGISTER_SPACE_SIZE (256ULL * (1ULL << 20)) +#define CSR1212_REGISTER_SPACE_END (CSR1212_REGISTER_SPACE_BASE + CSR1212_REGISTER_SPACE_SIZE) + +#define CSR1212_CSR_ARCH_REG_SPACE_BASE (0xfffff0000000ULL) +#define CSR1212_CSR_ARCH_REG_SPACE_SIZE (512) +#define CSR1212_CSR_ARCH_REG_SPACE_END (CSR1212_CSR_ARCH_REG_SPACE_BASE + CSR1212_CSR_ARCH_REG_SPACE_SIZE) +#define CSR1212_CSR_ARCH_REG_SPACE_OFFSET (CSR1212_CSR_ARCH_REG_SPACE_BASE - CSR1212_REGISTER_SPACE_BASE) + +#define CSR1212_CSR_BUS_DEP_REG_SPACE_BASE (0xfffff0000200ULL) +#define CSR1212_CSR_BUS_DEP_REG_SPACE_SIZE (512) +#define CSR1212_CSR_BUS_DEP_REG_SPACE_END (CSR1212_CSR_BUS_DEP_REG_SPACE_BASE + CSR1212_CSR_BUS_DEP_REG_SPACE_SIZE) +#define CSR1212_CSR_BUS_DEP_REG_SPACE_OFFSET (CSR1212_CSR_BUS_DEP_REG_SPACE_BASE - CSR1212_REGISTER_SPACE_BASE) + +#define CSR1212_CONFIG_ROM_SPACE_BASE (0xfffff0000400ULL) +#define CSR1212_CONFIG_ROM_SPACE_SIZE (1024) +#define CSR1212_CONFIG_ROM_SPACE_END (CSR1212_CONFIG_ROM_SPACE_BASE + CSR1212_CONFIG_ROM_SPACE_SIZE) +#define CSR1212_CONFIG_ROM_SPACE_OFFSET (CSR1212_CONFIG_ROM_SPACE_BASE - CSR1212_REGISTER_SPACE_BASE) + +#define CSR1212_UNITS_SPACE_BASE (0xfffff0000800ULL) +#define CSR1212_UNITS_SPACE_SIZE ((256ULL * (1ULL << 20)) - 2048) +#define CSR1212_UNITS_SPACE_END (CSR1212_UNITS_SPACE_BASE + CSR1212_UNITS_SPACE_SIZE) +#define CSR1212_UNITS_SPACE_OFFSET (CSR1212_UNITS_SPACE_BASE - CSR1212_REGISTER_SPACE_BASE) + +#define CSR1212_INVALID_ADDR_SPACE -1 + + +/* Config ROM image structures */ +struct csr1212_bus_info_block_img { + u8 length; + u8 crc_length; + u16 crc; + + /* Must be last */ + u32 data[0]; /* older gcc can't handle [] which is standard */ +}; + +struct csr1212_leaf { + int len; + u32 *data; +}; + +struct csr1212_dentry { + struct csr1212_dentry *next, *prev; + struct csr1212_keyval *kv; +}; + +struct csr1212_directory { + int len; + struct csr1212_dentry *dentries_head, *dentries_tail; +}; + +struct csr1212_keyval { + struct { + u8 type; + u8 id; + } key; + union { + u32 immediate; + u32 csr_offset; + struct csr1212_leaf leaf; + struct csr1212_directory directory; + } value; + struct csr1212_keyval *associate; + atomic_t refcnt; + + /* used in generating and/or parsing CSR image */ + struct csr1212_keyval *next, *prev; /* flat list of CSR elements */ + u32 offset; /* position in CSR from 0xffff f000 0000 */ + u8 valid; /* flag indicating keyval has valid data*/ +}; + + +struct csr1212_cache_region { + struct csr1212_cache_region *next, *prev; + u32 offset_start; /* inclusive */ + u32 offset_end; /* exclusive */ +}; + +struct csr1212_csr_rom_cache { + struct csr1212_csr_rom_cache *next, *prev; + struct csr1212_cache_region *filled_head, *filled_tail; + struct csr1212_keyval *layout_head, *layout_tail; + size_t size; + u32 offset; + struct csr1212_keyval *ext_rom; + size_t len; + + /* Must be last */ + u32 data[0]; /* older gcc can't handle [] which is standard */ +}; + +struct csr1212_csr { + size_t bus_info_len; /* bus info block length in bytes */ + size_t crc_len; /* crc length in bytes */ + u32 *bus_info_data; /* bus info data incl bus name and EUI */ + + void *private; /* private, bus specific data */ + struct csr1212_bus_ops *ops; + + struct csr1212_keyval *root_kv; + + int max_rom; /* max bytes readable in Config ROM region */ + + /* Items below used for image parsing and generation */ + struct csr1212_csr_rom_cache *cache_head, *cache_tail; +}; + +struct csr1212_bus_ops { + /* This function is used by csr1212 to read additional information + * from remote nodes when parsing a Config ROM (i.e., read Config ROM + * entries located in the Units Space. Must return 0 on success + * anything else indicates an error. */ + int (*bus_read) (struct csr1212_csr *csr, u64 addr, + u16 length, void *buffer, void *private); + + /* This function is used by csr1212 to allocate a region in units space + * in the event that Config ROM entries don't all fit in the predefined + * 1K region. The void *private parameter is private member of struct + * csr1212_csr. */ + u64 (*allocate_addr_range) (u64 size, u32 alignment, void *private); + + /* This function is used by csr1212 to release a region in units space + * that is no longer needed. */ + void (*release_addr) (u64 addr, void *private); + + /* This function is used by csr1212 to determine the max read request + * supported by a remote node when reading the ConfigROM space. Must + * return 0, 1, or 2 per IEEE 1212. */ + int (*get_max_rom) (u32 *bus_info, void *private); +}; + + +/* Descriptor Leaf manipulation macros */ +#define CSR1212_DESCRIPTOR_LEAF_TYPE_SHIFT 24 +#define CSR1212_DESCRIPTOR_LEAF_SPECIFIER_ID_MASK 0xffffff +#define CSR1212_DESCRIPTOR_LEAF_OVERHEAD (1 * sizeof(u32)) + +#define CSR1212_DESCRIPTOR_LEAF_TYPE(kv) \ + (be32_to_cpu((kv)->value.leaf.data[0]) >> \ + CSR1212_DESCRIPTOR_LEAF_TYPE_SHIFT) +#define CSR1212_DESCRIPTOR_LEAF_SPECIFIER_ID(kv) \ + (be32_to_cpu((kv)->value.leaf.data[0]) & \ + CSR1212_DESCRIPTOR_LEAF_SPECIFIER_ID_MASK) + + +/* Text Descriptor Leaf manipulation macros */ +#define CSR1212_TEXTUAL_DESCRIPTOR_LEAF_WIDTH_SHIFT 28 +#define CSR1212_TEXTUAL_DESCRIPTOR_LEAF_WIDTH_MASK 0xf /* after shift */ +#define CSR1212_TEXTUAL_DESCRIPTOR_LEAF_CHAR_SET_SHIFT 16 +#define CSR1212_TEXTUAL_DESCRIPTOR_LEAF_CHAR_SET_MASK 0xfff /* after shift */ +#define CSR1212_TEXTUAL_DESCRIPTOR_LEAF_LANGUAGE_MASK 0xffff +#define CSR1212_TEXTUAL_DESCRIPTOR_LEAF_OVERHEAD (1 * sizeof(u32)) + +#define CSR1212_TEXTUAL_DESCRIPTOR_LEAF_WIDTH(kv) \ + (be32_to_cpu((kv)->value.leaf.data[1]) >> \ + CSR1212_TEXTUAL_DESCRIPTOR_LEAF_WIDTH_SHIFT) +#define CSR1212_TEXTUAL_DESCRIPTOR_LEAF_CHAR_SET(kv) \ + ((be32_to_cpu((kv)->value.leaf.data[1]) >> \ + CSR1212_TEXTUAL_DESCRIPTOR_LEAF_CHAR_SET_SHIFT) & \ + CSR1212_TEXTUAL_DESCRIPTOR_LEAF_CHAR_SET_MASK) +#define CSR1212_TEXTUAL_DESCRIPTOR_LEAF_LANGUAGE(kv) \ + (be32_to_cpu((kv)->value.leaf.data[1]) & \ + CSR1212_TEXTUAL_DESCRIPTOR_LEAF_LANGUAGE_MASK) +#define CSR1212_TEXTUAL_DESCRIPTOR_LEAF_DATA(kv) \ + (&((kv)->value.leaf.data[2])) + + +/* The following 2 function are for creating new Configuration ROM trees. The + * first function is used for both creating local trees and parsing remote + * trees. The second function adds pertinent information to local Configuration + * ROM trees - namely data for the bus information block. */ +extern struct csr1212_csr *csr1212_create_csr(struct csr1212_bus_ops *ops, + size_t bus_info_size, + void *private); +extern void csr1212_init_local_csr(struct csr1212_csr *csr, + const u32 *bus_info_data, int max_rom); + + +/* Destroy a Configuration ROM tree and release all memory taken by the tree. */ +extern void csr1212_destroy_csr(struct csr1212_csr *csr); + + +/* The following set of functions are fore creating new keyvals for placement in + * a Configuration ROM tree. Code that creates new keyvals with these functions + * must release those keyvals with csr1212_release_keyval() when they are no + * longer needed. */ +extern struct csr1212_keyval *csr1212_new_immediate(u8 key, u32 value); +extern struct csr1212_keyval *csr1212_new_directory(u8 key); +extern struct csr1212_keyval *csr1212_new_string_descriptor_leaf(const char *s); + + +/* The following function manages association between keyvals. Typically, + * Descriptor Leaves and Directories will be associated with another keyval and + * it is desirable for the Descriptor keyval to be place immediately after the + * keyval that it is associated with. + * Take care with subsequent ROM modifications: There is no function to remove + * previously specified associations. + */ +extern void csr1212_associate_keyval(struct csr1212_keyval *kv, + struct csr1212_keyval *associate); + + +/* The following functions manage the association of a keyval and directories. + * A keyval may be attached to more than one directory. */ +extern int csr1212_attach_keyval_to_directory(struct csr1212_keyval *dir, + struct csr1212_keyval *kv); +extern void csr1212_detach_keyval_from_directory(struct csr1212_keyval *dir, + struct csr1212_keyval *kv); + + +/* Creates a complete Configuration ROM image in the list of caches available + * via csr->cache_head. */ +extern int csr1212_generate_csr_image(struct csr1212_csr *csr); + + +/* This is a convience function for reading a block of data out of one of the + * caches in the csr->cache_head list. */ +extern int csr1212_read(struct csr1212_csr *csr, u32 offset, void *buffer, + u32 len); + + +/* The following functions are in place for parsing Configuration ROM images. + * csr1212_parse_keyval() is used should there be a need to directly parse a + * Configuration ROM directly. */ +extern int csr1212_parse_keyval(struct csr1212_keyval *kv, + struct csr1212_csr_rom_cache *cache); +extern int csr1212_parse_csr(struct csr1212_csr *csr); + + +/* This function allocates a new cache which may be used for either parsing or + * generating sub-sets of Configuration ROM images. */ +static inline struct csr1212_csr_rom_cache * +csr1212_rom_cache_malloc(u32 offset, size_t size) +{ + struct csr1212_csr_rom_cache *cache; + + cache = CSR1212_MALLOC(sizeof(*cache) + size); + if (!cache) + return NULL; + + cache->next = NULL; + cache->prev = NULL; + cache->filled_head = NULL; + cache->filled_tail = NULL; + cache->layout_head = NULL; + cache->layout_tail = NULL; + cache->offset = offset; + cache->size = size; + cache->ext_rom = NULL; + + return cache; +} + + +/* This function ensures that a keyval contains data when referencing a keyval + * created by parsing a Configuration ROM. */ +extern struct csr1212_keyval * +csr1212_get_keyval(struct csr1212_csr *csr, struct csr1212_keyval *kv); + + +/* This function increments the reference count for a keyval should there be a + * need for code to retain a keyval that has been parsed. */ +static inline void csr1212_keep_keyval(struct csr1212_keyval *kv) +{ + atomic_inc(&kv->refcnt); + smp_mb__after_atomic_inc(); +} + + +/* This function decrements a keyval's reference count and will destroy the + * keyval when there are no more users of the keyval. This should be called by + * any code that calls csr1212_keep_keyval() or any of the keyval creation + * routines csr1212_new_*(). */ +extern void csr1212_release_keyval(struct csr1212_keyval *kv); + + +/* + * This macro allows for looping over the keyval entries in a directory and it + * ensures that keyvals from remote ConfigROMs are parsed properly. + * + * struct csr1212_csr *_csr points to the CSR associated with dir. + * struct csr1212_keyval *_kv points to the current keyval (loop index). + * struct csr1212_keyval *_dir points to the directory to be looped. + * struct csr1212_dentry *_pos is used internally for indexing. + * + * kv will be NULL upon exit of the loop. + */ +#define csr1212_for_each_dir_entry(_csr, _kv, _dir, _pos) \ + for (csr1212_get_keyval((_csr), (_dir)), \ + _pos = (_dir)->value.directory.dentries_head, \ + _kv = (_pos) ? csr1212_get_keyval((_csr), _pos->kv) : NULL;\ + (_kv) && (_pos); \ + (_kv->associate == NULL) ? \ + ((_pos = _pos->next), (_kv = (_pos) ? \ + csr1212_get_keyval((_csr), _pos->kv) : \ + NULL)) : \ + (_kv = csr1212_get_keyval((_csr), _kv->associate))) + +#endif /* __CSR1212_H__ */ diff --git a/drivers/ieee1394/dma.c b/drivers/ieee1394/dma.c new file mode 100644 index 0000000..1aba8c1 --- /dev/null +++ b/drivers/ieee1394/dma.c @@ -0,0 +1,290 @@ +/* + * DMA region bookkeeping routines + * + * Copyright (C) 2002 Maas Digital LLC + * + * This code is licensed under the GPL. See the file COPYING in the root + * directory of the kernel sources for details. + */ + +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/scatterlist.h> + +#include "dma.h" + +/* dma_prog_region */ + +void dma_prog_region_init(struct dma_prog_region *prog) +{ + prog->kvirt = NULL; + prog->dev = NULL; + prog->n_pages = 0; + prog->bus_addr = 0; +} + +int dma_prog_region_alloc(struct dma_prog_region *prog, unsigned long n_bytes, + struct pci_dev *dev) +{ + /* round up to page size */ + n_bytes = PAGE_ALIGN(n_bytes); + + prog->n_pages = n_bytes >> PAGE_SHIFT; + + prog->kvirt = pci_alloc_consistent(dev, n_bytes, &prog->bus_addr); + if (!prog->kvirt) { + printk(KERN_ERR + "dma_prog_region_alloc: pci_alloc_consistent() failed\n"); + dma_prog_region_free(prog); + return -ENOMEM; + } + + prog->dev = dev; + + return 0; +} + +void dma_prog_region_free(struct dma_prog_region *prog) +{ + if (prog->kvirt) { + pci_free_consistent(prog->dev, prog->n_pages << PAGE_SHIFT, + prog->kvirt, prog->bus_addr); + } + + prog->kvirt = NULL; + prog->dev = NULL; + prog->n_pages = 0; + prog->bus_addr = 0; +} + +/* dma_region */ + +/** + * dma_region_init - clear out all fields but do not allocate anything + */ +void dma_region_init(struct dma_region *dma) +{ + dma->kvirt = NULL; + dma->dev = NULL; + dma->n_pages = 0; + dma->n_dma_pages = 0; + dma->sglist = NULL; +} + +/** + * dma_region_alloc - allocate the buffer and map it to the IOMMU + */ +int dma_region_alloc(struct dma_region *dma, unsigned long n_bytes, + struct pci_dev *dev, int direction) +{ + unsigned int i; + + /* round up to page size */ + n_bytes = PAGE_ALIGN(n_bytes); + + dma->n_pages = n_bytes >> PAGE_SHIFT; + + dma->kvirt = vmalloc_32(n_bytes); + if (!dma->kvirt) { + printk(KERN_ERR "dma_region_alloc: vmalloc_32() failed\n"); + goto err; + } + + /* Clear the ram out, no junk to the user */ + memset(dma->kvirt, 0, n_bytes); + + /* allocate scatter/gather list */ + dma->sglist = vmalloc(dma->n_pages * sizeof(*dma->sglist)); + if (!dma->sglist) { + printk(KERN_ERR "dma_region_alloc: vmalloc(sglist) failed\n"); + goto err; + } + + sg_init_table(dma->sglist, dma->n_pages); + + /* fill scatter/gather list with pages */ + for (i = 0; i < dma->n_pages; i++) { + unsigned long va = + (unsigned long)dma->kvirt + (i << PAGE_SHIFT); + + sg_set_page(&dma->sglist[i], vmalloc_to_page((void *)va), + PAGE_SIZE, 0); + } + + /* map sglist to the IOMMU */ + dma->n_dma_pages = + pci_map_sg(dev, dma->sglist, dma->n_pages, direction); + + if (dma->n_dma_pages == 0) { + printk(KERN_ERR "dma_region_alloc: pci_map_sg() failed\n"); + goto err; + } + + dma->dev = dev; + dma->direction = direction; + + return 0; + + err: + dma_region_free(dma); + return -ENOMEM; +} + +/** + * dma_region_free - unmap and free the buffer + */ +void dma_region_free(struct dma_region *dma) +{ + if (dma->n_dma_pages) { + pci_unmap_sg(dma->dev, dma->sglist, dma->n_pages, + dma->direction); + dma->n_dma_pages = 0; + dma->dev = NULL; + } + + vfree(dma->sglist); + dma->sglist = NULL; + + vfree(dma->kvirt); + dma->kvirt = NULL; + dma->n_pages = 0; +} + +/* find the scatterlist index and remaining offset corresponding to a + given offset from the beginning of the buffer */ +static inline int dma_region_find(struct dma_region *dma, unsigned long offset, + unsigned int start, unsigned long *rem) +{ + int i; + unsigned long off = offset; + + for (i = start; i < dma->n_dma_pages; i++) { + if (off < sg_dma_len(&dma->sglist[i])) { + *rem = off; + break; + } + + off -= sg_dma_len(&dma->sglist[i]); + } + + BUG_ON(i >= dma->n_dma_pages); + + return i; +} + +/** + * dma_region_offset_to_bus - get bus address of an offset within a DMA region + * + * Returns the DMA bus address of the byte with the given @offset relative to + * the beginning of the @dma. + */ +dma_addr_t dma_region_offset_to_bus(struct dma_region * dma, + unsigned long offset) +{ + unsigned long rem = 0; + + struct scatterlist *sg = + &dma->sglist[dma_region_find(dma, offset, 0, &rem)]; + return sg_dma_address(sg) + rem; +} + +/** + * dma_region_sync_for_cpu - sync the CPU's view of the buffer + */ +void dma_region_sync_for_cpu(struct dma_region *dma, unsigned long offset, + unsigned long len) +{ + int first, last; + unsigned long rem = 0; + + if (!len) + len = 1; + + first = dma_region_find(dma, offset, 0, &rem); + last = dma_region_find(dma, rem + len - 1, first, &rem); + + pci_dma_sync_sg_for_cpu(dma->dev, &dma->sglist[first], last - first + 1, + dma->direction); +} + +/** + * dma_region_sync_for_device - sync the IO bus' view of the buffer + */ +void dma_region_sync_for_device(struct dma_region *dma, unsigned long offset, + unsigned long len) +{ + int first, last; + unsigned long rem = 0; + + if (!len) + len = 1; + + first = dma_region_find(dma, offset, 0, &rem); + last = dma_region_find(dma, rem + len - 1, first, &rem); + + pci_dma_sync_sg_for_device(dma->dev, &dma->sglist[first], + last - first + 1, dma->direction); +} + +#ifdef CONFIG_MMU + +static int dma_region_pagefault(struct vm_area_struct *vma, + struct vm_fault *vmf) +{ + struct dma_region *dma = (struct dma_region *)vma->vm_private_data; + + if (!dma->kvirt) + return VM_FAULT_SIGBUS; + + if (vmf->pgoff >= dma->n_pages) + return VM_FAULT_SIGBUS; + + vmf->page = vmalloc_to_page(dma->kvirt + (vmf->pgoff << PAGE_SHIFT)); + get_page(vmf->page); + return 0; +} + +static struct vm_operations_struct dma_region_vm_ops = { + .fault = dma_region_pagefault, +}; + +/** + * dma_region_mmap - map the buffer into a user space process + */ +int dma_region_mmap(struct dma_region *dma, struct file *file, + struct vm_area_struct *vma) +{ + unsigned long size; + + if (!dma->kvirt) + return -EINVAL; + + /* must be page-aligned (XXX: comment is wrong, we could allow pgoff) */ + if (vma->vm_pgoff != 0) + return -EINVAL; + + /* check the length */ + size = vma->vm_end - vma->vm_start; + if (size > (dma->n_pages << PAGE_SHIFT)) + return -EINVAL; + + vma->vm_ops = &dma_region_vm_ops; + vma->vm_private_data = dma; + vma->vm_file = file; + vma->vm_flags |= VM_RESERVED | VM_ALWAYSDUMP; + + return 0; +} + +#else /* CONFIG_MMU */ + +int dma_region_mmap(struct dma_region *dma, struct file *file, + struct vm_area_struct *vma) +{ + return -EINVAL; +} + +#endif /* CONFIG_MMU */ diff --git a/drivers/ieee1394/dma.h b/drivers/ieee1394/dma.h new file mode 100644 index 0000000..2727bcd --- /dev/null +++ b/drivers/ieee1394/dma.h @@ -0,0 +1,88 @@ +/* + * DMA region bookkeeping routines + * + * Copyright (C) 2002 Maas Digital LLC + * + * This code is licensed under the GPL. See the file COPYING in the root + * directory of the kernel sources for details. + */ + +#ifndef IEEE1394_DMA_H +#define IEEE1394_DMA_H + +#include <asm/types.h> + +struct pci_dev; +struct scatterlist; +struct vm_area_struct; + +/** + * struct dma_prog_region - small contiguous DMA buffer + * @kvirt: kernel virtual address + * @dev: PCI device + * @n_pages: number of kernel pages + * @bus_addr: base bus address + * + * a small, physically contiguous DMA buffer with random-access, synchronous + * usage characteristics + */ +struct dma_prog_region { + unsigned char *kvirt; + struct pci_dev *dev; + unsigned int n_pages; + dma_addr_t bus_addr; +}; + +/* clear out all fields but do not allocate any memory */ +void dma_prog_region_init(struct dma_prog_region *prog); +int dma_prog_region_alloc(struct dma_prog_region *prog, unsigned long n_bytes, + struct pci_dev *dev); +void dma_prog_region_free(struct dma_prog_region *prog); + +static inline dma_addr_t dma_prog_region_offset_to_bus( + struct dma_prog_region *prog, unsigned long offset) +{ + return prog->bus_addr + offset; +} + +/** + * struct dma_region - large non-contiguous DMA buffer + * @virt: kernel virtual address + * @dev: PCI device + * @n_pages: number of kernel pages + * @n_dma_pages: number of IOMMU pages + * @sglist: IOMMU mapping + * @direction: PCI_DMA_TODEVICE, etc. + * + * a large, non-physically-contiguous DMA buffer with streaming, asynchronous + * usage characteristics + */ +struct dma_region { + unsigned char *kvirt; + struct pci_dev *dev; + unsigned int n_pages; + unsigned int n_dma_pages; + struct scatterlist *sglist; + int direction; +}; + +void dma_region_init(struct dma_region *dma); +int dma_region_alloc(struct dma_region *dma, unsigned long n_bytes, + struct pci_dev *dev, int direction); +void dma_region_free(struct dma_region *dma); +void dma_region_sync_for_cpu(struct dma_region *dma, unsigned long offset, + unsigned long len); +void dma_region_sync_for_device(struct dma_region *dma, unsigned long offset, + unsigned long len); +int dma_region_mmap(struct dma_region *dma, struct file *file, + struct vm_area_struct *vma); +dma_addr_t dma_region_offset_to_bus(struct dma_region *dma, + unsigned long offset); + +/** + * dma_region_i - macro to index into a DMA region (or dma_prog_region) + */ +#define dma_region_i(_dma, _type, _index) \ + ( ((_type*) ((_dma)->kvirt)) + (_index) ) + +#endif /* IEEE1394_DMA_H */ diff --git a/drivers/ieee1394/dv1394-private.h b/drivers/ieee1394/dv1394-private.h new file mode 100644 index 0000000..7d1d284 --- /dev/null +++ b/drivers/ieee1394/dv1394-private.h @@ -0,0 +1,587 @@ +/* + * dv1394-private.h - DV input/output over IEEE 1394 on OHCI chips + * Copyright (C)2001 Daniel Maas <dmaas@dcine.com> + * receive by Dan Dennedy <dan@dennedy.org> + * + * based on: + * video1394.h - driver for OHCI 1394 boards + * Copyright (C)1999,2000 Sebastien Rougeaux <sebastien.rougeaux@anu.edu.au> + * Peter Schlaile <udbz@rz.uni-karlsruhe.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _DV_1394_PRIVATE_H +#define _DV_1394_PRIVATE_H + +#include "ieee1394.h" +#include "ohci1394.h" +#include "dma.h" + +/* data structures private to the dv1394 driver */ +/* none of this is exposed to user-space */ + + +/* + the 8-byte CIP (Common Isochronous Packet) header that precedes + each packet of DV data. + + See the IEC 61883 standard. +*/ + +struct CIP_header { unsigned char b[8]; }; + +static inline void fill_cip_header(struct CIP_header *cip, + unsigned char source_node_id, + unsigned long counter, + enum pal_or_ntsc format, + unsigned long timestamp) +{ + cip->b[0] = source_node_id; + cip->b[1] = 0x78; /* packet size in quadlets (480/4) - even for empty packets! */ + cip->b[2] = 0x00; + cip->b[3] = counter; + + cip->b[4] = 0x80; /* const */ + + switch(format) { + case DV1394_PAL: + cip->b[5] = 0x80; + break; + case DV1394_NTSC: + cip->b[5] = 0x00; + break; + } + + cip->b[6] = timestamp >> 8; + cip->b[7] = timestamp & 0xFF; +} + + + +/* + DMA commands used to program the OHCI's DMA engine + + See the Texas Instruments OHCI 1394 chipset documentation. +*/ + +struct output_more_immediate { u32 q[8]; }; +struct output_more { u32 q[4]; }; +struct output_last { u32 q[4]; }; +struct input_more { u32 q[4]; }; +struct input_last { u32 q[4]; }; + +/* outputs */ + +static inline void fill_output_more_immediate(struct output_more_immediate *omi, + unsigned char tag, + unsigned char channel, + unsigned char sync_tag, + unsigned int payload_size) +{ + omi->q[0] = cpu_to_le32(0x02000000 | 8); /* OUTPUT_MORE_IMMEDIATE; 8 is the size of the IT header */ + omi->q[1] = 0; + omi->q[2] = 0; + omi->q[3] = 0; + + /* IT packet header */ + omi->q[4] = cpu_to_le32( (0x0 << 16) /* IEEE1394_SPEED_100 */ + | (tag << 14) + | (channel << 8) + | (TCODE_ISO_DATA << 4) + | (sync_tag) ); + + /* reserved field; mimic behavior of my Sony DSR-40 */ + omi->q[5] = cpu_to_le32((payload_size << 16) | (0x7F << 8) | 0xA0); + + omi->q[6] = 0; + omi->q[7] = 0; +} + +static inline void fill_output_more(struct output_more *om, + unsigned int data_size, + unsigned long data_phys_addr) +{ + om->q[0] = cpu_to_le32(data_size); + om->q[1] = cpu_to_le32(data_phys_addr); + om->q[2] = 0; + om->q[3] = 0; +} + +static inline void fill_output_last(struct output_last *ol, + int want_timestamp, + int want_interrupt, + unsigned int data_size, + unsigned long data_phys_addr) +{ + u32 temp = 0; + temp |= 1 << 28; /* OUTPUT_LAST */ + + if (want_timestamp) /* controller will update timestamp at DMA time */ + temp |= 1 << 27; + + if (want_interrupt) + temp |= 3 << 20; + + temp |= 3 << 18; /* must take branch */ + temp |= data_size; + + ol->q[0] = cpu_to_le32(temp); + ol->q[1] = cpu_to_le32(data_phys_addr); + ol->q[2] = 0; + ol->q[3] = 0; +} + +/* inputs */ + +static inline void fill_input_more(struct input_more *im, + int want_interrupt, + unsigned int data_size, + unsigned long data_phys_addr) +{ + u32 temp = 2 << 28; /* INPUT_MORE */ + temp |= 8 << 24; /* s = 1, update xferStatus and resCount */ + if (want_interrupt) + temp |= 0 << 20; /* interrupts, i=0 in packet-per-buffer mode */ + temp |= 0x0 << 16; /* disable branch to address for packet-per-buffer mode */ + /* disable wait on sync field, not used in DV :-( */ + temp |= data_size; + + im->q[0] = cpu_to_le32(temp); + im->q[1] = cpu_to_le32(data_phys_addr); + im->q[2] = 0; /* branchAddress and Z not use in packet-per-buffer mode */ + im->q[3] = 0; /* xferStatus & resCount, resCount must be initialize to data_size */ +} + +static inline void fill_input_last(struct input_last *il, + int want_interrupt, + unsigned int data_size, + unsigned long data_phys_addr) +{ + u32 temp = 3 << 28; /* INPUT_LAST */ + temp |= 8 << 24; /* s = 1, update xferStatus and resCount */ + if (want_interrupt) + temp |= 3 << 20; /* enable interrupts */ + temp |= 0xC << 16; /* enable branch to address */ + /* disable wait on sync field, not used in DV :-( */ + temp |= data_size; + + il->q[0] = cpu_to_le32(temp); + il->q[1] = cpu_to_le32(data_phys_addr); + il->q[2] = cpu_to_le32(1); /* branchAddress (filled in later) and Z = 1 descriptor in next block */ + il->q[3] = cpu_to_le32(data_size); /* xferStatus & resCount, resCount must be initialize to data_size */ +} + + + +/* + A "DMA descriptor block" consists of several contiguous DMA commands. + struct DMA_descriptor_block encapsulates all of the commands necessary + to send one packet of DV data. + + There are three different types of these blocks: + + 1) command to send an empty packet (CIP header only, no DV data): + + OUTPUT_MORE-Immediate <-- contains the iso header in-line + OUTPUT_LAST <-- points to the CIP header + + 2) command to send a full packet when the DV data payload does NOT + cross a page boundary: + + OUTPUT_MORE-Immediate <-- contains the iso header in-line + OUTPUT_MORE <-- points to the CIP header + OUTPUT_LAST <-- points to entire DV data payload + + 3) command to send a full packet when the DV payload DOES cross + a page boundary: + + OUTPUT_MORE-Immediate <-- contains the iso header in-line + OUTPUT_MORE <-- points to the CIP header + OUTPUT_MORE <-- points to first part of DV data payload + OUTPUT_LAST <-- points to second part of DV data payload + + This struct describes all three block types using unions. + + !!! It is vital that an even number of these descriptor blocks fit on one + page of memory, since a block cannot cross a page boundary !!! + + */ + +struct DMA_descriptor_block { + + union { + struct { + /* iso header, common to all output block types */ + struct output_more_immediate omi; + + union { + /* empty packet */ + struct { + struct output_last ol; /* CIP header */ + } empty; + + /* full packet */ + struct { + struct output_more om; /* CIP header */ + + union { + /* payload does not cross page boundary */ + struct { + struct output_last ol; /* data payload */ + } nocross; + + /* payload crosses page boundary */ + struct { + struct output_more om; /* data payload */ + struct output_last ol; /* data payload */ + } cross; + } u; + + } full; + } u; + } out; + + struct { + struct input_last il; + } in; + + } u; + + /* ensure that PAGE_SIZE % sizeof(struct DMA_descriptor_block) == 0 + by padding out to 128 bytes */ + u32 __pad__[12]; +}; + + +/* struct frame contains all data associated with one frame in the + ringbuffer these are allocated when the DMA context is initialized + do_dv1394_init(). They are re-used after the card finishes + transmitting the frame. */ + +struct video_card; /* forward declaration */ + +struct frame { + + /* points to the struct video_card that owns this frame */ + struct video_card *video; + + /* index of this frame in video_card->frames[] */ + unsigned int frame_num; + + /* FRAME_CLEAR - DMA program not set up, waiting for data + FRAME_READY - DMA program written, ready to transmit + + Changes to these should be locked against the interrupt + */ + enum { + FRAME_CLEAR = 0, + FRAME_READY + } state; + + /* whether this frame has been DMA'ed already; used only from + the IRQ handler to determine whether the frame can be reset */ + int done; + + + /* kernel virtual pointer to the start of this frame's data in + the user ringbuffer. Use only for CPU access; to get the DMA + bus address you must go through the video->user_dma mapping */ + unsigned long data; + + /* Max # of packets per frame */ +#define MAX_PACKETS 500 + + + /* a PAGE_SIZE memory pool for allocating CIP headers + !header_pool must be aligned to PAGE_SIZE! */ + struct CIP_header *header_pool; + dma_addr_t header_pool_dma; + + + /* a physically contiguous memory pool for allocating DMA + descriptor blocks; usually around 64KB in size + !descriptor_pool must be aligned to PAGE_SIZE! */ + struct DMA_descriptor_block *descriptor_pool; + dma_addr_t descriptor_pool_dma; + unsigned long descriptor_pool_size; + + + /* # of packets allocated for this frame */ + unsigned int n_packets; + + + /* below are several pointers (kernel virtual addresses, not + DMA bus addresses) to parts of the DMA program. These are + set each time the DMA program is written in + frame_prepare(). They are used later on, e.g. from the + interrupt handler, to check the status of the frame */ + + /* points to status/timestamp field of first DMA packet */ + /* (we'll check it later to monitor timestamp accuracy) */ + u32 *frame_begin_timestamp; + + /* the timestamp we assigned to the first packet in the frame */ + u32 assigned_timestamp; + + /* pointer to the first packet's CIP header (where the timestamp goes) */ + struct CIP_header *cip_syt1; + + /* pointer to the second packet's CIP header + (only set if the first packet was empty) */ + struct CIP_header *cip_syt2; + + /* in order to figure out what caused an interrupt, + store pointers to the status fields of the two packets + that can cause interrupts. We'll check these from the + interrupt handler. + */ + u32 *mid_frame_timestamp; + u32 *frame_end_timestamp; + + /* branch address field of final packet. This is effectively + the "tail" in the chain of DMA descriptor blocks. + We will fill it with the address of the first DMA descriptor + block in the subsequent frame, once it is ready. + */ + u32 *frame_end_branch; + + /* the number of descriptors in the first descriptor block + of the frame. Needed to start DMA */ + int first_n_descriptors; +}; + + +struct packet { + u16 timestamp; + u16 invalid; + u16 iso_header; + u16 data_length; + u32 cip_h1; + u32 cip_h2; + unsigned char data[480]; + unsigned char padding[16]; /* force struct size =512 for page alignment */ +}; + + +/* allocate/free a frame */ +static struct frame* frame_new(unsigned int frame_num, struct video_card *video); +static void frame_delete(struct frame *f); + +/* reset f so that it can be used again */ +static void frame_reset(struct frame *f); + +/* struct video_card contains all data associated with one instance + of the dv1394 driver +*/ +enum modes { + MODE_RECEIVE, + MODE_TRANSMIT +}; + +struct video_card { + + /* ohci card to which this instance corresponds */ + struct ti_ohci *ohci; + + /* OHCI card id; the link between the VFS inode and a specific video_card + (essentially the device minor number) */ + int id; + + /* entry in dv1394_cards */ + struct list_head list; + + /* OHCI card IT DMA context number, -1 if not in use */ + int ohci_it_ctx; + struct ohci1394_iso_tasklet it_tasklet; + + /* register offsets for current IT DMA context, 0 if not in use */ + u32 ohci_IsoXmitContextControlSet; + u32 ohci_IsoXmitContextControlClear; + u32 ohci_IsoXmitCommandPtr; + + /* OHCI card IR DMA context number, -1 if not in use */ + struct ohci1394_iso_tasklet ir_tasklet; + int ohci_ir_ctx; + + /* register offsets for current IR DMA context, 0 if not in use */ + u32 ohci_IsoRcvContextControlSet; + u32 ohci_IsoRcvContextControlClear; + u32 ohci_IsoRcvCommandPtr; + u32 ohci_IsoRcvContextMatch; + + + /* CONCURRENCY CONTROL */ + + /* there are THREE levels of locking associated with video_card. */ + + /* + 1) the 'open' flag - this prevents more than one process from + opening the device. (the driver currently assumes only one opener). + This is a regular int, but use test_and_set_bit() (on bit zero) + for atomicity. + */ + unsigned long open; + + /* + 2) the spinlock - this provides mutual exclusion between the interrupt + handler and process-context operations. Generally you must take the + spinlock under the following conditions: + 1) DMA (and hence the interrupt handler) may be running + AND + 2) you need to operate on the video_card, especially active_frame + + It is OK to play with video_card without taking the spinlock if + you are certain that DMA is not running. Even if DMA is running, + it is OK to *read* active_frame with the lock, then drop it + immediately. This is safe because the interrupt handler will never + advance active_frame onto a frame that is not READY (and the spinlock + must be held while marking a frame READY). + + spinlock is also used to protect ohci_it_ctx and ohci_ir_ctx, + which can be accessed from both process and interrupt context + */ + spinlock_t spinlock; + + /* flag to prevent spurious interrupts (which OHCI seems to + generate a lot :) from accessing the struct */ + int dma_running; + + /* + 3) the sleeping mutex 'mtx' - this is used from process context only, + to serialize various operations on the video_card. Even though only one + open() is allowed, we still need to prevent multiple threads of execution + from entering calls like read, write, ioctl, etc. + + I honestly can't think of a good reason to use dv1394 from several threads + at once, but we need to serialize anyway to prevent oopses =). + + NOTE: if you need both spinlock and mtx, take mtx first to avoid deadlock! + */ + struct mutex mtx; + + /* people waiting for buffer space, please form a line here... */ + wait_queue_head_t waitq; + + /* support asynchronous I/O signals (SIGIO) */ + struct fasync_struct *fasync; + + /* the large, non-contiguous (rvmalloc()) ringbuffer for DV + data, exposed to user-space via mmap() */ + unsigned long dv_buf_size; + struct dma_region dv_buf; + + /* next byte in the ringbuffer that a write() call will fill */ + size_t write_off; + + struct frame *frames[DV1394_MAX_FRAMES]; + + /* n_frames also serves as an indicator that this struct video_card is + initialized and ready to run DMA buffers */ + + int n_frames; + + /* this is the frame that is currently "owned" by the OHCI DMA controller + (set to -1 iff DMA is not running) + + ! must lock against the interrupt handler when accessing it ! + + RULES: + + Only the interrupt handler may change active_frame if DMA + is running; if not, process may change it + + If the next frame is READY, the interrupt handler will advance + active_frame when the current frame is finished. + + If the next frame is CLEAR, the interrupt handler will re-transmit + the current frame, and the dropped_frames counter will be incremented. + + The interrupt handler will NEVER advance active_frame to a + frame that is not READY. + */ + int active_frame; + int first_run; + + /* the same locking rules apply to these three fields also: */ + + /* altered ONLY from process context. Must check first_clear_frame->state; + if it's READY, that means the ringbuffer is full with READY frames; + if it's CLEAR, that means one or more ringbuffer frames are CLEAR */ + unsigned int first_clear_frame; + + /* altered both by process and interrupt */ + unsigned int n_clear_frames; + + /* only altered by the interrupt */ + unsigned int dropped_frames; + + + + /* the CIP accumulator and continuity counter are properties + of the DMA stream as a whole (not a single frame), so they + are stored here in the video_card */ + + unsigned long cip_accum; + unsigned long cip_n, cip_d; + unsigned int syt_offset; + unsigned int continuity_counter; + + enum pal_or_ntsc pal_or_ntsc; + + /* redundant, but simplifies the code somewhat */ + unsigned int frame_size; /* in bytes */ + + /* the isochronous channel to use, -1 if video card is inactive */ + int channel; + + + /* physically contiguous packet ringbuffer for receive */ + struct dma_region packet_buf; + unsigned long packet_buf_size; + + unsigned int current_packet; + int first_frame; /* received first start frame marker? */ + enum modes mode; +}; + +/* + if the video_card is not initialized, then the ONLY fields that are valid are: + ohci + open + n_frames +*/ + +static inline int video_card_initialized(struct video_card *v) +{ + return v->n_frames > 0; +} + +static int do_dv1394_init(struct video_card *video, struct dv1394_init *init); +static int do_dv1394_init_default(struct video_card *video); +static void do_dv1394_shutdown(struct video_card *video, int free_user_buf); + + +/* NTSC empty packet rate accurate to within 0.01%, + calibrated against a Sony DSR-40 DVCAM deck */ + +#define CIP_N_NTSC 68000000 +#define CIP_D_NTSC 1068000000 + +#define CIP_N_PAL 1 +#define CIP_D_PAL 16 + +#endif /* _DV_1394_PRIVATE_H */ + diff --git a/drivers/ieee1394/dv1394.c b/drivers/ieee1394/dv1394.c new file mode 100644 index 0000000..c19f232 --- /dev/null +++ b/drivers/ieee1394/dv1394.c @@ -0,0 +1,2596 @@ +/* + * dv1394.c - DV input/output over IEEE 1394 on OHCI chips + * Copyright (C)2001 Daniel Maas <dmaas@dcine.com> + * receive by Dan Dennedy <dan@dennedy.org> + * + * based on: + * video1394.c - video driver for OHCI 1394 boards + * Copyright (C)1999,2000 Sebastien Rougeaux <sebastien.rougeaux@anu.edu.au> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + OVERVIEW + + I designed dv1394 as a "pipe" that you can use to shoot DV onto a + FireWire bus. In transmission mode, dv1394 does the following: + + 1. accepts contiguous frames of DV data from user-space, via write() + or mmap() (see dv1394.h for the complete API) + 2. wraps IEC 61883 packets around the DV data, inserting + empty synchronization packets as necessary + 3. assigns accurate SYT timestamps to the outgoing packets + 4. shoots them out using the OHCI card's IT DMA engine + + Thanks to Dan Dennedy, we now have a receive mode that does the following: + + 1. accepts raw IEC 61883 packets from the OHCI card + 2. re-assembles the DV data payloads into contiguous frames, + discarding empty packets + 3. sends the DV data to user-space via read() or mmap() +*/ + +/* + TODO: + + - tunable frame-drop behavior: either loop last frame, or halt transmission + + - use a scatter/gather buffer for DMA programs (f->descriptor_pool) + so that we don't rely on allocating 64KB of contiguous kernel memory + via pci_alloc_consistent() + + DONE: + - during reception, better handling of dropped frames and continuity errors + - during reception, prevent DMA from bypassing the irq tasklets + - reduce irq rate during reception (1/250 packets). + - add many more internal buffers during reception with scatter/gather dma. + - add dbc (continuity) checking on receive, increment status.dropped_frames + if not continuous. + - restart IT DMA after a bus reset + - safely obtain and release ISO Tx channels in cooperation with OHCI driver + - map received DIF blocks to their proper location in DV frame (ensure + recovery if dropped packet) + - handle bus resets gracefully (OHCI card seems to take care of this itself(!)) + - do not allow resizing the user_buf once allocated; eliminate nuke_buffer_mappings + - eliminated #ifdef DV1394_DEBUG_LEVEL by inventing macros debug_printk and irq_printk + - added wmb() and mb() to places where PCI read/write ordering needs to be enforced + - set video->id correctly + - store video_cards in an array indexed by OHCI card ID, rather than a list + - implement DMA context allocation to cooperate with other users of the OHCI + - fix all XXX showstoppers + - disable IR/IT DMA interrupts on shutdown + - flush pci writes to the card by issuing a read + - character device dispatching + - switch over to the new kernel DMA API (pci_map_*()) (* needs testing on platforms with IOMMU!) + - keep all video_cards in a list (for open() via chardev), set file->private_data = video + - dv1394_poll should indicate POLLIN when receiving buffers are available + - add proc fs interface to set cip_n, cip_d, syt_offset, and video signal + - expose xmit and recv as separate devices (not exclusive) + - expose NTSC and PAL as separate devices (can be overridden) + +*/ + +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/mutex.h> +#include <linux/bitops.h> +#include <asm/byteorder.h> +#include <asm/atomic.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <linux/delay.h> +#include <asm/pgtable.h> +#include <asm/page.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/vmalloc.h> +#include <linux/string.h> +#include <linux/compat.h> +#include <linux/cdev.h> + +#include "dv1394.h" +#include "dv1394-private.h" +#include "highlevel.h" +#include "hosts.h" +#include "ieee1394.h" +#include "ieee1394_core.h" +#include "ieee1394_hotplug.h" +#include "ieee1394_types.h" +#include "nodemgr.h" +#include "ohci1394.h" + +/* DEBUG LEVELS: + 0 - no debugging messages + 1 - some debugging messages, but none during DMA frame transmission + 2 - lots of messages, including during DMA frame transmission + (will cause undeflows if your machine is too slow!) +*/ + +#define DV1394_DEBUG_LEVEL 0 + +/* for debugging use ONLY: allow more than one open() of the device */ +/* #define DV1394_ALLOW_MORE_THAN_ONE_OPEN 1 */ + +#if DV1394_DEBUG_LEVEL >= 2 +#define irq_printk( args... ) printk( args ) +#else +#define irq_printk( args... ) do {} while (0) +#endif + +#if DV1394_DEBUG_LEVEL >= 1 +#define debug_printk( args... ) printk( args) +#else +#define debug_printk( args... ) do {} while (0) +#endif + +/* issue a dummy PCI read to force the preceding write + to be posted to the PCI bus immediately */ + +static inline void flush_pci_write(struct ti_ohci *ohci) +{ + mb(); + reg_read(ohci, OHCI1394_IsochronousCycleTimer); +} + +static void it_tasklet_func(unsigned long data); +static void ir_tasklet_func(unsigned long data); + +#ifdef CONFIG_COMPAT +static long dv1394_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg); +#endif + +/* GLOBAL DATA */ + +/* list of all video_cards */ +static LIST_HEAD(dv1394_cards); +static DEFINE_SPINLOCK(dv1394_cards_lock); + +/* translate from a struct file* to the corresponding struct video_card* */ + +static inline struct video_card* file_to_video_card(struct file *file) +{ + return (struct video_card*) file->private_data; +} + +/*** FRAME METHODS *********************************************************/ + +static void frame_reset(struct frame *f) +{ + f->state = FRAME_CLEAR; + f->done = 0; + f->n_packets = 0; + f->frame_begin_timestamp = NULL; + f->assigned_timestamp = 0; + f->cip_syt1 = NULL; + f->cip_syt2 = NULL; + f->mid_frame_timestamp = NULL; + f->frame_end_timestamp = NULL; + f->frame_end_branch = NULL; +} + +static struct frame* frame_new(unsigned int frame_num, struct video_card *video) +{ + struct frame *f = kmalloc(sizeof(*f), GFP_KERNEL); + if (!f) + return NULL; + + f->video = video; + f->frame_num = frame_num; + + f->header_pool = pci_alloc_consistent(f->video->ohci->dev, PAGE_SIZE, &f->header_pool_dma); + if (!f->header_pool) { + printk(KERN_ERR "dv1394: failed to allocate CIP header pool\n"); + kfree(f); + return NULL; + } + + debug_printk("dv1394: frame_new: allocated CIP header pool at virt 0x%08lx (contig) dma 0x%08lx size %ld\n", + (unsigned long) f->header_pool, (unsigned long) f->header_pool_dma, PAGE_SIZE); + + f->descriptor_pool_size = MAX_PACKETS * sizeof(struct DMA_descriptor_block); + /* make it an even # of pages */ + f->descriptor_pool_size += PAGE_SIZE - (f->descriptor_pool_size%PAGE_SIZE); + + f->descriptor_pool = pci_alloc_consistent(f->video->ohci->dev, + f->descriptor_pool_size, + &f->descriptor_pool_dma); + if (!f->descriptor_pool) { + pci_free_consistent(f->video->ohci->dev, PAGE_SIZE, f->header_pool, f->header_pool_dma); + kfree(f); + return NULL; + } + + debug_printk("dv1394: frame_new: allocated DMA program memory at virt 0x%08lx (contig) dma 0x%08lx size %ld\n", + (unsigned long) f->descriptor_pool, (unsigned long) f->descriptor_pool_dma, f->descriptor_pool_size); + + f->data = 0; + frame_reset(f); + + return f; +} + +static void frame_delete(struct frame *f) +{ + pci_free_consistent(f->video->ohci->dev, PAGE_SIZE, f->header_pool, f->header_pool_dma); + pci_free_consistent(f->video->ohci->dev, f->descriptor_pool_size, f->descriptor_pool, f->descriptor_pool_dma); + kfree(f); +} + + + + +/* + frame_prepare() - build the DMA program for transmitting + + Frame_prepare() must be called OUTSIDE the video->spinlock. + However, frame_prepare() must still be serialized, so + it should be called WITH the video->mtx taken. + */ + +static void frame_prepare(struct video_card *video, unsigned int this_frame) +{ + struct frame *f = video->frames[this_frame]; + int last_frame; + + struct DMA_descriptor_block *block; + dma_addr_t block_dma; + struct CIP_header *cip; + dma_addr_t cip_dma; + + unsigned int n_descriptors, full_packets, packets_per_frame, payload_size; + + /* these flags denote packets that need special attention */ + int empty_packet, first_packet, last_packet, mid_packet; + + u32 *branch_address, *last_branch_address = NULL; + unsigned long data_p; + int first_packet_empty = 0; + u32 cycleTimer, ct_sec, ct_cyc, ct_off; + unsigned long irq_flags; + + irq_printk("frame_prepare( %d ) ---------------------\n", this_frame); + + full_packets = 0; + + + + if (video->pal_or_ntsc == DV1394_PAL) + packets_per_frame = DV1394_PAL_PACKETS_PER_FRAME; + else + packets_per_frame = DV1394_NTSC_PACKETS_PER_FRAME; + + while ( full_packets < packets_per_frame ) { + empty_packet = first_packet = last_packet = mid_packet = 0; + + data_p = f->data + full_packets * 480; + + /************************************************/ + /* allocate a descriptor block and a CIP header */ + /************************************************/ + + /* note: these should NOT cross a page boundary (DMA restriction) */ + + if (f->n_packets >= MAX_PACKETS) { + printk(KERN_ERR "dv1394: FATAL ERROR: max packet count exceeded\n"); + return; + } + + /* the block surely won't cross a page boundary, + since an even number of descriptor_blocks fit on a page */ + block = &(f->descriptor_pool[f->n_packets]); + + /* DMA address of the block = offset of block relative + to the kernel base address of the descriptor pool + + DMA base address of the descriptor pool */ + block_dma = ((unsigned long) block - (unsigned long) f->descriptor_pool) + f->descriptor_pool_dma; + + + /* the whole CIP pool fits on one page, so no worries about boundaries */ + if ( ((unsigned long) &(f->header_pool[f->n_packets]) - (unsigned long) f->header_pool) + > PAGE_SIZE) { + printk(KERN_ERR "dv1394: FATAL ERROR: no room to allocate CIP header\n"); + return; + } + + cip = &(f->header_pool[f->n_packets]); + + /* DMA address of the CIP header = offset of cip + relative to kernel base address of the header pool + + DMA base address of the header pool */ + cip_dma = (unsigned long) cip % PAGE_SIZE + f->header_pool_dma; + + /* is this an empty packet? */ + + if (video->cip_accum > (video->cip_d - video->cip_n)) { + empty_packet = 1; + payload_size = 8; + video->cip_accum -= (video->cip_d - video->cip_n); + } else { + payload_size = 488; + video->cip_accum += video->cip_n; + } + + /* there are three important packets each frame: + + the first packet in the frame - we ask the card to record the timestamp when + this packet is actually sent, so we can monitor + how accurate our timestamps are. Also, the first + packet serves as a semaphore to let us know that + it's OK to free the *previous* frame's DMA buffer + + the last packet in the frame - this packet is used to detect buffer underflows. + if this is the last ready frame, the last DMA block + will have a branch back to the beginning of the frame + (so that the card will re-send the frame on underflow). + if this branch gets taken, we know that at least one + frame has been dropped. When the next frame is ready, + the branch is pointed to its first packet, and the + semaphore is disabled. + + a "mid" packet slightly before the end of the frame - this packet should trigger + an interrupt so we can go and assign a timestamp to the first packet + in the next frame. We don't use the very last packet in the frame + for this purpose, because that would leave very little time to set + the timestamp before DMA starts on the next frame. + */ + + if (f->n_packets == 0) { + first_packet = 1; + } else if ( full_packets == (packets_per_frame-1) ) { + last_packet = 1; + } else if (f->n_packets == packets_per_frame) { + mid_packet = 1; + } + + + /********************/ + /* setup CIP header */ + /********************/ + + /* the timestamp will be written later from the + mid-frame interrupt handler. For now we just + store the address of the CIP header(s) that + need a timestamp. */ + + /* first packet in the frame needs a timestamp */ + if (first_packet) { + f->cip_syt1 = cip; + if (empty_packet) + first_packet_empty = 1; + + } else if (first_packet_empty && (f->n_packets == 1) ) { + /* if the first packet was empty, the second + packet's CIP header also needs a timestamp */ + f->cip_syt2 = cip; + } + + fill_cip_header(cip, + /* the node ID number of the OHCI card */ + reg_read(video->ohci, OHCI1394_NodeID) & 0x3F, + video->continuity_counter, + video->pal_or_ntsc, + 0xFFFF /* the timestamp is filled in later */); + + /* advance counter, only for full packets */ + if ( ! empty_packet ) + video->continuity_counter++; + + /******************************/ + /* setup DMA descriptor block */ + /******************************/ + + /* first descriptor - OUTPUT_MORE_IMMEDIATE, for the controller's IT header */ + fill_output_more_immediate( &(block->u.out.omi), 1, video->channel, 0, payload_size); + + if (empty_packet) { + /* second descriptor - OUTPUT_LAST for CIP header */ + fill_output_last( &(block->u.out.u.empty.ol), + + /* want completion status on all interesting packets */ + (first_packet || mid_packet || last_packet) ? 1 : 0, + + /* want interrupts on all interesting packets */ + (first_packet || mid_packet || last_packet) ? 1 : 0, + + sizeof(struct CIP_header), /* data size */ + cip_dma); + + if (first_packet) + f->frame_begin_timestamp = &(block->u.out.u.empty.ol.q[3]); + else if (mid_packet) + f->mid_frame_timestamp = &(block->u.out.u.empty.ol.q[3]); + else if (last_packet) { + f->frame_end_timestamp = &(block->u.out.u.empty.ol.q[3]); + f->frame_end_branch = &(block->u.out.u.empty.ol.q[2]); + } + + branch_address = &(block->u.out.u.empty.ol.q[2]); + n_descriptors = 3; + if (first_packet) + f->first_n_descriptors = n_descriptors; + + } else { /* full packet */ + + /* second descriptor - OUTPUT_MORE for CIP header */ + fill_output_more( &(block->u.out.u.full.om), + sizeof(struct CIP_header), /* data size */ + cip_dma); + + + /* third (and possibly fourth) descriptor - for DV data */ + /* the 480-byte payload can cross a page boundary; if so, + we need to split it into two DMA descriptors */ + + /* does the 480-byte data payload cross a page boundary? */ + if ( (PAGE_SIZE- ((unsigned long)data_p % PAGE_SIZE) ) < 480 ) { + + /* page boundary crossed */ + + fill_output_more( &(block->u.out.u.full.u.cross.om), + /* data size - how much of data_p fits on the first page */ + PAGE_SIZE - (data_p % PAGE_SIZE), + + /* DMA address of data_p */ + dma_region_offset_to_bus(&video->dv_buf, + data_p - (unsigned long) video->dv_buf.kvirt)); + + fill_output_last( &(block->u.out.u.full.u.cross.ol), + + /* want completion status on all interesting packets */ + (first_packet || mid_packet || last_packet) ? 1 : 0, + + /* want interrupt on all interesting packets */ + (first_packet || mid_packet || last_packet) ? 1 : 0, + + /* data size - remaining portion of data_p */ + 480 - (PAGE_SIZE - (data_p % PAGE_SIZE)), + + /* DMA address of data_p + PAGE_SIZE - (data_p % PAGE_SIZE) */ + dma_region_offset_to_bus(&video->dv_buf, + data_p + PAGE_SIZE - (data_p % PAGE_SIZE) - (unsigned long) video->dv_buf.kvirt)); + + if (first_packet) + f->frame_begin_timestamp = &(block->u.out.u.full.u.cross.ol.q[3]); + else if (mid_packet) + f->mid_frame_timestamp = &(block->u.out.u.full.u.cross.ol.q[3]); + else if (last_packet) { + f->frame_end_timestamp = &(block->u.out.u.full.u.cross.ol.q[3]); + f->frame_end_branch = &(block->u.out.u.full.u.cross.ol.q[2]); + } + + branch_address = &(block->u.out.u.full.u.cross.ol.q[2]); + + n_descriptors = 5; + if (first_packet) + f->first_n_descriptors = n_descriptors; + + full_packets++; + + } else { + /* fits on one page */ + + fill_output_last( &(block->u.out.u.full.u.nocross.ol), + + /* want completion status on all interesting packets */ + (first_packet || mid_packet || last_packet) ? 1 : 0, + + /* want interrupt on all interesting packets */ + (first_packet || mid_packet || last_packet) ? 1 : 0, + + 480, /* data size (480 bytes of DV data) */ + + + /* DMA address of data_p */ + dma_region_offset_to_bus(&video->dv_buf, + data_p - (unsigned long) video->dv_buf.kvirt)); + + if (first_packet) + f->frame_begin_timestamp = &(block->u.out.u.full.u.nocross.ol.q[3]); + else if (mid_packet) + f->mid_frame_timestamp = &(block->u.out.u.full.u.nocross.ol.q[3]); + else if (last_packet) { + f->frame_end_timestamp = &(block->u.out.u.full.u.nocross.ol.q[3]); + f->frame_end_branch = &(block->u.out.u.full.u.nocross.ol.q[2]); + } + + branch_address = &(block->u.out.u.full.u.nocross.ol.q[2]); + + n_descriptors = 4; + if (first_packet) + f->first_n_descriptors = n_descriptors; + + full_packets++; + } + } + + /* link this descriptor block into the DMA program by filling in + the branch address of the previous block */ + + /* note: we are not linked into the active DMA chain yet */ + + if (last_branch_address) { + *(last_branch_address) = cpu_to_le32(block_dma | n_descriptors); + } + + last_branch_address = branch_address; + + + f->n_packets++; + + } + + /* when we first assemble a new frame, set the final branch + to loop back up to the top */ + *(f->frame_end_branch) = cpu_to_le32(f->descriptor_pool_dma | f->first_n_descriptors); + + /* make the latest version of this frame visible to the PCI card */ + dma_region_sync_for_device(&video->dv_buf, f->data - (unsigned long) video->dv_buf.kvirt, video->frame_size); + + /* lock against DMA interrupt */ + spin_lock_irqsave(&video->spinlock, irq_flags); + + f->state = FRAME_READY; + + video->n_clear_frames--; + + last_frame = video->first_clear_frame - 1; + if (last_frame == -1) + last_frame = video->n_frames-1; + + video->first_clear_frame = (video->first_clear_frame + 1) % video->n_frames; + + irq_printk(" frame %d prepared, active_frame = %d, n_clear_frames = %d, first_clear_frame = %d\n last=%d\n", + this_frame, video->active_frame, video->n_clear_frames, video->first_clear_frame, last_frame); + + irq_printk(" begin_ts %08lx mid_ts %08lx end_ts %08lx end_br %08lx\n", + (unsigned long) f->frame_begin_timestamp, + (unsigned long) f->mid_frame_timestamp, + (unsigned long) f->frame_end_timestamp, + (unsigned long) f->frame_end_branch); + + if (video->active_frame != -1) { + + /* if DMA is already active, we are almost done */ + /* just link us onto the active DMA chain */ + if (video->frames[last_frame]->frame_end_branch) { + u32 temp; + + /* point the previous frame's tail to this frame's head */ + *(video->frames[last_frame]->frame_end_branch) = cpu_to_le32(f->descriptor_pool_dma | f->first_n_descriptors); + + /* this write MUST precede the next one, or we could silently drop frames */ + wmb(); + + /* disable the want_status semaphore on the last packet */ + temp = le32_to_cpu(*(video->frames[last_frame]->frame_end_branch - 2)); + temp &= 0xF7CFFFFF; + *(video->frames[last_frame]->frame_end_branch - 2) = cpu_to_le32(temp); + + /* flush these writes to memory ASAP */ + flush_pci_write(video->ohci); + + /* NOTE: + ideally the writes should be "atomic": if + the OHCI card reads the want_status flag in + between them, we'll falsely report a + dropped frame. Hopefully this window is too + small to really matter, and the consequence + is rather harmless. */ + + + irq_printk(" new frame %d linked onto DMA chain\n", this_frame); + + } else { + printk(KERN_ERR "dv1394: last frame not ready???\n"); + } + + } else { + + u32 transmit_sec, transmit_cyc; + u32 ts_cyc, ts_off; + + /* DMA is stopped, so this is the very first frame */ + video->active_frame = this_frame; + + /* set CommandPtr to address and size of first descriptor block */ + reg_write(video->ohci, video->ohci_IsoXmitCommandPtr, + video->frames[video->active_frame]->descriptor_pool_dma | + f->first_n_descriptors); + + /* assign a timestamp based on the current cycle time... + We'll tell the card to begin DMA 100 cycles from now, + and assign a timestamp 103 cycles from now */ + + cycleTimer = reg_read(video->ohci, OHCI1394_IsochronousCycleTimer); + + ct_sec = cycleTimer >> 25; + ct_cyc = (cycleTimer >> 12) & 0x1FFF; + ct_off = cycleTimer & 0xFFF; + + transmit_sec = ct_sec; + transmit_cyc = ct_cyc + 100; + + transmit_sec += transmit_cyc/8000; + transmit_cyc %= 8000; + + ts_off = ct_off; + ts_cyc = transmit_cyc + 3; + ts_cyc %= 8000; + + f->assigned_timestamp = (ts_cyc&0xF) << 12; + + /* now actually write the timestamp into the appropriate CIP headers */ + if (f->cip_syt1) { + f->cip_syt1->b[6] = f->assigned_timestamp >> 8; + f->cip_syt1->b[7] = f->assigned_timestamp & 0xFF; + } + if (f->cip_syt2) { + f->cip_syt2->b[6] = f->assigned_timestamp >> 8; + f->cip_syt2->b[7] = f->assigned_timestamp & 0xFF; + } + + /* --- start DMA --- */ + + /* clear all bits in ContextControl register */ + + reg_write(video->ohci, video->ohci_IsoXmitContextControlClear, 0xFFFFFFFF); + wmb(); + + /* the OHCI card has the ability to start ISO transmission on a + particular cycle (start-on-cycle). This way we can ensure that + the first DV frame will have an accurate timestamp. + + However, start-on-cycle only appears to work if the OHCI card + is cycle master! Since the consequences of messing up the first + timestamp are minimal*, just disable start-on-cycle for now. + + * my DV deck drops the first few frames before it "locks in;" + so the first frame having an incorrect timestamp is inconsequential. + */ + +#if 0 + reg_write(video->ohci, video->ohci_IsoXmitContextControlSet, + (1 << 31) /* enable start-on-cycle */ + | ( (transmit_sec & 0x3) << 29) + | (transmit_cyc << 16)); + wmb(); +#endif + + video->dma_running = 1; + + /* set the 'run' bit */ + reg_write(video->ohci, video->ohci_IsoXmitContextControlSet, 0x8000); + flush_pci_write(video->ohci); + + /* --- DMA should be running now --- */ + + debug_printk(" Cycle = %4u ContextControl = %08x CmdPtr = %08x\n", + (reg_read(video->ohci, OHCI1394_IsochronousCycleTimer) >> 12) & 0x1FFF, + reg_read(video->ohci, video->ohci_IsoXmitContextControlSet), + reg_read(video->ohci, video->ohci_IsoXmitCommandPtr)); + + debug_printk(" DMA start - current cycle %4u, transmit cycle %4u (%2u), assigning ts cycle %2u\n", + ct_cyc, transmit_cyc, transmit_cyc & 0xF, ts_cyc & 0xF); + +#if DV1394_DEBUG_LEVEL >= 2 + { + /* check if DMA is really running */ + int i = 0; + while (i < 20) { + mb(); + mdelay(1); + if (reg_read(video->ohci, video->ohci_IsoXmitContextControlSet) & (1 << 10)) { + printk("DMA ACTIVE after %d msec\n", i); + break; + } + i++; + } + + printk("set = %08x, cmdPtr = %08x\n", + reg_read(video->ohci, video->ohci_IsoXmitContextControlSet), + reg_read(video->ohci, video->ohci_IsoXmitCommandPtr) + ); + + if ( ! (reg_read(video->ohci, video->ohci_IsoXmitContextControlSet) & (1 << 10)) ) { + printk("DMA did NOT go active after 20ms, event = %x\n", + reg_read(video->ohci, video->ohci_IsoXmitContextControlSet) & 0x1F); + } else + printk("DMA is RUNNING!\n"); + } +#endif + + } + + + spin_unlock_irqrestore(&video->spinlock, irq_flags); +} + + + +/*** RECEIVE FUNCTIONS *****************************************************/ + +/* + frame method put_packet + + map and copy the packet data to its location in the frame + based upon DIF section and sequence +*/ + +static void inline +frame_put_packet (struct frame *f, struct packet *p) +{ + int section_type = p->data[0] >> 5; /* section type is in bits 5 - 7 */ + int dif_sequence = p->data[1] >> 4; /* dif sequence number is in bits 4 - 7 */ + int dif_block = p->data[2]; + + /* sanity check */ + if (dif_sequence > 11 || dif_block > 149) return; + + switch (section_type) { + case 0: /* 1 Header block */ + memcpy( (void *) f->data + dif_sequence * 150 * 80, p->data, 480); + break; + + case 1: /* 2 Subcode blocks */ + memcpy( (void *) f->data + dif_sequence * 150 * 80 + (1 + dif_block) * 80, p->data, 480); + break; + + case 2: /* 3 VAUX blocks */ + memcpy( (void *) f->data + dif_sequence * 150 * 80 + (3 + dif_block) * 80, p->data, 480); + break; + + case 3: /* 9 Audio blocks interleaved with video */ + memcpy( (void *) f->data + dif_sequence * 150 * 80 + (6 + dif_block * 16) * 80, p->data, 480); + break; + + case 4: /* 135 Video blocks interleaved with audio */ + memcpy( (void *) f->data + dif_sequence * 150 * 80 + (7 + (dif_block / 15) + dif_block) * 80, p->data, 480); + break; + + default: /* we can not handle any other data */ + break; + } +} + + +static void start_dma_receive(struct video_card *video) +{ + if (video->first_run == 1) { + video->first_run = 0; + + /* start DMA once all of the frames are READY */ + video->n_clear_frames = 0; + video->first_clear_frame = -1; + video->current_packet = 0; + video->active_frame = 0; + + /* reset iso recv control register */ + reg_write(video->ohci, video->ohci_IsoRcvContextControlClear, 0xFFFFFFFF); + wmb(); + + /* clear bufferFill, set isochHeader and speed (0=100) */ + reg_write(video->ohci, video->ohci_IsoRcvContextControlSet, 0x40000000); + + /* match on all tags, listen on channel */ + reg_write(video->ohci, video->ohci_IsoRcvContextMatch, 0xf0000000 | video->channel); + + /* address and first descriptor block + Z=1 */ + reg_write(video->ohci, video->ohci_IsoRcvCommandPtr, + video->frames[0]->descriptor_pool_dma | 1); /* Z=1 */ + wmb(); + + video->dma_running = 1; + + /* run */ + reg_write(video->ohci, video->ohci_IsoRcvContextControlSet, 0x8000); + flush_pci_write(video->ohci); + + debug_printk("dv1394: DMA started\n"); + +#if DV1394_DEBUG_LEVEL >= 2 + { + int i; + + for (i = 0; i < 1000; ++i) { + mdelay(1); + if (reg_read(video->ohci, video->ohci_IsoRcvContextControlSet) & (1 << 10)) { + printk("DMA ACTIVE after %d msec\n", i); + break; + } + } + if ( reg_read(video->ohci, video->ohci_IsoRcvContextControlSet) & (1 << 11) ) { + printk("DEAD, event = %x\n", + reg_read(video->ohci, video->ohci_IsoRcvContextControlSet) & 0x1F); + } else + printk("RUNNING!\n"); + } +#endif + } else if ( reg_read(video->ohci, video->ohci_IsoRcvContextControlSet) & (1 << 11) ) { + debug_printk("DEAD, event = %x\n", + reg_read(video->ohci, video->ohci_IsoRcvContextControlSet) & 0x1F); + + /* wake */ + reg_write(video->ohci, video->ohci_IsoRcvContextControlSet, (1 << 12)); + } +} + + +/* + receive_packets() - build the DMA program for receiving +*/ + +static void receive_packets(struct video_card *video) +{ + struct DMA_descriptor_block *block = NULL; + dma_addr_t block_dma = 0; + struct packet *data = NULL; + dma_addr_t data_dma = 0; + u32 *last_branch_address = NULL; + unsigned long irq_flags; + int want_interrupt = 0; + struct frame *f = NULL; + int i, j; + + spin_lock_irqsave(&video->spinlock, irq_flags); + + for (j = 0; j < video->n_frames; j++) { + + /* connect frames */ + if (j > 0 && f != NULL && f->frame_end_branch != NULL) + *(f->frame_end_branch) = cpu_to_le32(video->frames[j]->descriptor_pool_dma | 1); /* set Z=1 */ + + f = video->frames[j]; + + for (i = 0; i < MAX_PACKETS; i++) { + /* locate a descriptor block and packet from the buffer */ + block = &(f->descriptor_pool[i]); + block_dma = ((unsigned long) block - (unsigned long) f->descriptor_pool) + f->descriptor_pool_dma; + + data = ((struct packet*)video->packet_buf.kvirt) + f->frame_num * MAX_PACKETS + i; + data_dma = dma_region_offset_to_bus( &video->packet_buf, + ((unsigned long) data - (unsigned long) video->packet_buf.kvirt) ); + + /* setup DMA descriptor block */ + want_interrupt = ((i % (MAX_PACKETS/2)) == 0 || i == (MAX_PACKETS-1)); + fill_input_last( &(block->u.in.il), want_interrupt, 512, data_dma); + + /* link descriptors */ + last_branch_address = f->frame_end_branch; + + if (last_branch_address != NULL) + *(last_branch_address) = cpu_to_le32(block_dma | 1); /* set Z=1 */ + + f->frame_end_branch = &(block->u.in.il.q[2]); + } + + } /* next j */ + + spin_unlock_irqrestore(&video->spinlock, irq_flags); + +} + + + +/*** MANAGEMENT FUNCTIONS **************************************************/ + +static int do_dv1394_init(struct video_card *video, struct dv1394_init *init) +{ + unsigned long flags, new_buf_size; + int i; + u64 chan_mask; + int retval = -EINVAL; + + debug_printk("dv1394: initialising %d\n", video->id); + if (init->api_version != DV1394_API_VERSION) + return -EINVAL; + + /* first sanitize all the parameters */ + if ( (init->n_frames < 2) || (init->n_frames > DV1394_MAX_FRAMES) ) + return -EINVAL; + + if ( (init->format != DV1394_NTSC) && (init->format != DV1394_PAL) ) + return -EINVAL; + + if ( (init->syt_offset == 0) || (init->syt_offset > 50) ) + /* default SYT offset is 3 cycles */ + init->syt_offset = 3; + + if (init->channel > 63) + init->channel = 63; + + chan_mask = (u64)1 << init->channel; + + /* calculate what size DMA buffer is needed */ + if (init->format == DV1394_NTSC) + new_buf_size = DV1394_NTSC_FRAME_SIZE * init->n_frames; + else + new_buf_size = DV1394_PAL_FRAME_SIZE * init->n_frames; + + /* round up to PAGE_SIZE */ + if (new_buf_size % PAGE_SIZE) new_buf_size += PAGE_SIZE - (new_buf_size % PAGE_SIZE); + + /* don't allow the user to allocate the DMA buffer more than once */ + if (video->dv_buf.kvirt && video->dv_buf_size != new_buf_size) { + printk("dv1394: re-sizing the DMA buffer is not allowed\n"); + return -EINVAL; + } + + /* shutdown the card if it's currently active */ + /* (the card should not be reset if the parameters are screwy) */ + + do_dv1394_shutdown(video, 0); + + /* try to claim the ISO channel */ + spin_lock_irqsave(&video->ohci->IR_channel_lock, flags); + if (video->ohci->ISO_channel_usage & chan_mask) { + spin_unlock_irqrestore(&video->ohci->IR_channel_lock, flags); + retval = -EBUSY; + goto err; + } + video->ohci->ISO_channel_usage |= chan_mask; + spin_unlock_irqrestore(&video->ohci->IR_channel_lock, flags); + + video->channel = init->channel; + + /* initialize misc. fields of video */ + video->n_frames = init->n_frames; + video->pal_or_ntsc = init->format; + + video->cip_accum = 0; + video->continuity_counter = 0; + + video->active_frame = -1; + video->first_clear_frame = 0; + video->n_clear_frames = video->n_frames; + video->dropped_frames = 0; + + video->write_off = 0; + + video->first_run = 1; + video->current_packet = -1; + video->first_frame = 0; + + if (video->pal_or_ntsc == DV1394_NTSC) { + video->cip_n = init->cip_n != 0 ? init->cip_n : CIP_N_NTSC; + video->cip_d = init->cip_d != 0 ? init->cip_d : CIP_D_NTSC; + video->frame_size = DV1394_NTSC_FRAME_SIZE; + } else { + video->cip_n = init->cip_n != 0 ? init->cip_n : CIP_N_PAL; + video->cip_d = init->cip_d != 0 ? init->cip_d : CIP_D_PAL; + video->frame_size = DV1394_PAL_FRAME_SIZE; + } + + video->syt_offset = init->syt_offset; + + /* find and claim DMA contexts on the OHCI card */ + + if (video->ohci_it_ctx == -1) { + ohci1394_init_iso_tasklet(&video->it_tasklet, OHCI_ISO_TRANSMIT, + it_tasklet_func, (unsigned long) video); + + if (ohci1394_register_iso_tasklet(video->ohci, &video->it_tasklet) < 0) { + printk(KERN_ERR "dv1394: could not find an available IT DMA context\n"); + retval = -EBUSY; + goto err; + } + + video->ohci_it_ctx = video->it_tasklet.context; + debug_printk("dv1394: claimed IT DMA context %d\n", video->ohci_it_ctx); + } + + if (video->ohci_ir_ctx == -1) { + ohci1394_init_iso_tasklet(&video->ir_tasklet, OHCI_ISO_RECEIVE, + ir_tasklet_func, (unsigned long) video); + + if (ohci1394_register_iso_tasklet(video->ohci, &video->ir_tasklet) < 0) { + printk(KERN_ERR "dv1394: could not find an available IR DMA context\n"); + retval = -EBUSY; + goto err; + } + video->ohci_ir_ctx = video->ir_tasklet.context; + debug_printk("dv1394: claimed IR DMA context %d\n", video->ohci_ir_ctx); + } + + /* allocate struct frames */ + for (i = 0; i < init->n_frames; i++) { + video->frames[i] = frame_new(i, video); + + if (!video->frames[i]) { + printk(KERN_ERR "dv1394: Cannot allocate frame structs\n"); + retval = -ENOMEM; + goto err; + } + } + + if (!video->dv_buf.kvirt) { + /* allocate the ringbuffer */ + retval = dma_region_alloc(&video->dv_buf, new_buf_size, video->ohci->dev, PCI_DMA_TODEVICE); + if (retval) + goto err; + + video->dv_buf_size = new_buf_size; + + debug_printk("dv1394: Allocated %d frame buffers, total %u pages (%u DMA pages), %lu bytes\n", + video->n_frames, video->dv_buf.n_pages, + video->dv_buf.n_dma_pages, video->dv_buf_size); + } + + /* set up the frame->data pointers */ + for (i = 0; i < video->n_frames; i++) + video->frames[i]->data = (unsigned long) video->dv_buf.kvirt + i * video->frame_size; + + if (!video->packet_buf.kvirt) { + /* allocate packet buffer */ + video->packet_buf_size = sizeof(struct packet) * video->n_frames * MAX_PACKETS; + if (video->packet_buf_size % PAGE_SIZE) + video->packet_buf_size += PAGE_SIZE - (video->packet_buf_size % PAGE_SIZE); + + retval = dma_region_alloc(&video->packet_buf, video->packet_buf_size, + video->ohci->dev, PCI_DMA_FROMDEVICE); + if (retval) + goto err; + + debug_printk("dv1394: Allocated %d packets in buffer, total %u pages (%u DMA pages), %lu bytes\n", + video->n_frames*MAX_PACKETS, video->packet_buf.n_pages, + video->packet_buf.n_dma_pages, video->packet_buf_size); + } + + /* set up register offsets for IT context */ + /* IT DMA context registers are spaced 16 bytes apart */ + video->ohci_IsoXmitContextControlSet = OHCI1394_IsoXmitContextControlSet+16*video->ohci_it_ctx; + video->ohci_IsoXmitContextControlClear = OHCI1394_IsoXmitContextControlClear+16*video->ohci_it_ctx; + video->ohci_IsoXmitCommandPtr = OHCI1394_IsoXmitCommandPtr+16*video->ohci_it_ctx; + + /* enable interrupts for IT context */ + reg_write(video->ohci, OHCI1394_IsoXmitIntMaskSet, (1 << video->ohci_it_ctx)); + debug_printk("dv1394: interrupts enabled for IT context %d\n", video->ohci_it_ctx); + + /* set up register offsets for IR context */ + /* IR DMA context registers are spaced 32 bytes apart */ + video->ohci_IsoRcvContextControlSet = OHCI1394_IsoRcvContextControlSet+32*video->ohci_ir_ctx; + video->ohci_IsoRcvContextControlClear = OHCI1394_IsoRcvContextControlClear+32*video->ohci_ir_ctx; + video->ohci_IsoRcvCommandPtr = OHCI1394_IsoRcvCommandPtr+32*video->ohci_ir_ctx; + video->ohci_IsoRcvContextMatch = OHCI1394_IsoRcvContextMatch+32*video->ohci_ir_ctx; + + /* enable interrupts for IR context */ + reg_write(video->ohci, OHCI1394_IsoRecvIntMaskSet, (1 << video->ohci_ir_ctx) ); + debug_printk("dv1394: interrupts enabled for IR context %d\n", video->ohci_ir_ctx); + + return 0; + +err: + do_dv1394_shutdown(video, 1); + return retval; +} + +/* if the user doesn't bother to call ioctl(INIT) before starting + mmap() or read()/write(), just give him some default values */ + +static int do_dv1394_init_default(struct video_card *video) +{ + struct dv1394_init init; + + init.api_version = DV1394_API_VERSION; + init.n_frames = DV1394_MAX_FRAMES / 4; + init.channel = video->channel; + init.format = video->pal_or_ntsc; + init.cip_n = video->cip_n; + init.cip_d = video->cip_d; + init.syt_offset = video->syt_offset; + + return do_dv1394_init(video, &init); +} + +/* do NOT call from interrupt context */ +static void stop_dma(struct video_card *video) +{ + unsigned long flags; + int i; + + /* no interrupts */ + spin_lock_irqsave(&video->spinlock, flags); + + video->dma_running = 0; + + if ( (video->ohci_it_ctx == -1) && (video->ohci_ir_ctx == -1) ) + goto out; + + /* stop DMA if in progress */ + if ( (video->active_frame != -1) || + (reg_read(video->ohci, video->ohci_IsoXmitContextControlClear) & (1 << 10)) || + (reg_read(video->ohci, video->ohci_IsoRcvContextControlClear) & (1 << 10)) ) { + + /* clear the .run bits */ + reg_write(video->ohci, video->ohci_IsoXmitContextControlClear, (1 << 15)); + reg_write(video->ohci, video->ohci_IsoRcvContextControlClear, (1 << 15)); + flush_pci_write(video->ohci); + + video->active_frame = -1; + video->first_run = 1; + + /* wait until DMA really stops */ + i = 0; + while (i < 1000) { + + /* wait 0.1 millisecond */ + udelay(100); + + if ( (reg_read(video->ohci, video->ohci_IsoXmitContextControlClear) & (1 << 10)) || + (reg_read(video->ohci, video->ohci_IsoRcvContextControlClear) & (1 << 10)) ) { + /* still active */ + debug_printk("dv1394: stop_dma: DMA not stopped yet\n" ); + mb(); + } else { + debug_printk("dv1394: stop_dma: DMA stopped safely after %d ms\n", i/10); + break; + } + + i++; + } + + if (i == 1000) { + printk(KERN_ERR "dv1394: stop_dma: DMA still going after %d ms!\n", i/10); + } + } + else + debug_printk("dv1394: stop_dma: already stopped.\n"); + +out: + spin_unlock_irqrestore(&video->spinlock, flags); +} + + + +static void do_dv1394_shutdown(struct video_card *video, int free_dv_buf) +{ + int i; + + debug_printk("dv1394: shutdown...\n"); + + /* stop DMA if in progress */ + stop_dma(video); + + /* release the DMA contexts */ + if (video->ohci_it_ctx != -1) { + video->ohci_IsoXmitContextControlSet = 0; + video->ohci_IsoXmitContextControlClear = 0; + video->ohci_IsoXmitCommandPtr = 0; + + /* disable interrupts for IT context */ + reg_write(video->ohci, OHCI1394_IsoXmitIntMaskClear, (1 << video->ohci_it_ctx)); + + /* remove tasklet */ + ohci1394_unregister_iso_tasklet(video->ohci, &video->it_tasklet); + debug_printk("dv1394: IT context %d released\n", video->ohci_it_ctx); + video->ohci_it_ctx = -1; + } + + if (video->ohci_ir_ctx != -1) { + video->ohci_IsoRcvContextControlSet = 0; + video->ohci_IsoRcvContextControlClear = 0; + video->ohci_IsoRcvCommandPtr = 0; + video->ohci_IsoRcvContextMatch = 0; + + /* disable interrupts for IR context */ + reg_write(video->ohci, OHCI1394_IsoRecvIntMaskClear, (1 << video->ohci_ir_ctx)); + + /* remove tasklet */ + ohci1394_unregister_iso_tasklet(video->ohci, &video->ir_tasklet); + debug_printk("dv1394: IR context %d released\n", video->ohci_ir_ctx); + video->ohci_ir_ctx = -1; + } + + /* release the ISO channel */ + if (video->channel != -1) { + u64 chan_mask; + unsigned long flags; + + chan_mask = (u64)1 << video->channel; + + spin_lock_irqsave(&video->ohci->IR_channel_lock, flags); + video->ohci->ISO_channel_usage &= ~(chan_mask); + spin_unlock_irqrestore(&video->ohci->IR_channel_lock, flags); + + video->channel = -1; + } + + /* free the frame structs */ + for (i = 0; i < DV1394_MAX_FRAMES; i++) { + if (video->frames[i]) + frame_delete(video->frames[i]); + video->frames[i] = NULL; + } + + video->n_frames = 0; + + /* we can't free the DMA buffer unless it is guaranteed that + no more user-space mappings exist */ + + if (free_dv_buf) { + dma_region_free(&video->dv_buf); + video->dv_buf_size = 0; + } + + /* free packet buffer */ + dma_region_free(&video->packet_buf); + video->packet_buf_size = 0; + + debug_printk("dv1394: shutdown OK\n"); +} + +/* + ********************************** + *** MMAP() THEORY OF OPERATION *** + ********************************** + + The ringbuffer cannot be re-allocated or freed while + a user program maintains a mapping of it. (note that a mapping + can persist even after the device fd is closed!) + + So, only let the user process allocate the DMA buffer once. + To resize or deallocate it, you must close the device file + and open it again. + + Previously Dan M. hacked out a scheme that allowed the DMA + buffer to change by forcefully unmapping it from the user's + address space. It was prone to error because it's very hard to + track all the places the buffer could have been mapped (we + would have had to walk the vma list of every process in the + system to be sure we found all the mappings!). Instead, we + force the user to choose one buffer size and stick with + it. This small sacrifice is worth the huge reduction in + error-prone code in dv1394. +*/ + +static int dv1394_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_card *video = file_to_video_card(file); + int retval = -EINVAL; + + /* + * We cannot use the blocking variant mutex_lock here because .mmap + * is called with mmap_sem held, while .ioctl, .read, .write acquire + * video->mtx and subsequently call copy_to/from_user which will + * grab mmap_sem in case of a page fault. + */ + if (!mutex_trylock(&video->mtx)) + return -EAGAIN; + + if ( ! video_card_initialized(video) ) { + retval = do_dv1394_init_default(video); + if (retval) + goto out; + } + + retval = dma_region_mmap(&video->dv_buf, file, vma); +out: + mutex_unlock(&video->mtx); + return retval; +} + +/*** DEVICE FILE INTERFACE *************************************************/ + +/* no need to serialize, multiple threads OK */ +static unsigned int dv1394_poll(struct file *file, struct poll_table_struct *wait) +{ + struct video_card *video = file_to_video_card(file); + unsigned int mask = 0; + unsigned long flags; + + poll_wait(file, &video->waitq, wait); + + spin_lock_irqsave(&video->spinlock, flags); + if ( video->n_frames == 0 ) { + + } else if ( video->active_frame == -1 ) { + /* nothing going on */ + mask |= POLLOUT; + } else { + /* any clear/ready buffers? */ + if (video->n_clear_frames >0) + mask |= POLLOUT | POLLIN; + } + spin_unlock_irqrestore(&video->spinlock, flags); + + return mask; +} + +static int dv1394_fasync(int fd, struct file *file, int on) +{ + /* I just copied this code verbatim from Alan Cox's mouse driver example + (Documentation/DocBook/) */ + + struct video_card *video = file_to_video_card(file); + + int retval = fasync_helper(fd, file, on, &video->fasync); + + if (retval < 0) + return retval; + return 0; +} + +static ssize_t dv1394_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct video_card *video = file_to_video_card(file); + DECLARE_WAITQUEUE(wait, current); + ssize_t ret; + size_t cnt; + unsigned long flags; + int target_frame; + + /* serialize this to prevent multi-threaded mayhem */ + if (file->f_flags & O_NONBLOCK) { + if (!mutex_trylock(&video->mtx)) + return -EAGAIN; + } else { + if (mutex_lock_interruptible(&video->mtx)) + return -ERESTARTSYS; + } + + if ( !video_card_initialized(video) ) { + ret = do_dv1394_init_default(video); + if (ret) { + mutex_unlock(&video->mtx); + return ret; + } + } + + ret = 0; + add_wait_queue(&video->waitq, &wait); + + while (count > 0) { + + /* must set TASK_INTERRUPTIBLE *before* checking for free + buffers; otherwise we could miss a wakeup if the interrupt + fires between the check and the schedule() */ + + set_current_state(TASK_INTERRUPTIBLE); + + spin_lock_irqsave(&video->spinlock, flags); + + target_frame = video->first_clear_frame; + + spin_unlock_irqrestore(&video->spinlock, flags); + + if (video->frames[target_frame]->state == FRAME_CLEAR) { + + /* how much room is left in the target frame buffer */ + cnt = video->frame_size - (video->write_off - target_frame * video->frame_size); + + } else { + /* buffer is already used */ + cnt = 0; + } + + if (cnt > count) + cnt = count; + + if (cnt <= 0) { + /* no room left, gotta wait */ + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + break; + } + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + break; + } + + schedule(); + + continue; /* start over from 'while(count > 0)...' */ + } + + if (copy_from_user(video->dv_buf.kvirt + video->write_off, buffer, cnt)) { + if (!ret) + ret = -EFAULT; + break; + } + + video->write_off = (video->write_off + cnt) % (video->n_frames * video->frame_size); + + count -= cnt; + buffer += cnt; + ret += cnt; + + if (video->write_off == video->frame_size * ((target_frame + 1) % video->n_frames)) + frame_prepare(video, target_frame); + } + + remove_wait_queue(&video->waitq, &wait); + set_current_state(TASK_RUNNING); + mutex_unlock(&video->mtx); + return ret; +} + + +static ssize_t dv1394_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct video_card *video = file_to_video_card(file); + DECLARE_WAITQUEUE(wait, current); + ssize_t ret; + size_t cnt; + unsigned long flags; + int target_frame; + + /* serialize this to prevent multi-threaded mayhem */ + if (file->f_flags & O_NONBLOCK) { + if (!mutex_trylock(&video->mtx)) + return -EAGAIN; + } else { + if (mutex_lock_interruptible(&video->mtx)) + return -ERESTARTSYS; + } + + if ( !video_card_initialized(video) ) { + ret = do_dv1394_init_default(video); + if (ret) { + mutex_unlock(&video->mtx); + return ret; + } + video->continuity_counter = -1; + + receive_packets(video); + + start_dma_receive(video); + } + + ret = 0; + add_wait_queue(&video->waitq, &wait); + + while (count > 0) { + + /* must set TASK_INTERRUPTIBLE *before* checking for free + buffers; otherwise we could miss a wakeup if the interrupt + fires between the check and the schedule() */ + + set_current_state(TASK_INTERRUPTIBLE); + + spin_lock_irqsave(&video->spinlock, flags); + + target_frame = video->first_clear_frame; + + spin_unlock_irqrestore(&video->spinlock, flags); + + if (target_frame >= 0 && + video->n_clear_frames > 0 && + video->frames[target_frame]->state == FRAME_CLEAR) { + + /* how much room is left in the target frame buffer */ + cnt = video->frame_size - (video->write_off - target_frame * video->frame_size); + + } else { + /* buffer is already used */ + cnt = 0; + } + + if (cnt > count) + cnt = count; + + if (cnt <= 0) { + /* no room left, gotta wait */ + if (file->f_flags & O_NONBLOCK) { + if (!ret) + ret = -EAGAIN; + break; + } + if (signal_pending(current)) { + if (!ret) + ret = -ERESTARTSYS; + break; + } + + schedule(); + + continue; /* start over from 'while(count > 0)...' */ + } + + if (copy_to_user(buffer, video->dv_buf.kvirt + video->write_off, cnt)) { + if (!ret) + ret = -EFAULT; + break; + } + + video->write_off = (video->write_off + cnt) % (video->n_frames * video->frame_size); + + count -= cnt; + buffer += cnt; + ret += cnt; + + if (video->write_off == video->frame_size * ((target_frame + 1) % video->n_frames)) { + spin_lock_irqsave(&video->spinlock, flags); + video->n_clear_frames--; + video->first_clear_frame = (video->first_clear_frame + 1) % video->n_frames; + spin_unlock_irqrestore(&video->spinlock, flags); + } + } + + remove_wait_queue(&video->waitq, &wait); + set_current_state(TASK_RUNNING); + mutex_unlock(&video->mtx); + return ret; +} + + +/*** DEVICE IOCTL INTERFACE ************************************************/ + +static long dv1394_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct video_card *video = file_to_video_card(file); + unsigned long flags; + int ret = -EINVAL; + void __user *argp = (void __user *)arg; + + DECLARE_WAITQUEUE(wait, current); + + /* serialize this to prevent multi-threaded mayhem */ + if (file->f_flags & O_NONBLOCK) { + if (!mutex_trylock(&video->mtx)) + return -EAGAIN; + } else { + if (mutex_lock_interruptible(&video->mtx)) + return -ERESTARTSYS; + } + + switch(cmd) + { + case DV1394_IOC_SUBMIT_FRAMES: { + unsigned int n_submit; + + if ( !video_card_initialized(video) ) { + ret = do_dv1394_init_default(video); + if (ret) + goto out; + } + + n_submit = (unsigned int) arg; + + if (n_submit > video->n_frames) { + ret = -EINVAL; + goto out; + } + + while (n_submit > 0) { + + add_wait_queue(&video->waitq, &wait); + set_current_state(TASK_INTERRUPTIBLE); + + spin_lock_irqsave(&video->spinlock, flags); + + /* wait until video->first_clear_frame is really CLEAR */ + while (video->frames[video->first_clear_frame]->state != FRAME_CLEAR) { + + spin_unlock_irqrestore(&video->spinlock, flags); + + if (signal_pending(current)) { + remove_wait_queue(&video->waitq, &wait); + set_current_state(TASK_RUNNING); + ret = -EINTR; + goto out; + } + + schedule(); + set_current_state(TASK_INTERRUPTIBLE); + + spin_lock_irqsave(&video->spinlock, flags); + } + spin_unlock_irqrestore(&video->spinlock, flags); + + remove_wait_queue(&video->waitq, &wait); + set_current_state(TASK_RUNNING); + + frame_prepare(video, video->first_clear_frame); + + n_submit--; + } + + ret = 0; + break; + } + + case DV1394_IOC_WAIT_FRAMES: { + unsigned int n_wait; + + if ( !video_card_initialized(video) ) { + ret = -EINVAL; + goto out; + } + + n_wait = (unsigned int) arg; + + /* since we re-run the last frame on underflow, we will + never actually have n_frames clear frames; at most only + n_frames - 1 */ + + if (n_wait > (video->n_frames-1) ) { + ret = -EINVAL; + goto out; + } + + add_wait_queue(&video->waitq, &wait); + set_current_state(TASK_INTERRUPTIBLE); + + spin_lock_irqsave(&video->spinlock, flags); + + while (video->n_clear_frames < n_wait) { + + spin_unlock_irqrestore(&video->spinlock, flags); + + if (signal_pending(current)) { + remove_wait_queue(&video->waitq, &wait); + set_current_state(TASK_RUNNING); + ret = -EINTR; + goto out; + } + + schedule(); + set_current_state(TASK_INTERRUPTIBLE); + + spin_lock_irqsave(&video->spinlock, flags); + } + + spin_unlock_irqrestore(&video->spinlock, flags); + + remove_wait_queue(&video->waitq, &wait); + set_current_state(TASK_RUNNING); + ret = 0; + break; + } + + case DV1394_IOC_RECEIVE_FRAMES: { + unsigned int n_recv; + + if ( !video_card_initialized(video) ) { + ret = -EINVAL; + goto out; + } + + n_recv = (unsigned int) arg; + + /* at least one frame must be active */ + if (n_recv > (video->n_frames-1) ) { + ret = -EINVAL; + goto out; + } + + spin_lock_irqsave(&video->spinlock, flags); + + /* release the clear frames */ + video->n_clear_frames -= n_recv; + + /* advance the clear frame cursor */ + video->first_clear_frame = (video->first_clear_frame + n_recv) % video->n_frames; + + /* reset dropped_frames */ + video->dropped_frames = 0; + + spin_unlock_irqrestore(&video->spinlock, flags); + + ret = 0; + break; + } + + case DV1394_IOC_START_RECEIVE: { + if ( !video_card_initialized(video) ) { + ret = do_dv1394_init_default(video); + if (ret) + goto out; + } + + video->continuity_counter = -1; + + receive_packets(video); + + start_dma_receive(video); + + ret = 0; + break; + } + + case DV1394_IOC_INIT: { + struct dv1394_init init; + if (!argp) { + ret = do_dv1394_init_default(video); + } else { + if (copy_from_user(&init, argp, sizeof(init))) { + ret = -EFAULT; + goto out; + } + ret = do_dv1394_init(video, &init); + } + break; + } + + case DV1394_IOC_SHUTDOWN: + do_dv1394_shutdown(video, 0); + ret = 0; + break; + + + case DV1394_IOC_GET_STATUS: { + struct dv1394_status status; + + if ( !video_card_initialized(video) ) { + ret = -EINVAL; + goto out; + } + + status.init.api_version = DV1394_API_VERSION; + status.init.channel = video->channel; + status.init.n_frames = video->n_frames; + status.init.format = video->pal_or_ntsc; + status.init.cip_n = video->cip_n; + status.init.cip_d = video->cip_d; + status.init.syt_offset = video->syt_offset; + + status.first_clear_frame = video->first_clear_frame; + + /* the rest of the fields need to be locked against the interrupt */ + spin_lock_irqsave(&video->spinlock, flags); + + status.active_frame = video->active_frame; + status.n_clear_frames = video->n_clear_frames; + + status.dropped_frames = video->dropped_frames; + + /* reset dropped_frames */ + video->dropped_frames = 0; + + spin_unlock_irqrestore(&video->spinlock, flags); + + if (copy_to_user(argp, &status, sizeof(status))) { + ret = -EFAULT; + goto out; + } + + ret = 0; + break; + } + + default: + break; + } + + out: + mutex_unlock(&video->mtx); + return ret; +} + +/*** DEVICE FILE INTERFACE CONTINUED ***************************************/ + +static int dv1394_open(struct inode *inode, struct file *file) +{ + struct video_card *video = NULL; + + if (file->private_data) { + video = (struct video_card*) file->private_data; + + } else { + /* look up the card by ID */ + unsigned long flags; + + spin_lock_irqsave(&dv1394_cards_lock, flags); + if (!list_empty(&dv1394_cards)) { + struct video_card *p; + list_for_each_entry(p, &dv1394_cards, list) { + if ((p->id) == ieee1394_file_to_instance(file)) { + video = p; + break; + } + } + } + spin_unlock_irqrestore(&dv1394_cards_lock, flags); + + if (!video) { + debug_printk("dv1394: OHCI card %d not found", ieee1394_file_to_instance(file)); + return -ENODEV; + } + + file->private_data = (void*) video; + } + +#ifndef DV1394_ALLOW_MORE_THAN_ONE_OPEN + + if ( test_and_set_bit(0, &video->open) ) { + /* video is already open by someone else */ + return -EBUSY; + } + +#endif + + return 0; +} + + +static int dv1394_release(struct inode *inode, struct file *file) +{ + struct video_card *video = file_to_video_card(file); + + /* OK to free the DMA buffer, no more mappings can exist */ + do_dv1394_shutdown(video, 1); + + /* give someone else a turn */ + clear_bit(0, &video->open); + + return 0; +} + + +/*** DEVICE DRIVER HANDLERS ************************************************/ + +static void it_tasklet_func(unsigned long data) +{ + int wake = 0; + struct video_card *video = (struct video_card*) data; + + spin_lock(&video->spinlock); + + if (!video->dma_running) + goto out; + + irq_printk("ContextControl = %08x, CommandPtr = %08x\n", + reg_read(video->ohci, video->ohci_IsoXmitContextControlSet), + reg_read(video->ohci, video->ohci_IsoXmitCommandPtr) + ); + + + if ( (video->ohci_it_ctx != -1) && + (reg_read(video->ohci, video->ohci_IsoXmitContextControlSet) & (1 << 10)) ) { + + struct frame *f; + unsigned int frame, i; + + + if (video->active_frame == -1) + frame = 0; + else + frame = video->active_frame; + + /* check all the DMA-able frames */ + for (i = 0; i < video->n_frames; i++, frame = (frame+1) % video->n_frames) { + + irq_printk("IRQ checking frame %d...", frame); + f = video->frames[frame]; + if (f->state != FRAME_READY) { + irq_printk("clear, skipping\n"); + /* we don't own this frame */ + continue; + } + + irq_printk("DMA\n"); + + /* check the frame begin semaphore to see if we can free the previous frame */ + if ( *(f->frame_begin_timestamp) ) { + int prev_frame; + struct frame *prev_f; + + + + /* don't reset, need this later *(f->frame_begin_timestamp) = 0; */ + irq_printk(" BEGIN\n"); + + prev_frame = frame - 1; + if (prev_frame == -1) + prev_frame += video->n_frames; + prev_f = video->frames[prev_frame]; + + /* make sure we can actually garbage collect + this frame */ + if ( (prev_f->state == FRAME_READY) && + prev_f->done && (!f->done) ) + { + frame_reset(prev_f); + video->n_clear_frames++; + wake = 1; + video->active_frame = frame; + + irq_printk(" BEGIN - freeing previous frame %d, new active frame is %d\n", prev_frame, frame); + } else { + irq_printk(" BEGIN - can't free yet\n"); + } + + f->done = 1; + } + + + /* see if we need to set the timestamp for the next frame */ + if ( *(f->mid_frame_timestamp) ) { + struct frame *next_frame; + u32 begin_ts, ts_cyc, ts_off; + + *(f->mid_frame_timestamp) = 0; + + begin_ts = le32_to_cpu(*(f->frame_begin_timestamp)); + + irq_printk(" MIDDLE - first packet was sent at cycle %4u (%2u), assigned timestamp was (%2u) %4u\n", + begin_ts & 0x1FFF, begin_ts & 0xF, + f->assigned_timestamp >> 12, f->assigned_timestamp & 0xFFF); + + /* prepare next frame and assign timestamp */ + next_frame = video->frames[ (frame+1) % video->n_frames ]; + + if (next_frame->state == FRAME_READY) { + irq_printk(" MIDDLE - next frame is ready, good\n"); + } else { + debug_printk("dv1394: Underflow! At least one frame has been dropped.\n"); + next_frame = f; + } + + /* set the timestamp to the timestamp of the last frame sent, + plus the length of the last frame sent, plus the syt latency */ + ts_cyc = begin_ts & 0xF; + /* advance one frame, plus syt latency (typically 2-3) */ + ts_cyc += f->n_packets + video->syt_offset ; + + ts_off = 0; + + ts_cyc += ts_off/3072; + ts_off %= 3072; + + next_frame->assigned_timestamp = ((ts_cyc&0xF) << 12) + ts_off; + if (next_frame->cip_syt1) { + next_frame->cip_syt1->b[6] = next_frame->assigned_timestamp >> 8; + next_frame->cip_syt1->b[7] = next_frame->assigned_timestamp & 0xFF; + } + if (next_frame->cip_syt2) { + next_frame->cip_syt2->b[6] = next_frame->assigned_timestamp >> 8; + next_frame->cip_syt2->b[7] = next_frame->assigned_timestamp & 0xFF; + } + + } + + /* see if the frame looped */ + if ( *(f->frame_end_timestamp) ) { + + *(f->frame_end_timestamp) = 0; + + debug_printk(" END - the frame looped at least once\n"); + + video->dropped_frames++; + } + + } /* for (each frame) */ + } + + if (wake) { + kill_fasync(&video->fasync, SIGIO, POLL_OUT); + + /* wake readers/writers/ioctl'ers */ + wake_up_interruptible(&video->waitq); + } + +out: + spin_unlock(&video->spinlock); +} + +static void ir_tasklet_func(unsigned long data) +{ + int wake = 0; + struct video_card *video = (struct video_card*) data; + + spin_lock(&video->spinlock); + + if (!video->dma_running) + goto out; + + if ( (video->ohci_ir_ctx != -1) && + (reg_read(video->ohci, video->ohci_IsoRcvContextControlSet) & (1 << 10)) ) { + + int sof=0; /* start-of-frame flag */ + struct frame *f; + u16 packet_length, packet_time; + int i, dbc=0; + struct DMA_descriptor_block *block = NULL; + u16 xferstatus; + + int next_i, prev_i; + struct DMA_descriptor_block *next = NULL; + dma_addr_t next_dma = 0; + struct DMA_descriptor_block *prev = NULL; + + /* loop over all descriptors in all frames */ + for (i = 0; i < video->n_frames*MAX_PACKETS; i++) { + struct packet *p = dma_region_i(&video->packet_buf, struct packet, video->current_packet); + + /* make sure we are seeing the latest changes to p */ + dma_region_sync_for_cpu(&video->packet_buf, + (unsigned long) p - (unsigned long) video->packet_buf.kvirt, + sizeof(struct packet)); + + packet_length = le16_to_cpu(p->data_length); + packet_time = le16_to_cpu(p->timestamp); + + irq_printk("received packet %02d, timestamp=%04x, length=%04x, sof=%02x%02x\n", video->current_packet, + packet_time, packet_length, + p->data[0], p->data[1]); + + /* get the descriptor based on packet_buffer cursor */ + f = video->frames[video->current_packet / MAX_PACKETS]; + block = &(f->descriptor_pool[video->current_packet % MAX_PACKETS]); + xferstatus = le32_to_cpu(block->u.in.il.q[3]) >> 16; + xferstatus &= 0x1F; + irq_printk("ir_tasklet_func: xferStatus/resCount [%d] = 0x%08x\n", i, le32_to_cpu(block->u.in.il.q[3]) ); + + /* get the current frame */ + f = video->frames[video->active_frame]; + + /* exclude empty packet */ + if (packet_length > 8 && xferstatus == 0x11) { + /* check for start of frame */ + /* DRD> Changed to check section type ([0]>>5==0) + and dif sequence ([1]>>4==0) */ + sof = ( (p->data[0] >> 5) == 0 && (p->data[1] >> 4) == 0); + + dbc = (int) (p->cip_h1 >> 24); + if ( video->continuity_counter != -1 && dbc > ((video->continuity_counter + 1) % 256) ) + { + printk(KERN_WARNING "dv1394: discontinuity detected, dropping all frames\n" ); + video->dropped_frames += video->n_clear_frames + 1; + video->first_frame = 0; + video->n_clear_frames = 0; + video->first_clear_frame = -1; + } + video->continuity_counter = dbc; + + if (!video->first_frame) { + if (sof) { + video->first_frame = 1; + } + + } else if (sof) { + /* close current frame */ + frame_reset(f); /* f->state = STATE_CLEAR */ + video->n_clear_frames++; + if (video->n_clear_frames > video->n_frames) { + video->dropped_frames++; + printk(KERN_WARNING "dv1394: dropped a frame during reception\n" ); + video->n_clear_frames = video->n_frames-1; + video->first_clear_frame = (video->first_clear_frame + 1) % video->n_frames; + } + if (video->first_clear_frame == -1) + video->first_clear_frame = video->active_frame; + + /* get the next frame */ + video->active_frame = (video->active_frame + 1) % video->n_frames; + f = video->frames[video->active_frame]; + irq_printk(" frame received, active_frame = %d, n_clear_frames = %d, first_clear_frame = %d\n", + video->active_frame, video->n_clear_frames, video->first_clear_frame); + } + if (video->first_frame) { + if (sof) { + /* open next frame */ + f->state = FRAME_READY; + } + + /* copy to buffer */ + if (f->n_packets > (video->frame_size / 480)) { + printk(KERN_ERR "frame buffer overflow during receive\n"); + } + + frame_put_packet(f, p); + + } /* first_frame */ + } + + /* stop, end of ready packets */ + else if (xferstatus == 0) { + break; + } + + /* reset xferStatus & resCount */ + block->u.in.il.q[3] = cpu_to_le32(512); + + /* terminate dma chain at this (next) packet */ + next_i = video->current_packet; + f = video->frames[next_i / MAX_PACKETS]; + next = &(f->descriptor_pool[next_i % MAX_PACKETS]); + next_dma = ((unsigned long) block - (unsigned long) f->descriptor_pool) + f->descriptor_pool_dma; + next->u.in.il.q[0] |= 3 << 20; /* enable interrupt */ + next->u.in.il.q[2] = 0; /* disable branch */ + + /* link previous to next */ + prev_i = (next_i == 0) ? (MAX_PACKETS * video->n_frames - 1) : (next_i - 1); + f = video->frames[prev_i / MAX_PACKETS]; + prev = &(f->descriptor_pool[prev_i % MAX_PACKETS]); + if (prev_i % (MAX_PACKETS/2)) { + prev->u.in.il.q[0] &= ~(3 << 20); /* no interrupt */ + } else { + prev->u.in.il.q[0] |= 3 << 20; /* enable interrupt */ + } + prev->u.in.il.q[2] = cpu_to_le32(next_dma | 1); /* set Z=1 */ + wmb(); + + /* wake up DMA in case it fell asleep */ + reg_write(video->ohci, video->ohci_IsoRcvContextControlSet, (1 << 12)); + + /* advance packet_buffer cursor */ + video->current_packet = (video->current_packet + 1) % (MAX_PACKETS * video->n_frames); + + } /* for all packets */ + + wake = 1; /* why the hell not? */ + + } /* receive interrupt */ + + if (wake) { + kill_fasync(&video->fasync, SIGIO, POLL_IN); + + /* wake readers/writers/ioctl'ers */ + wake_up_interruptible(&video->waitq); + } + +out: + spin_unlock(&video->spinlock); +} + +static struct cdev dv1394_cdev; +static const struct file_operations dv1394_fops= +{ + .owner = THIS_MODULE, + .poll = dv1394_poll, + .unlocked_ioctl = dv1394_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = dv1394_compat_ioctl, +#endif + .mmap = dv1394_mmap, + .open = dv1394_open, + .write = dv1394_write, + .read = dv1394_read, + .release = dv1394_release, + .fasync = dv1394_fasync, +}; + + +/*** HOTPLUG STUFF **********************************************************/ +/* + * Export information about protocols/devices supported by this driver. + */ +#ifdef MODULE +static struct ieee1394_device_id dv1394_id_table[] = { + { + .match_flags = IEEE1394_MATCH_SPECIFIER_ID | IEEE1394_MATCH_VERSION, + .specifier_id = AVC_UNIT_SPEC_ID_ENTRY & 0xffffff, + .version = AVC_SW_VERSION_ENTRY & 0xffffff + }, + { } +}; + +MODULE_DEVICE_TABLE(ieee1394, dv1394_id_table); +#endif /* MODULE */ + +static struct hpsb_protocol_driver dv1394_driver = { + .name = "dv1394", +}; + + +/*** IEEE1394 HPSB CALLBACKS ***********************************************/ + +static int dv1394_init(struct ti_ohci *ohci, enum pal_or_ntsc format, enum modes mode) +{ + struct video_card *video; + unsigned long flags; + int i; + + video = kzalloc(sizeof(*video), GFP_KERNEL); + if (!video) { + printk(KERN_ERR "dv1394: cannot allocate video_card\n"); + return -1; + } + + video->ohci = ohci; + /* lower 2 bits of id indicate which of four "plugs" + per host */ + video->id = ohci->host->id << 2; + if (format == DV1394_NTSC) + video->id |= mode; + else + video->id |= 2 + mode; + + video->ohci_it_ctx = -1; + video->ohci_ir_ctx = -1; + + video->ohci_IsoXmitContextControlSet = 0; + video->ohci_IsoXmitContextControlClear = 0; + video->ohci_IsoXmitCommandPtr = 0; + + video->ohci_IsoRcvContextControlSet = 0; + video->ohci_IsoRcvContextControlClear = 0; + video->ohci_IsoRcvCommandPtr = 0; + video->ohci_IsoRcvContextMatch = 0; + + video->n_frames = 0; /* flag that video is not initialized */ + video->channel = 63; /* default to broadcast channel */ + video->active_frame = -1; + + /* initialize the following */ + video->pal_or_ntsc = format; + video->cip_n = 0; /* 0 = use builtin default */ + video->cip_d = 0; + video->syt_offset = 0; + video->mode = mode; + + for (i = 0; i < DV1394_MAX_FRAMES; i++) + video->frames[i] = NULL; + + dma_region_init(&video->dv_buf); + video->dv_buf_size = 0; + dma_region_init(&video->packet_buf); + video->packet_buf_size = 0; + + clear_bit(0, &video->open); + spin_lock_init(&video->spinlock); + video->dma_running = 0; + mutex_init(&video->mtx); + init_waitqueue_head(&video->waitq); + video->fasync = NULL; + + spin_lock_irqsave(&dv1394_cards_lock, flags); + INIT_LIST_HEAD(&video->list); + list_add_tail(&video->list, &dv1394_cards); + spin_unlock_irqrestore(&dv1394_cards_lock, flags); + + debug_printk("dv1394: dv1394_init() OK on ID %d\n", video->id); + return 0; +} + +static void dv1394_remove_host(struct hpsb_host *host) +{ + struct video_card *video, *tmp_video; + unsigned long flags; + int found_ohci_card = 0; + + do { + video = NULL; + spin_lock_irqsave(&dv1394_cards_lock, flags); + list_for_each_entry(tmp_video, &dv1394_cards, list) { + if ((tmp_video->id >> 2) == host->id) { + list_del(&tmp_video->list); + video = tmp_video; + found_ohci_card = 1; + break; + } + } + spin_unlock_irqrestore(&dv1394_cards_lock, flags); + + if (video) { + do_dv1394_shutdown(video, 1); + kfree(video); + } + } while (video); + + if (found_ohci_card) + device_destroy(hpsb_protocol_class, MKDEV(IEEE1394_MAJOR, + IEEE1394_MINOR_BLOCK_DV1394 * 16 + (host->id << 2))); +} + +static void dv1394_add_host(struct hpsb_host *host) +{ + struct ti_ohci *ohci; + int id = host->id; + + /* We only work with the OHCI-1394 driver */ + if (strcmp(host->driver->name, OHCI1394_DRIVER_NAME)) + return; + + ohci = (struct ti_ohci *)host->hostdata; + + device_create(hpsb_protocol_class, NULL, + MKDEV(IEEE1394_MAJOR, + IEEE1394_MINOR_BLOCK_DV1394 * 16 + (id<<2)), + NULL, "dv1394-%d", id); + + dv1394_init(ohci, DV1394_NTSC, MODE_RECEIVE); + dv1394_init(ohci, DV1394_NTSC, MODE_TRANSMIT); + dv1394_init(ohci, DV1394_PAL, MODE_RECEIVE); + dv1394_init(ohci, DV1394_PAL, MODE_TRANSMIT); +} + + +/* Bus reset handler. In the event of a bus reset, we may need to + re-start the DMA contexts - otherwise the user program would + end up waiting forever. +*/ + +static void dv1394_host_reset(struct hpsb_host *host) +{ + struct ti_ohci *ohci; + struct video_card *video = NULL, *tmp_vid; + unsigned long flags; + + /* We only work with the OHCI-1394 driver */ + if (strcmp(host->driver->name, OHCI1394_DRIVER_NAME)) + return; + + ohci = (struct ti_ohci *)host->hostdata; + + + /* find the corresponding video_cards */ + spin_lock_irqsave(&dv1394_cards_lock, flags); + list_for_each_entry(tmp_vid, &dv1394_cards, list) { + if ((tmp_vid->id >> 2) == host->id) { + video = tmp_vid; + break; + } + } + spin_unlock_irqrestore(&dv1394_cards_lock, flags); + + if (!video) + return; + + + spin_lock_irqsave(&video->spinlock, flags); + + if (!video->dma_running) + goto out; + + /* check IT context */ + if (video->ohci_it_ctx != -1) { + u32 ctx; + + ctx = reg_read(video->ohci, video->ohci_IsoXmitContextControlSet); + + /* if (RUN but not ACTIVE) */ + if ( (ctx & (1<<15)) && + !(ctx & (1<<10)) ) { + + debug_printk("dv1394: IT context stopped due to bus reset; waking it up\n"); + + /* to be safe, assume a frame has been dropped. User-space programs + should handle this condition like an underflow. */ + video->dropped_frames++; + + /* for some reason you must clear, then re-set the RUN bit to restart DMA */ + + /* clear RUN */ + reg_write(video->ohci, video->ohci_IsoXmitContextControlClear, (1 << 15)); + flush_pci_write(video->ohci); + + /* set RUN */ + reg_write(video->ohci, video->ohci_IsoXmitContextControlSet, (1 << 15)); + flush_pci_write(video->ohci); + + /* set the WAKE bit (just in case; this isn't strictly necessary) */ + reg_write(video->ohci, video->ohci_IsoXmitContextControlSet, (1 << 12)); + flush_pci_write(video->ohci); + + irq_printk("dv1394: AFTER IT restart ctx 0x%08x ptr 0x%08x\n", + reg_read(video->ohci, video->ohci_IsoXmitContextControlSet), + reg_read(video->ohci, video->ohci_IsoXmitCommandPtr)); + } + } + + /* check IR context */ + if (video->ohci_ir_ctx != -1) { + u32 ctx; + + ctx = reg_read(video->ohci, video->ohci_IsoRcvContextControlSet); + + /* if (RUN but not ACTIVE) */ + if ( (ctx & (1<<15)) && + !(ctx & (1<<10)) ) { + + debug_printk("dv1394: IR context stopped due to bus reset; waking it up\n"); + + /* to be safe, assume a frame has been dropped. User-space programs + should handle this condition like an overflow. */ + video->dropped_frames++; + + /* for some reason you must clear, then re-set the RUN bit to restart DMA */ + /* XXX this doesn't work for me, I can't get IR DMA to restart :[ */ + + /* clear RUN */ + reg_write(video->ohci, video->ohci_IsoRcvContextControlClear, (1 << 15)); + flush_pci_write(video->ohci); + + /* set RUN */ + reg_write(video->ohci, video->ohci_IsoRcvContextControlSet, (1 << 15)); + flush_pci_write(video->ohci); + + /* set the WAKE bit (just in case; this isn't strictly necessary) */ + reg_write(video->ohci, video->ohci_IsoRcvContextControlSet, (1 << 12)); + flush_pci_write(video->ohci); + + irq_printk("dv1394: AFTER IR restart ctx 0x%08x ptr 0x%08x\n", + reg_read(video->ohci, video->ohci_IsoRcvContextControlSet), + reg_read(video->ohci, video->ohci_IsoRcvCommandPtr)); + } + } + +out: + spin_unlock_irqrestore(&video->spinlock, flags); + + /* wake readers/writers/ioctl'ers */ + wake_up_interruptible(&video->waitq); +} + +static struct hpsb_highlevel dv1394_highlevel = { + .name = "dv1394", + .add_host = dv1394_add_host, + .remove_host = dv1394_remove_host, + .host_reset = dv1394_host_reset, +}; + +#ifdef CONFIG_COMPAT + +#define DV1394_IOC32_INIT _IOW('#', 0x06, struct dv1394_init32) +#define DV1394_IOC32_GET_STATUS _IOR('#', 0x0c, struct dv1394_status32) + +struct dv1394_init32 { + u32 api_version; + u32 channel; + u32 n_frames; + u32 format; + u32 cip_n; + u32 cip_d; + u32 syt_offset; +}; + +struct dv1394_status32 { + struct dv1394_init32 init; + s32 active_frame; + u32 first_clear_frame; + u32 n_clear_frames; + u32 dropped_frames; +}; + +/* RED-PEN: this should use compat_alloc_userspace instead */ + +static int handle_dv1394_init(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct dv1394_init32 dv32; + struct dv1394_init dv; + mm_segment_t old_fs; + int ret; + + if (file->f_op->unlocked_ioctl != dv1394_ioctl) + return -EFAULT; + + if (copy_from_user(&dv32, (void __user *)arg, sizeof(dv32))) + return -EFAULT; + + dv.api_version = dv32.api_version; + dv.channel = dv32.channel; + dv.n_frames = dv32.n_frames; + dv.format = dv32.format; + dv.cip_n = (unsigned long)dv32.cip_n; + dv.cip_d = (unsigned long)dv32.cip_d; + dv.syt_offset = dv32.syt_offset; + + old_fs = get_fs(); + set_fs(KERNEL_DS); + ret = dv1394_ioctl(file, DV1394_IOC_INIT, (unsigned long)&dv); + set_fs(old_fs); + + return ret; +} + +static int handle_dv1394_get_status(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct dv1394_status32 dv32; + struct dv1394_status dv; + mm_segment_t old_fs; + int ret; + + if (file->f_op->unlocked_ioctl != dv1394_ioctl) + return -EFAULT; + + old_fs = get_fs(); + set_fs(KERNEL_DS); + ret = dv1394_ioctl(file, DV1394_IOC_GET_STATUS, (unsigned long)&dv); + set_fs(old_fs); + + if (!ret) { + dv32.init.api_version = dv.init.api_version; + dv32.init.channel = dv.init.channel; + dv32.init.n_frames = dv.init.n_frames; + dv32.init.format = dv.init.format; + dv32.init.cip_n = (u32)dv.init.cip_n; + dv32.init.cip_d = (u32)dv.init.cip_d; + dv32.init.syt_offset = dv.init.syt_offset; + dv32.active_frame = dv.active_frame; + dv32.first_clear_frame = dv.first_clear_frame; + dv32.n_clear_frames = dv.n_clear_frames; + dv32.dropped_frames = dv.dropped_frames; + + if (copy_to_user((struct dv1394_status32 __user *)arg, &dv32, sizeof(dv32))) + ret = -EFAULT; + } + + return ret; +} + + + +static long dv1394_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + switch (cmd) { + case DV1394_IOC_SHUTDOWN: + case DV1394_IOC_SUBMIT_FRAMES: + case DV1394_IOC_WAIT_FRAMES: + case DV1394_IOC_RECEIVE_FRAMES: + case DV1394_IOC_START_RECEIVE: + return dv1394_ioctl(file, cmd, arg); + + case DV1394_IOC32_INIT: + return handle_dv1394_init(file, cmd, arg); + case DV1394_IOC32_GET_STATUS: + return handle_dv1394_get_status(file, cmd, arg); + default: + return -ENOIOCTLCMD; + } +} + +#endif /* CONFIG_COMPAT */ + + +/*** KERNEL MODULE HANDLERS ************************************************/ + +MODULE_AUTHOR("Dan Maas <dmaas@dcine.com>, Dan Dennedy <dan@dennedy.org>"); +MODULE_DESCRIPTION("driver for DV input/output on OHCI board"); +MODULE_SUPPORTED_DEVICE("dv1394"); +MODULE_LICENSE("GPL"); + +static void __exit dv1394_exit_module(void) +{ + hpsb_unregister_protocol(&dv1394_driver); + hpsb_unregister_highlevel(&dv1394_highlevel); + cdev_del(&dv1394_cdev); +} + +static int __init dv1394_init_module(void) +{ + int ret; + + printk(KERN_WARNING + "NOTE: The dv1394 driver is unsupported and may be removed in a " + "future Linux release. Use raw1394 instead.\n"); + + cdev_init(&dv1394_cdev, &dv1394_fops); + dv1394_cdev.owner = THIS_MODULE; + ret = cdev_add(&dv1394_cdev, IEEE1394_DV1394_DEV, 16); + if (ret) { + printk(KERN_ERR "dv1394: unable to register character device\n"); + return ret; + } + + hpsb_register_highlevel(&dv1394_highlevel); + + ret = hpsb_register_protocol(&dv1394_driver); + if (ret) { + printk(KERN_ERR "dv1394: failed to register protocol\n"); + hpsb_unregister_highlevel(&dv1394_highlevel); + cdev_del(&dv1394_cdev); + return ret; + } + + return 0; +} + +module_init(dv1394_init_module); +module_exit(dv1394_exit_module); diff --git a/drivers/ieee1394/dv1394.h b/drivers/ieee1394/dv1394.h new file mode 100644 index 0000000..5807f52 --- /dev/null +++ b/drivers/ieee1394/dv1394.h @@ -0,0 +1,305 @@ +/* + * dv1394.h - DV input/output over IEEE 1394 on OHCI chips + * Copyright (C)2001 Daniel Maas <dmaas@dcine.com> + * receive by Dan Dennedy <dan@dennedy.org> + * + * based on: + * video1394.h - driver for OHCI 1394 boards + * Copyright (C)1999,2000 Sebastien Rougeaux <sebastien.rougeaux@anu.edu.au> + * Peter Schlaile <udbz@rz.uni-karlsruhe.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _DV_1394_H +#define _DV_1394_H + +/* This is the public user-space interface. Try not to break it. */ + +#define DV1394_API_VERSION 0x20011127 + +/* ******************** + ** ** + ** DV1394 API ** + ** ** + ******************** + + There are two methods of operating the DV1394 DV output device. + + 1) + + The simplest is an interface based on write(): simply write + full DV frames of data to the device, and they will be transmitted + as quickly as possible. The FD may be set for non-blocking I/O, + in which case you can use select() or poll() to wait for output + buffer space. + + To set the DV output parameters (e.g. whether you want NTSC or PAL + video), use the DV1394_INIT ioctl, passing in the parameters you + want in a struct dv1394_init. + + Example 1: + To play a raw .DV file: cat foo.DV > /dev/dv1394 + (cat will use write() internally) + + Example 2: + static struct dv1394_init init = { + 0x63, (broadcast channel) + 4, (four-frame ringbuffer) + DV1394_NTSC, (send NTSC video) + 0, 0 (default empty packet rate) + } + + ioctl(fd, DV1394_INIT, &init); + + while (1) { + read( <a raw DV file>, buf, DV1394_NTSC_FRAME_SIZE ); + write( <the dv1394 FD>, buf, DV1394_NTSC_FRAME_SIZE ); + } + + 2) + + For more control over buffering, and to avoid unnecessary copies + of the DV data, you can use the more sophisticated the mmap() interface. + First, call the DV1394_INIT ioctl to specify your parameters, + including the number of frames in the ringbuffer. Then, calling mmap() + on the dv1394 device will give you direct access to the ringbuffer + from which the DV card reads your frame data. + + The ringbuffer is simply one large, contiguous region of memory + containing two or more frames of packed DV data. Each frame of DV data + is 120000 bytes (NTSC) or 144000 bytes (PAL). + + Fill one or more frames in the ringbuffer, then use the DV1394_SUBMIT_FRAMES + ioctl to begin I/O. You can use either the DV1394_WAIT_FRAMES ioctl + or select()/poll() to wait until the frames are transmitted. Next, you'll + need to call the DV1394_GET_STATUS ioctl to determine which ringbuffer + frames are clear (ready to be filled with new DV data). Finally, use + DV1394_SUBMIT_FRAMES again to send the new data to the DV output. + + + Example: here is what a four-frame ringbuffer might look like + during DV transmission: + + + frame 0 frame 1 frame 2 frame 3 + + *--------------------------------------* + | CLEAR | DV data | DV data | CLEAR | + *--------------------------------------* + <ACTIVE> + + transmission goes in this direction --->>> + + + The DV hardware is currently transmitting the data in frame 1. + Once frame 1 is finished, it will automatically transmit frame 2. + (if frame 2 finishes before frame 3 is submitted, the device + will continue to transmit frame 2, and will increase the dropped_frames + counter each time it repeats the transmission). + + + If you called DV1394_GET_STATUS at this instant, you would + receive the following values: + + n_frames = 4 + active_frame = 1 + first_clear_frame = 3 + n_clear_frames = 2 + + At this point, you should write new DV data into frame 3 and optionally + frame 0. Then call DV1394_SUBMIT_FRAMES to inform the device that + it may transmit the new frames. + + ERROR HANDLING + + An error (buffer underflow/overflow or a break in the DV stream due + to a 1394 bus reset) can be detected by checking the dropped_frames + field of struct dv1394_status (obtained through the + DV1394_GET_STATUS ioctl). + + The best way to recover from such an error is to re-initialize + dv1394, either by using the DV1394_INIT ioctl call, or closing the + file descriptor and opening it again. (note that you must unmap all + ringbuffer mappings when closing the file descriptor, or else + dv1394 will still be considered 'in use'). + + MAIN LOOP + + For maximum efficiency and robustness against bus errors, you are + advised to model the main loop of your application after the + following pseudo-code example: + + (checks of system call return values omitted for brevity; always + check return values in your code!) + + while ( frames left ) { + + struct pollfd *pfd = ...; + + pfd->fd = dv1394_fd; + pfd->revents = 0; + pfd->events = POLLOUT | POLLIN; (OUT for transmit, IN for receive) + + (add other sources of I/O here) + + poll(pfd, 1, -1); (or select(); add a timeout if you want) + + if (pfd->revents) { + struct dv1394_status status; + + ioctl(dv1394_fd, DV1394_GET_STATUS, &status); + + if (status.dropped_frames > 0) { + reset_dv1394(); + } else { + for (int i = 0; i < status.n_clear_frames; i++) { + copy_DV_frame(); + } + } + } + } + + where copy_DV_frame() reads or writes on the dv1394 file descriptor + (read/write mode) or copies data to/from the mmap ringbuffer and + then calls ioctl(DV1394_SUBMIT_FRAMES) to notify dv1394 that new + frames are availble (mmap mode). + + reset_dv1394() is called in the event of a buffer + underflow/overflow or a halt in the DV stream (e.g. due to a 1394 + bus reset). To guarantee recovery from the error, this function + should close the dv1394 file descriptor (and munmap() all + ringbuffer mappings, if you are using them), then re-open the + dv1394 device (and re-map the ringbuffer). + +*/ + + +/* maximum number of frames in the ringbuffer */ +#define DV1394_MAX_FRAMES 32 + +/* number of *full* isochronous packets per DV frame */ +#define DV1394_NTSC_PACKETS_PER_FRAME 250 +#define DV1394_PAL_PACKETS_PER_FRAME 300 + +/* size of one frame's worth of DV data, in bytes */ +#define DV1394_NTSC_FRAME_SIZE (480 * DV1394_NTSC_PACKETS_PER_FRAME) +#define DV1394_PAL_FRAME_SIZE (480 * DV1394_PAL_PACKETS_PER_FRAME) + + +/* ioctl() commands */ +#include "ieee1394-ioctl.h" + + +enum pal_or_ntsc { + DV1394_NTSC = 0, + DV1394_PAL +}; + + + + +/* this is the argument to DV1394_INIT */ +struct dv1394_init { + /* DV1394_API_VERSION */ + unsigned int api_version; + + /* isochronous transmission channel to use */ + unsigned int channel; + + /* number of frames in the ringbuffer. Must be at least 2 + and at most DV1394_MAX_FRAMES. */ + unsigned int n_frames; + + /* send/receive PAL or NTSC video format */ + enum pal_or_ntsc format; + + /* the following are used only for transmission */ + + /* set these to zero unless you want a + non-default empty packet rate (see below) */ + unsigned long cip_n; + unsigned long cip_d; + + /* set this to zero unless you want a + non-default SYT cycle offset (default = 3 cycles) */ + unsigned int syt_offset; +}; + +/* NOTE: you may only allocate the DV frame ringbuffer once each time + you open the dv1394 device. DV1394_INIT will fail if you call it a + second time with different 'n_frames' or 'format' arguments (which + would imply a different size for the ringbuffer). If you need a + different buffer size, simply close and re-open the device, then + initialize it with your new settings. */ + +/* Q: What are cip_n and cip_d? */ + +/* + A: DV video streams do not utilize 100% of the potential bandwidth offered + by IEEE 1394 (FireWire). To achieve the correct rate of data transmission, + DV devices must periodically insert empty packets into the 1394 data stream. + Typically there is one empty packet per 14-16 data-carrying packets. + + Some DV devices will accept a wide range of empty packet rates, while others + require a precise rate. If the dv1394 driver produces empty packets at + a rate that your device does not accept, you may see ugly patterns on the + DV output, or even no output at all. + + The default empty packet insertion rate seems to work for many people; if + your DV output is stable, you can simply ignore this discussion. However, + we have exposed the empty packet rate as a parameter to support devices that + do not work with the default rate. + + The decision to insert an empty packet is made with a numerator/denominator + algorithm. Empty packets are produced at an average rate of CIP_N / CIP_D. + You can alter the empty packet rate by passing non-zero values for cip_n + and cip_d to the INIT ioctl. + + */ + + + +struct dv1394_status { + /* this embedded init struct returns the current dv1394 + parameters in use */ + struct dv1394_init init; + + /* the ringbuffer frame that is currently being + displayed. (-1 if the device is not transmitting anything) */ + int active_frame; + + /* index of the first buffer (ahead of active_frame) that + is ready to be filled with data */ + unsigned int first_clear_frame; + + /* how many buffers, including first_clear_buffer, are + ready to be filled with data */ + unsigned int n_clear_frames; + + /* how many times the DV stream has underflowed, overflowed, + or otherwise encountered an error, since the previous call + to DV1394_GET_STATUS */ + unsigned int dropped_frames; + + /* N.B. The dropped_frames counter is only a lower bound on the actual + number of dropped frames, with the special case that if dropped_frames + is zero, then it is guaranteed that NO frames have been dropped + since the last call to DV1394_GET_STATUS. + */ +}; + + +#endif /* _DV_1394_H */ diff --git a/drivers/ieee1394/eth1394.c b/drivers/ieee1394/eth1394.c new file mode 100644 index 0000000..2012869 --- /dev/null +++ b/drivers/ieee1394/eth1394.c @@ -0,0 +1,1752 @@ +/* + * eth1394.c -- IPv4 driver for Linux IEEE-1394 Subsystem + * + * Copyright (C) 2001-2003 Ben Collins <bcollins@debian.org> + * 2000 Bonin Franck <boninf@free.fr> + * 2003 Steve Kinneberg <kinnebergsteve@acmsystems.com> + * + * Mainly based on work by Emanuel Pirker and Andreas E. Bombe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * This driver intends to support RFC 2734, which describes a method for + * transporting IPv4 datagrams over IEEE-1394 serial busses. + * + * TODO: + * RFC 2734 related: + * - Add MCAP. Limited Multicast exists only to 224.0.0.1 and 224.0.0.2. + * + * Non-RFC 2734 related: + * - Handle fragmented skb's coming from the networking layer. + * - Move generic GASP reception to core 1394 code + * - Convert kmalloc/kfree for link fragments to use kmem_cache_* instead + * - Stability improvements + * - Performance enhancements + * - Consider garbage collecting old partial datagrams after X amount of time + */ + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/workqueue.h> + +#include <linux/netdevice.h> +#include <linux/inetdevice.h> +#include <linux/if_arp.h> +#include <linux/if_ether.h> +#include <linux/ip.h> +#include <linux/in.h> +#include <linux/tcp.h> +#include <linux/skbuff.h> +#include <linux/bitops.h> +#include <linux/ethtool.h> +#include <asm/uaccess.h> +#include <asm/delay.h> +#include <asm/unaligned.h> +#include <net/arp.h> + +#include "config_roms.h" +#include "csr1212.h" +#include "eth1394.h" +#include "highlevel.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" + +#define ETH1394_PRINT_G(level, fmt, args...) \ + printk(level "%s: " fmt, driver_name, ## args) + +#define ETH1394_PRINT(level, dev_name, fmt, args...) \ + printk(level "%s: %s: " fmt, driver_name, dev_name, ## args) + +struct fragment_info { + struct list_head list; + int offset; + int len; +}; + +struct partial_datagram { + struct list_head list; + u16 dgl; + u16 dg_size; + u16 ether_type; + struct sk_buff *skb; + char *pbuf; + struct list_head frag_info; +}; + +struct pdg_list { + struct list_head list; /* partial datagram list per node */ + unsigned int sz; /* partial datagram list size per node */ + spinlock_t lock; /* partial datagram lock */ +}; + +struct eth1394_host_info { + struct hpsb_host *host; + struct net_device *dev; +}; + +struct eth1394_node_ref { + struct unit_directory *ud; + struct list_head list; +}; + +struct eth1394_node_info { + u16 maxpayload; /* max payload */ + u8 sspd; /* max speed */ + u64 fifo; /* FIFO address */ + struct pdg_list pdg; /* partial RX datagram lists */ + int dgl; /* outgoing datagram label */ +}; + +static const char driver_name[] = "eth1394"; + +static struct kmem_cache *packet_task_cache; + +static struct hpsb_highlevel eth1394_highlevel; + +/* Use common.lf to determine header len */ +static const int hdr_type_len[] = { + sizeof(struct eth1394_uf_hdr), + sizeof(struct eth1394_ff_hdr), + sizeof(struct eth1394_sf_hdr), + sizeof(struct eth1394_sf_hdr) +}; + +static const u16 eth1394_speedto_maxpayload[] = { +/* S100, S200, S400, S800, S1600, S3200 */ + 512, 1024, 2048, 4096, 4096, 4096 +}; + +MODULE_AUTHOR("Ben Collins (bcollins@debian.org)"); +MODULE_DESCRIPTION("IEEE 1394 IPv4 Driver (IPv4-over-1394 as per RFC 2734)"); +MODULE_LICENSE("GPL"); + +/* + * The max_partial_datagrams parameter is the maximum number of fragmented + * datagrams per node that eth1394 will keep in memory. Providing an upper + * bound allows us to limit the amount of memory that partial datagrams + * consume in the event that some partial datagrams are never completed. + */ +static int max_partial_datagrams = 25; +module_param(max_partial_datagrams, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(max_partial_datagrams, + "Maximum number of partially received fragmented datagrams " + "(default = 25)."); + + +static int ether1394_header(struct sk_buff *skb, struct net_device *dev, + unsigned short type, const void *daddr, + const void *saddr, unsigned len); +static int ether1394_rebuild_header(struct sk_buff *skb); +static int ether1394_header_parse(const struct sk_buff *skb, + unsigned char *haddr); +static int ether1394_header_cache(const struct neighbour *neigh, + struct hh_cache *hh); +static void ether1394_header_cache_update(struct hh_cache *hh, + const struct net_device *dev, + const unsigned char *haddr); +static int ether1394_tx(struct sk_buff *skb, struct net_device *dev); +static void ether1394_iso(struct hpsb_iso *iso); + +static struct ethtool_ops ethtool_ops; + +static int ether1394_write(struct hpsb_host *host, int srcid, int destid, + quadlet_t *data, u64 addr, size_t len, u16 flags); +static void ether1394_add_host(struct hpsb_host *host); +static void ether1394_remove_host(struct hpsb_host *host); +static void ether1394_host_reset(struct hpsb_host *host); + +/* Function for incoming 1394 packets */ +static struct hpsb_address_ops addr_ops = { + .write = ether1394_write, +}; + +/* Ieee1394 highlevel driver functions */ +static struct hpsb_highlevel eth1394_highlevel = { + .name = driver_name, + .add_host = ether1394_add_host, + .remove_host = ether1394_remove_host, + .host_reset = ether1394_host_reset, +}; + +static int ether1394_recv_init(struct eth1394_priv *priv) +{ + unsigned int iso_buf_size; + + /* FIXME: rawiso limits us to PAGE_SIZE */ + iso_buf_size = min((unsigned int)PAGE_SIZE, + 2 * (1U << (priv->host->csr.max_rec + 1))); + + priv->iso = hpsb_iso_recv_init(priv->host, + ETHER1394_GASP_BUFFERS * iso_buf_size, + ETHER1394_GASP_BUFFERS, + priv->broadcast_channel, + HPSB_ISO_DMA_PACKET_PER_BUFFER, + 1, ether1394_iso); + if (priv->iso == NULL) { + ETH1394_PRINT_G(KERN_ERR, "Failed to allocate IR context\n"); + priv->bc_state = ETHER1394_BC_ERROR; + return -EAGAIN; + } + + if (hpsb_iso_recv_start(priv->iso, -1, (1 << 3), -1) < 0) + priv->bc_state = ETHER1394_BC_STOPPED; + else + priv->bc_state = ETHER1394_BC_RUNNING; + return 0; +} + +/* This is called after an "ifup" */ +static int ether1394_open(struct net_device *dev) +{ + struct eth1394_priv *priv = netdev_priv(dev); + int ret; + + if (priv->bc_state == ETHER1394_BC_ERROR) { + ret = ether1394_recv_init(priv); + if (ret) + return ret; + } + netif_start_queue(dev); + return 0; +} + +/* This is called after an "ifdown" */ +static int ether1394_stop(struct net_device *dev) +{ + /* flush priv->wake */ + flush_scheduled_work(); + + netif_stop_queue(dev); + return 0; +} + +/* Return statistics to the caller */ +static struct net_device_stats *ether1394_stats(struct net_device *dev) +{ + return &(((struct eth1394_priv *)netdev_priv(dev))->stats); +} + +/* FIXME: What to do if we timeout? I think a host reset is probably in order, + * so that's what we do. Should we increment the stat counters too? */ +static void ether1394_tx_timeout(struct net_device *dev) +{ + struct hpsb_host *host = + ((struct eth1394_priv *)netdev_priv(dev))->host; + + ETH1394_PRINT(KERN_ERR, dev->name, "Timeout, resetting host\n"); + ether1394_host_reset(host); +} + +static inline int ether1394_max_mtu(struct hpsb_host* host) +{ + return (1 << (host->csr.max_rec + 1)) + - sizeof(union eth1394_hdr) - ETHER1394_GASP_OVERHEAD; +} + +static int ether1394_change_mtu(struct net_device *dev, int new_mtu) +{ + int max_mtu; + + if (new_mtu < 68) + return -EINVAL; + + max_mtu = ether1394_max_mtu( + ((struct eth1394_priv *)netdev_priv(dev))->host); + if (new_mtu > max_mtu) { + ETH1394_PRINT(KERN_INFO, dev->name, + "Local node constrains MTU to %d\n", max_mtu); + return -ERANGE; + } + + dev->mtu = new_mtu; + return 0; +} + +static void purge_partial_datagram(struct list_head *old) +{ + struct partial_datagram *pd; + struct list_head *lh, *n; + struct fragment_info *fi; + + pd = list_entry(old, struct partial_datagram, list); + + list_for_each_safe(lh, n, &pd->frag_info) { + fi = list_entry(lh, struct fragment_info, list); + list_del(lh); + kfree(fi); + } + list_del(old); + kfree_skb(pd->skb); + kfree(pd); +} + +/****************************************** + * 1394 bus activity functions + ******************************************/ + +static struct eth1394_node_ref *eth1394_find_node(struct list_head *inl, + struct unit_directory *ud) +{ + struct eth1394_node_ref *node; + + list_for_each_entry(node, inl, list) + if (node->ud == ud) + return node; + + return NULL; +} + +static struct eth1394_node_ref *eth1394_find_node_guid(struct list_head *inl, + u64 guid) +{ + struct eth1394_node_ref *node; + + list_for_each_entry(node, inl, list) + if (node->ud->ne->guid == guid) + return node; + + return NULL; +} + +static struct eth1394_node_ref *eth1394_find_node_nodeid(struct list_head *inl, + nodeid_t nodeid) +{ + struct eth1394_node_ref *node; + + list_for_each_entry(node, inl, list) + if (node->ud->ne->nodeid == nodeid) + return node; + + return NULL; +} + +static int eth1394_new_node(struct eth1394_host_info *hi, + struct unit_directory *ud) +{ + struct eth1394_priv *priv; + struct eth1394_node_ref *new_node; + struct eth1394_node_info *node_info; + + new_node = kmalloc(sizeof(*new_node), GFP_KERNEL); + if (!new_node) + return -ENOMEM; + + node_info = kmalloc(sizeof(*node_info), GFP_KERNEL); + if (!node_info) { + kfree(new_node); + return -ENOMEM; + } + + spin_lock_init(&node_info->pdg.lock); + INIT_LIST_HEAD(&node_info->pdg.list); + node_info->pdg.sz = 0; + node_info->fifo = CSR1212_INVALID_ADDR_SPACE; + + ud->device.driver_data = node_info; + new_node->ud = ud; + + priv = netdev_priv(hi->dev); + list_add_tail(&new_node->list, &priv->ip_node_list); + return 0; +} + +static int eth1394_probe(struct device *dev) +{ + struct unit_directory *ud; + struct eth1394_host_info *hi; + + ud = container_of(dev, struct unit_directory, device); + hi = hpsb_get_hostinfo(ð1394_highlevel, ud->ne->host); + if (!hi) + return -ENOENT; + + return eth1394_new_node(hi, ud); +} + +static int eth1394_remove(struct device *dev) +{ + struct unit_directory *ud; + struct eth1394_host_info *hi; + struct eth1394_priv *priv; + struct eth1394_node_ref *old_node; + struct eth1394_node_info *node_info; + struct list_head *lh, *n; + unsigned long flags; + + ud = container_of(dev, struct unit_directory, device); + hi = hpsb_get_hostinfo(ð1394_highlevel, ud->ne->host); + if (!hi) + return -ENOENT; + + priv = netdev_priv(hi->dev); + + old_node = eth1394_find_node(&priv->ip_node_list, ud); + if (!old_node) + return 0; + + list_del(&old_node->list); + kfree(old_node); + + node_info = (struct eth1394_node_info*)ud->device.driver_data; + + spin_lock_irqsave(&node_info->pdg.lock, flags); + /* The partial datagram list should be empty, but we'll just + * make sure anyway... */ + list_for_each_safe(lh, n, &node_info->pdg.list) + purge_partial_datagram(lh); + spin_unlock_irqrestore(&node_info->pdg.lock, flags); + + kfree(node_info); + ud->device.driver_data = NULL; + return 0; +} + +static int eth1394_update(struct unit_directory *ud) +{ + struct eth1394_host_info *hi; + struct eth1394_priv *priv; + struct eth1394_node_ref *node; + + hi = hpsb_get_hostinfo(ð1394_highlevel, ud->ne->host); + if (!hi) + return -ENOENT; + + priv = netdev_priv(hi->dev); + node = eth1394_find_node(&priv->ip_node_list, ud); + if (node) + return 0; + + return eth1394_new_node(hi, ud); +} + +static struct ieee1394_device_id eth1394_id_table[] = { + { + .match_flags = (IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION), + .specifier_id = ETHER1394_GASP_SPECIFIER_ID, + .version = ETHER1394_GASP_VERSION, + }, + {} +}; + +MODULE_DEVICE_TABLE(ieee1394, eth1394_id_table); + +static struct hpsb_protocol_driver eth1394_proto_driver = { + .name = driver_name, + .id_table = eth1394_id_table, + .update = eth1394_update, + .driver = { + .probe = eth1394_probe, + .remove = eth1394_remove, + }, +}; + +static void ether1394_reset_priv(struct net_device *dev, int set_mtu) +{ + unsigned long flags; + int i; + struct eth1394_priv *priv = netdev_priv(dev); + struct hpsb_host *host = priv->host; + u64 guid = get_unaligned((u64 *)&(host->csr.rom->bus_info_data[3])); + int max_speed = IEEE1394_SPEED_MAX; + + spin_lock_irqsave(&priv->lock, flags); + + memset(priv->ud_list, 0, sizeof(priv->ud_list)); + priv->bc_maxpayload = 512; + + /* Determine speed limit */ + /* FIXME: This is broken for nodes with link speed < PHY speed, + * and it is suboptimal for S200B...S800B hardware. + * The result of nodemgr's speed probe should be used somehow. */ + for (i = 0; i < host->node_count; i++) { + /* take care of S100B...S400B PHY ports */ + if (host->speed[i] == SELFID_SPEED_UNKNOWN) { + max_speed = IEEE1394_SPEED_100; + break; + } + if (max_speed > host->speed[i]) + max_speed = host->speed[i]; + } + priv->bc_sspd = max_speed; + + if (set_mtu) { + /* Use the RFC 2734 default 1500 octets or the maximum payload + * as initial MTU */ + dev->mtu = min(1500, ether1394_max_mtu(host)); + + /* Set our hardware address while we're at it */ + memcpy(dev->dev_addr, &guid, sizeof(u64)); + memset(dev->broadcast, 0xff, sizeof(u64)); + } + + spin_unlock_irqrestore(&priv->lock, flags); +} + +static const struct header_ops ether1394_header_ops = { + .create = ether1394_header, + .rebuild = ether1394_rebuild_header, + .cache = ether1394_header_cache, + .cache_update = ether1394_header_cache_update, + .parse = ether1394_header_parse, +}; + +static void ether1394_init_dev(struct net_device *dev) +{ + dev->open = ether1394_open; + dev->stop = ether1394_stop; + dev->hard_start_xmit = ether1394_tx; + dev->get_stats = ether1394_stats; + dev->tx_timeout = ether1394_tx_timeout; + dev->change_mtu = ether1394_change_mtu; + + dev->header_ops = ðer1394_header_ops; + + SET_ETHTOOL_OPS(dev, ðtool_ops); + + dev->watchdog_timeo = ETHER1394_TIMEOUT; + dev->flags = IFF_BROADCAST | IFF_MULTICAST; + dev->features = NETIF_F_HIGHDMA; + dev->addr_len = ETH1394_ALEN; + dev->hard_header_len = ETH1394_HLEN; + dev->type = ARPHRD_IEEE1394; + + /* FIXME: This value was copied from ether_setup(). Is it too much? */ + dev->tx_queue_len = 1000; +} + +/* + * Wake the queue up after commonly encountered transmit failure conditions are + * hopefully over. Currently only tlabel exhaustion is accounted for. + */ +static void ether1394_wake_queue(struct work_struct *work) +{ + struct eth1394_priv *priv; + struct hpsb_packet *packet; + + priv = container_of(work, struct eth1394_priv, wake); + packet = hpsb_alloc_packet(0); + + /* This is really bad, but unjam the queue anyway. */ + if (!packet) + goto out; + + packet->host = priv->host; + packet->node_id = priv->wake_node; + /* + * A transaction label is all we really want. If we get one, it almost + * always means we can get a lot more because the ieee1394 core recycled + * a whole batch of tlabels, at last. + */ + if (hpsb_get_tlabel(packet) == 0) + hpsb_free_tlabel(packet); + + hpsb_free_packet(packet); +out: + netif_wake_queue(priv->wake_dev); +} + +/* + * This function is called every time a card is found. It is generally called + * when the module is installed. This is where we add all of our ethernet + * devices. One for each host. + */ +static void ether1394_add_host(struct hpsb_host *host) +{ + struct eth1394_host_info *hi = NULL; + struct net_device *dev = NULL; + struct eth1394_priv *priv; + u64 fifo_addr; + + if (hpsb_config_rom_ip1394_add(host) != 0) { + ETH1394_PRINT_G(KERN_ERR, "Can't add IP-over-1394 ROM entry\n"); + return; + } + + fifo_addr = hpsb_allocate_and_register_addrspace( + ð1394_highlevel, host, &addr_ops, + ETHER1394_REGION_ADDR_LEN, ETHER1394_REGION_ADDR_LEN, + CSR1212_INVALID_ADDR_SPACE, CSR1212_INVALID_ADDR_SPACE); + if (fifo_addr == CSR1212_INVALID_ADDR_SPACE) { + ETH1394_PRINT_G(KERN_ERR, "Cannot register CSR space\n"); + hpsb_config_rom_ip1394_remove(host); + return; + } + + dev = alloc_netdev(sizeof(*priv), "eth%d", ether1394_init_dev); + if (dev == NULL) { + ETH1394_PRINT_G(KERN_ERR, "Out of memory\n"); + goto out; + } + + SET_NETDEV_DEV(dev, &host->device); + + priv = netdev_priv(dev); + INIT_LIST_HEAD(&priv->ip_node_list); + spin_lock_init(&priv->lock); + priv->host = host; + priv->local_fifo = fifo_addr; + INIT_WORK(&priv->wake, ether1394_wake_queue); + priv->wake_dev = dev; + + hi = hpsb_create_hostinfo(ð1394_highlevel, host, sizeof(*hi)); + if (hi == NULL) { + ETH1394_PRINT_G(KERN_ERR, "Out of memory\n"); + goto out; + } + + ether1394_reset_priv(dev, 1); + + if (register_netdev(dev)) { + ETH1394_PRINT_G(KERN_ERR, "Cannot register the driver\n"); + goto out; + } + + ETH1394_PRINT(KERN_INFO, dev->name, "IPv4 over IEEE 1394 (fw-host%d)\n", + host->id); + + hi->host = host; + hi->dev = dev; + + /* Ignore validity in hopes that it will be set in the future. It'll + * be checked when the eth device is opened. */ + priv->broadcast_channel = host->csr.broadcast_channel & 0x3f; + + ether1394_recv_init(priv); + return; +out: + if (dev) + free_netdev(dev); + if (hi) + hpsb_destroy_hostinfo(ð1394_highlevel, host); + hpsb_unregister_addrspace(ð1394_highlevel, host, fifo_addr); + hpsb_config_rom_ip1394_remove(host); +} + +/* Remove a card from our list */ +static void ether1394_remove_host(struct hpsb_host *host) +{ + struct eth1394_host_info *hi; + struct eth1394_priv *priv; + + hi = hpsb_get_hostinfo(ð1394_highlevel, host); + if (!hi) + return; + priv = netdev_priv(hi->dev); + hpsb_unregister_addrspace(ð1394_highlevel, host, priv->local_fifo); + hpsb_config_rom_ip1394_remove(host); + if (priv->iso) + hpsb_iso_shutdown(priv->iso); + unregister_netdev(hi->dev); + free_netdev(hi->dev); +} + +/* A bus reset happened */ +static void ether1394_host_reset(struct hpsb_host *host) +{ + struct eth1394_host_info *hi; + struct eth1394_priv *priv; + struct net_device *dev; + struct list_head *lh, *n; + struct eth1394_node_ref *node; + struct eth1394_node_info *node_info; + unsigned long flags; + + hi = hpsb_get_hostinfo(ð1394_highlevel, host); + + /* This can happen for hosts that we don't use */ + if (!hi) + return; + + dev = hi->dev; + priv = netdev_priv(dev); + + /* Reset our private host data, but not our MTU */ + netif_stop_queue(dev); + ether1394_reset_priv(dev, 0); + + list_for_each_entry(node, &priv->ip_node_list, list) { + node_info = node->ud->device.driver_data; + + spin_lock_irqsave(&node_info->pdg.lock, flags); + + list_for_each_safe(lh, n, &node_info->pdg.list) + purge_partial_datagram(lh); + + INIT_LIST_HEAD(&(node_info->pdg.list)); + node_info->pdg.sz = 0; + + spin_unlock_irqrestore(&node_info->pdg.lock, flags); + } + + netif_wake_queue(dev); +} + +/****************************************** + * HW Header net device functions + ******************************************/ +/* These functions have been adapted from net/ethernet/eth.c */ + +/* Create a fake MAC header for an arbitrary protocol layer. + * saddr=NULL means use device source address + * daddr=NULL means leave destination address (eg unresolved arp). */ +static int ether1394_header(struct sk_buff *skb, struct net_device *dev, + unsigned short type, const void *daddr, + const void *saddr, unsigned len) +{ + struct eth1394hdr *eth = + (struct eth1394hdr *)skb_push(skb, ETH1394_HLEN); + + eth->h_proto = htons(type); + + if (dev->flags & (IFF_LOOPBACK | IFF_NOARP)) { + memset(eth->h_dest, 0, dev->addr_len); + return dev->hard_header_len; + } + + if (daddr) { + memcpy(eth->h_dest, daddr, dev->addr_len); + return dev->hard_header_len; + } + + return -dev->hard_header_len; +} + +/* Rebuild the faked MAC header. This is called after an ARP + * (or in future other address resolution) has completed on this + * sk_buff. We now let ARP fill in the other fields. + * + * This routine CANNOT use cached dst->neigh! + * Really, it is used only when dst->neigh is wrong. + */ +static int ether1394_rebuild_header(struct sk_buff *skb) +{ + struct eth1394hdr *eth = (struct eth1394hdr *)skb->data; + + if (eth->h_proto == htons(ETH_P_IP)) + return arp_find((unsigned char *)ð->h_dest, skb); + + ETH1394_PRINT(KERN_DEBUG, skb->dev->name, + "unable to resolve type %04x addresses\n", + ntohs(eth->h_proto)); + return 0; +} + +static int ether1394_header_parse(const struct sk_buff *skb, + unsigned char *haddr) +{ + memcpy(haddr, skb->dev->dev_addr, ETH1394_ALEN); + return ETH1394_ALEN; +} + +static int ether1394_header_cache(const struct neighbour *neigh, + struct hh_cache *hh) +{ + unsigned short type = hh->hh_type; + struct net_device *dev = neigh->dev; + struct eth1394hdr *eth = + (struct eth1394hdr *)((u8 *)hh->hh_data + 16 - ETH1394_HLEN); + + if (type == htons(ETH_P_802_3)) + return -1; + + eth->h_proto = type; + memcpy(eth->h_dest, neigh->ha, dev->addr_len); + + hh->hh_len = ETH1394_HLEN; + return 0; +} + +/* Called by Address Resolution module to notify changes in address. */ +static void ether1394_header_cache_update(struct hh_cache *hh, + const struct net_device *dev, + const unsigned char * haddr) +{ + memcpy((u8 *)hh->hh_data + 16 - ETH1394_HLEN, haddr, dev->addr_len); +} + +/****************************************** + * Datagram reception code + ******************************************/ + +/* Copied from net/ethernet/eth.c */ +static u16 ether1394_type_trans(struct sk_buff *skb, struct net_device *dev) +{ + struct eth1394hdr *eth; + unsigned char *rawp; + + skb_reset_mac_header(skb); + skb_pull(skb, ETH1394_HLEN); + eth = eth1394_hdr(skb); + + if (*eth->h_dest & 1) { + if (memcmp(eth->h_dest, dev->broadcast, dev->addr_len) == 0) + skb->pkt_type = PACKET_BROADCAST; +#if 0 + else + skb->pkt_type = PACKET_MULTICAST; +#endif + } else { + if (memcmp(eth->h_dest, dev->dev_addr, dev->addr_len)) + skb->pkt_type = PACKET_OTHERHOST; + } + + if (ntohs(eth->h_proto) >= 1536) + return eth->h_proto; + + rawp = skb->data; + + if (*(unsigned short *)rawp == 0xFFFF) + return htons(ETH_P_802_3); + + return htons(ETH_P_802_2); +} + +/* Parse an encapsulated IP1394 header into an ethernet frame packet. + * We also perform ARP translation here, if need be. */ +static u16 ether1394_parse_encap(struct sk_buff *skb, struct net_device *dev, + nodeid_t srcid, nodeid_t destid, + u16 ether_type) +{ + struct eth1394_priv *priv = netdev_priv(dev); + u64 dest_hw; + unsigned short ret = 0; + + /* Setup our hw addresses. We use these to build the ethernet header. */ + if (destid == (LOCAL_BUS | ALL_NODES)) + dest_hw = ~0ULL; /* broadcast */ + else + dest_hw = cpu_to_be64((u64)priv->host->csr.guid_hi << 32 | + priv->host->csr.guid_lo); + + /* If this is an ARP packet, convert it. First, we want to make + * use of some of the fields, since they tell us a little bit + * about the sending machine. */ + if (ether_type == htons(ETH_P_ARP)) { + struct eth1394_arp *arp1394 = (struct eth1394_arp *)skb->data; + struct arphdr *arp = (struct arphdr *)skb->data; + unsigned char *arp_ptr = (unsigned char *)(arp + 1); + u64 fifo_addr = (u64)ntohs(arp1394->fifo_hi) << 32 | + ntohl(arp1394->fifo_lo); + u8 max_rec = min(priv->host->csr.max_rec, + (u8)(arp1394->max_rec)); + int sspd = arp1394->sspd; + u16 maxpayload; + struct eth1394_node_ref *node; + struct eth1394_node_info *node_info; + __be64 guid; + + /* Sanity check. MacOSX seems to be sending us 131 in this + * field (atleast on my Panther G5). Not sure why. */ + if (sspd > 5 || sspd < 0) + sspd = 0; + + maxpayload = min(eth1394_speedto_maxpayload[sspd], + (u16)(1 << (max_rec + 1))); + + guid = get_unaligned(&arp1394->s_uniq_id); + node = eth1394_find_node_guid(&priv->ip_node_list, + be64_to_cpu(guid)); + if (!node) + return 0; + + node_info = + (struct eth1394_node_info *)node->ud->device.driver_data; + + /* Update our speed/payload/fifo_offset table */ + node_info->maxpayload = maxpayload; + node_info->sspd = sspd; + node_info->fifo = fifo_addr; + + /* Now that we're done with the 1394 specific stuff, we'll + * need to alter some of the data. Believe it or not, all + * that needs to be done is sender_IP_address needs to be + * moved, the destination hardware address get stuffed + * in and the hardware address length set to 8. + * + * IMPORTANT: The code below overwrites 1394 specific data + * needed above so keep the munging of the data for the + * higher level IP stack last. */ + + arp->ar_hln = 8; + arp_ptr += arp->ar_hln; /* skip over sender unique id */ + *(u32 *)arp_ptr = arp1394->sip; /* move sender IP addr */ + arp_ptr += arp->ar_pln; /* skip over sender IP addr */ + + if (arp->ar_op == htons(ARPOP_REQUEST)) + memset(arp_ptr, 0, sizeof(u64)); + else + memcpy(arp_ptr, dev->dev_addr, sizeof(u64)); + } + + /* Now add the ethernet header. */ + if (dev_hard_header(skb, dev, ntohs(ether_type), &dest_hw, NULL, + skb->len) >= 0) + ret = ether1394_type_trans(skb, dev); + + return ret; +} + +static int fragment_overlap(struct list_head *frag_list, int offset, int len) +{ + struct fragment_info *fi; + int end = offset + len; + + list_for_each_entry(fi, frag_list, list) + if (offset < fi->offset + fi->len && end > fi->offset) + return 1; + + return 0; +} + +static struct list_head *find_partial_datagram(struct list_head *pdgl, int dgl) +{ + struct partial_datagram *pd; + + list_for_each_entry(pd, pdgl, list) + if (pd->dgl == dgl) + return &pd->list; + + return NULL; +} + +/* Assumes that new fragment does not overlap any existing fragments */ +static int new_fragment(struct list_head *frag_info, int offset, int len) +{ + struct list_head *lh; + struct fragment_info *fi, *fi2, *new; + + list_for_each(lh, frag_info) { + fi = list_entry(lh, struct fragment_info, list); + if (fi->offset + fi->len == offset) { + /* The new fragment can be tacked on to the end */ + fi->len += len; + /* Did the new fragment plug a hole? */ + fi2 = list_entry(lh->next, struct fragment_info, list); + if (fi->offset + fi->len == fi2->offset) { + /* glue fragments together */ + fi->len += fi2->len; + list_del(lh->next); + kfree(fi2); + } + return 0; + } else if (offset + len == fi->offset) { + /* The new fragment can be tacked on to the beginning */ + fi->offset = offset; + fi->len += len; + /* Did the new fragment plug a hole? */ + fi2 = list_entry(lh->prev, struct fragment_info, list); + if (fi2->offset + fi2->len == fi->offset) { + /* glue fragments together */ + fi2->len += fi->len; + list_del(lh); + kfree(fi); + } + return 0; + } else if (offset > fi->offset + fi->len) { + break; + } else if (offset + len < fi->offset) { + lh = lh->prev; + break; + } + } + + new = kmalloc(sizeof(*new), GFP_ATOMIC); + if (!new) + return -ENOMEM; + + new->offset = offset; + new->len = len; + + list_add(&new->list, lh); + return 0; +} + +static int new_partial_datagram(struct net_device *dev, struct list_head *pdgl, + int dgl, int dg_size, char *frag_buf, + int frag_off, int frag_len) +{ + struct partial_datagram *new; + + new = kmalloc(sizeof(*new), GFP_ATOMIC); + if (!new) + return -ENOMEM; + + INIT_LIST_HEAD(&new->frag_info); + + if (new_fragment(&new->frag_info, frag_off, frag_len) < 0) { + kfree(new); + return -ENOMEM; + } + + new->dgl = dgl; + new->dg_size = dg_size; + + new->skb = dev_alloc_skb(dg_size + dev->hard_header_len + 15); + if (!new->skb) { + struct fragment_info *fi = list_entry(new->frag_info.next, + struct fragment_info, + list); + kfree(fi); + kfree(new); + return -ENOMEM; + } + + skb_reserve(new->skb, (dev->hard_header_len + 15) & ~15); + new->pbuf = skb_put(new->skb, dg_size); + memcpy(new->pbuf + frag_off, frag_buf, frag_len); + + list_add(&new->list, pdgl); + return 0; +} + +static int update_partial_datagram(struct list_head *pdgl, struct list_head *lh, + char *frag_buf, int frag_off, int frag_len) +{ + struct partial_datagram *pd = + list_entry(lh, struct partial_datagram, list); + + if (new_fragment(&pd->frag_info, frag_off, frag_len) < 0) + return -ENOMEM; + + memcpy(pd->pbuf + frag_off, frag_buf, frag_len); + + /* Move list entry to beginnig of list so that oldest partial + * datagrams percolate to the end of the list */ + list_move(lh, pdgl); + return 0; +} + +static int is_datagram_complete(struct list_head *lh, int dg_size) +{ + struct partial_datagram *pd; + struct fragment_info *fi; + + pd = list_entry(lh, struct partial_datagram, list); + fi = list_entry(pd->frag_info.next, struct fragment_info, list); + + return (fi->len == dg_size); +} + +/* Packet reception. We convert the IP1394 encapsulation header to an + * ethernet header, and fill it with some of our other fields. This is + * an incoming packet from the 1394 bus. */ +static int ether1394_data_handler(struct net_device *dev, int srcid, int destid, + char *buf, int len) +{ + struct sk_buff *skb; + unsigned long flags; + struct eth1394_priv *priv = netdev_priv(dev); + union eth1394_hdr *hdr = (union eth1394_hdr *)buf; + u16 ether_type = 0; /* initialized to clear warning */ + int hdr_len; + struct unit_directory *ud = priv->ud_list[NODEID_TO_NODE(srcid)]; + struct eth1394_node_info *node_info; + + if (!ud) { + struct eth1394_node_ref *node; + node = eth1394_find_node_nodeid(&priv->ip_node_list, srcid); + if (unlikely(!node)) { + HPSB_PRINT(KERN_ERR, "ether1394 rx: sender nodeid " + "lookup failure: " NODE_BUS_FMT, + NODE_BUS_ARGS(priv->host, srcid)); + priv->stats.rx_dropped++; + return -1; + } + ud = node->ud; + + priv->ud_list[NODEID_TO_NODE(srcid)] = ud; + } + + node_info = (struct eth1394_node_info *)ud->device.driver_data; + + /* First, did we receive a fragmented or unfragmented datagram? */ + hdr->words.word1 = ntohs(hdr->words.word1); + + hdr_len = hdr_type_len[hdr->common.lf]; + + if (hdr->common.lf == ETH1394_HDR_LF_UF) { + /* An unfragmented datagram has been received by the ieee1394 + * bus. Build an skbuff around it so we can pass it to the + * high level network layer. */ + + skb = dev_alloc_skb(len + dev->hard_header_len + 15); + if (unlikely(!skb)) { + ETH1394_PRINT_G(KERN_ERR, "Out of memory\n"); + priv->stats.rx_dropped++; + return -1; + } + skb_reserve(skb, (dev->hard_header_len + 15) & ~15); + memcpy(skb_put(skb, len - hdr_len), buf + hdr_len, + len - hdr_len); + ether_type = hdr->uf.ether_type; + } else { + /* A datagram fragment has been received, now the fun begins. */ + + struct list_head *pdgl, *lh; + struct partial_datagram *pd; + int fg_off; + int fg_len = len - hdr_len; + int dg_size; + int dgl; + int retval; + struct pdg_list *pdg = &(node_info->pdg); + + hdr->words.word3 = ntohs(hdr->words.word3); + /* The 4th header word is reserved so no need to do ntohs() */ + + if (hdr->common.lf == ETH1394_HDR_LF_FF) { + ether_type = hdr->ff.ether_type; + dgl = hdr->ff.dgl; + dg_size = hdr->ff.dg_size + 1; + fg_off = 0; + } else { + hdr->words.word2 = ntohs(hdr->words.word2); + dgl = hdr->sf.dgl; + dg_size = hdr->sf.dg_size + 1; + fg_off = hdr->sf.fg_off; + } + spin_lock_irqsave(&pdg->lock, flags); + + pdgl = &(pdg->list); + lh = find_partial_datagram(pdgl, dgl); + + if (lh == NULL) { + while (pdg->sz >= max_partial_datagrams) { + /* remove the oldest */ + purge_partial_datagram(pdgl->prev); + pdg->sz--; + } + + retval = new_partial_datagram(dev, pdgl, dgl, dg_size, + buf + hdr_len, fg_off, + fg_len); + if (retval < 0) { + spin_unlock_irqrestore(&pdg->lock, flags); + goto bad_proto; + } + pdg->sz++; + lh = find_partial_datagram(pdgl, dgl); + } else { + pd = list_entry(lh, struct partial_datagram, list); + + if (fragment_overlap(&pd->frag_info, fg_off, fg_len)) { + /* Overlapping fragments, obliterate old + * datagram and start new one. */ + purge_partial_datagram(lh); + retval = new_partial_datagram(dev, pdgl, dgl, + dg_size, + buf + hdr_len, + fg_off, fg_len); + if (retval < 0) { + pdg->sz--; + spin_unlock_irqrestore(&pdg->lock, flags); + goto bad_proto; + } + } else { + retval = update_partial_datagram(pdgl, lh, + buf + hdr_len, + fg_off, fg_len); + if (retval < 0) { + /* Couldn't save off fragment anyway + * so might as well obliterate the + * datagram now. */ + purge_partial_datagram(lh); + pdg->sz--; + spin_unlock_irqrestore(&pdg->lock, flags); + goto bad_proto; + } + } /* fragment overlap */ + } /* new datagram or add to existing one */ + + pd = list_entry(lh, struct partial_datagram, list); + + if (hdr->common.lf == ETH1394_HDR_LF_FF) + pd->ether_type = ether_type; + + if (is_datagram_complete(lh, dg_size)) { + ether_type = pd->ether_type; + pdg->sz--; + skb = skb_get(pd->skb); + purge_partial_datagram(lh); + spin_unlock_irqrestore(&pdg->lock, flags); + } else { + /* Datagram is not complete, we're done for the + * moment. */ + spin_unlock_irqrestore(&pdg->lock, flags); + return 0; + } + } /* unframgented datagram or fragmented one */ + + /* Write metadata, and then pass to the receive level */ + skb->dev = dev; + skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */ + + /* Parse the encapsulation header. This actually does the job of + * converting to an ethernet frame header, aswell as arp + * conversion if needed. ARP conversion is easier in this + * direction, since we are using ethernet as our backend. */ + skb->protocol = ether1394_parse_encap(skb, dev, srcid, destid, + ether_type); + + spin_lock_irqsave(&priv->lock, flags); + + if (!skb->protocol) { + priv->stats.rx_errors++; + priv->stats.rx_dropped++; + dev_kfree_skb_any(skb); + } else if (netif_rx(skb) == NET_RX_DROP) { + priv->stats.rx_errors++; + priv->stats.rx_dropped++; + } else { + priv->stats.rx_packets++; + priv->stats.rx_bytes += skb->len; + } + + spin_unlock_irqrestore(&priv->lock, flags); + +bad_proto: + if (netif_queue_stopped(dev)) + netif_wake_queue(dev); + + dev->last_rx = jiffies; + + return 0; +} + +static int ether1394_write(struct hpsb_host *host, int srcid, int destid, + quadlet_t *data, u64 addr, size_t len, u16 flags) +{ + struct eth1394_host_info *hi; + + hi = hpsb_get_hostinfo(ð1394_highlevel, host); + if (unlikely(!hi)) { + ETH1394_PRINT_G(KERN_ERR, "No net device at fw-host%d\n", + host->id); + return RCODE_ADDRESS_ERROR; + } + + if (ether1394_data_handler(hi->dev, srcid, destid, (char*)data, len)) + return RCODE_ADDRESS_ERROR; + else + return RCODE_COMPLETE; +} + +static void ether1394_iso(struct hpsb_iso *iso) +{ + quadlet_t *data; + char *buf; + struct eth1394_host_info *hi; + struct net_device *dev; + struct eth1394_priv *priv; + unsigned int len; + u32 specifier_id; + u16 source_id; + int i; + int nready; + + hi = hpsb_get_hostinfo(ð1394_highlevel, iso->host); + if (unlikely(!hi)) { + ETH1394_PRINT_G(KERN_ERR, "No net device at fw-host%d\n", + iso->host->id); + return; + } + + dev = hi->dev; + + nready = hpsb_iso_n_ready(iso); + for (i = 0; i < nready; i++) { + struct hpsb_iso_packet_info *info = + &iso->infos[(iso->first_packet + i) % iso->buf_packets]; + data = (quadlet_t *)(iso->data_buf.kvirt + info->offset); + + /* skip over GASP header */ + buf = (char *)data + 8; + len = info->len - 8; + + specifier_id = (be32_to_cpu(data[0]) & 0xffff) << 8 | + (be32_to_cpu(data[1]) & 0xff000000) >> 24; + source_id = be32_to_cpu(data[0]) >> 16; + + priv = netdev_priv(dev); + + if (info->channel != (iso->host->csr.broadcast_channel & 0x3f) + || specifier_id != ETHER1394_GASP_SPECIFIER_ID) { + /* This packet is not for us */ + continue; + } + ether1394_data_handler(dev, source_id, LOCAL_BUS | ALL_NODES, + buf, len); + } + + hpsb_iso_recv_release_packets(iso, i); + + dev->last_rx = jiffies; +} + +/****************************************** + * Datagram transmission code + ******************************************/ + +/* Convert a standard ARP packet to 1394 ARP. The first 8 bytes (the entire + * arphdr) is the same format as the ip1394 header, so they overlap. The rest + * needs to be munged a bit. The remainder of the arphdr is formatted based + * on hwaddr len and ipaddr len. We know what they'll be, so it's easy to + * judge. + * + * Now that the EUI is used for the hardware address all we need to do to make + * this work for 1394 is to insert 2 quadlets that contain max_rec size, + * speed, and unicast FIFO address information between the sender_unique_id + * and the IP addresses. + */ +static void ether1394_arp_to_1394arp(struct sk_buff *skb, + struct net_device *dev) +{ + struct eth1394_priv *priv = netdev_priv(dev); + struct arphdr *arp = (struct arphdr *)skb->data; + unsigned char *arp_ptr = (unsigned char *)(arp + 1); + struct eth1394_arp *arp1394 = (struct eth1394_arp *)skb->data; + + arp1394->hw_addr_len = 16; + arp1394->sip = *(u32*)(arp_ptr + ETH1394_ALEN); + arp1394->max_rec = priv->host->csr.max_rec; + arp1394->sspd = priv->host->csr.lnk_spd; + arp1394->fifo_hi = htons(priv->local_fifo >> 32); + arp1394->fifo_lo = htonl(priv->local_fifo & ~0x0); +} + +/* We need to encapsulate the standard header with our own. We use the + * ethernet header's proto for our own. */ +static unsigned int ether1394_encapsulate_prep(unsigned int max_payload, + __be16 proto, + union eth1394_hdr *hdr, + u16 dg_size, u16 dgl) +{ + unsigned int adj_max_payload = + max_payload - hdr_type_len[ETH1394_HDR_LF_UF]; + + /* Does it all fit in one packet? */ + if (dg_size <= adj_max_payload) { + hdr->uf.lf = ETH1394_HDR_LF_UF; + hdr->uf.ether_type = proto; + } else { + hdr->ff.lf = ETH1394_HDR_LF_FF; + hdr->ff.ether_type = proto; + hdr->ff.dg_size = dg_size - 1; + hdr->ff.dgl = dgl; + adj_max_payload = max_payload - hdr_type_len[ETH1394_HDR_LF_FF]; + } + return DIV_ROUND_UP(dg_size, adj_max_payload); +} + +static unsigned int ether1394_encapsulate(struct sk_buff *skb, + unsigned int max_payload, + union eth1394_hdr *hdr) +{ + union eth1394_hdr *bufhdr; + int ftype = hdr->common.lf; + int hdrsz = hdr_type_len[ftype]; + unsigned int adj_max_payload = max_payload - hdrsz; + + switch (ftype) { + case ETH1394_HDR_LF_UF: + bufhdr = (union eth1394_hdr *)skb_push(skb, hdrsz); + bufhdr->words.word1 = htons(hdr->words.word1); + bufhdr->words.word2 = hdr->words.word2; + break; + + case ETH1394_HDR_LF_FF: + bufhdr = (union eth1394_hdr *)skb_push(skb, hdrsz); + bufhdr->words.word1 = htons(hdr->words.word1); + bufhdr->words.word2 = hdr->words.word2; + bufhdr->words.word3 = htons(hdr->words.word3); + bufhdr->words.word4 = 0; + + /* Set frag type here for future interior fragments */ + hdr->common.lf = ETH1394_HDR_LF_IF; + hdr->sf.fg_off = 0; + break; + + default: + hdr->sf.fg_off += adj_max_payload; + bufhdr = (union eth1394_hdr *)skb_pull(skb, adj_max_payload); + if (max_payload >= skb->len) + hdr->common.lf = ETH1394_HDR_LF_LF; + bufhdr->words.word1 = htons(hdr->words.word1); + bufhdr->words.word2 = htons(hdr->words.word2); + bufhdr->words.word3 = htons(hdr->words.word3); + bufhdr->words.word4 = 0; + } + return min(max_payload, skb->len); +} + +static struct hpsb_packet *ether1394_alloc_common_packet(struct hpsb_host *host) +{ + struct hpsb_packet *p; + + p = hpsb_alloc_packet(0); + if (p) { + p->host = host; + p->generation = get_hpsb_generation(host); + p->type = hpsb_async; + } + return p; +} + +static int ether1394_prep_write_packet(struct hpsb_packet *p, + struct hpsb_host *host, nodeid_t node, + u64 addr, void *data, int tx_len) +{ + p->node_id = node; + + if (hpsb_get_tlabel(p)) + return -EAGAIN; + + p->tcode = TCODE_WRITEB; + p->header_size = 16; + p->expect_response = 1; + p->header[0] = + p->node_id << 16 | p->tlabel << 10 | 1 << 8 | TCODE_WRITEB << 4; + p->header[1] = host->node_id << 16 | addr >> 32; + p->header[2] = addr & 0xffffffff; + p->header[3] = tx_len << 16; + p->data_size = (tx_len + 3) & ~3; + p->data = data; + + return 0; +} + +static void ether1394_prep_gasp_packet(struct hpsb_packet *p, + struct eth1394_priv *priv, + struct sk_buff *skb, int length) +{ + p->header_size = 4; + p->tcode = TCODE_STREAM_DATA; + + p->header[0] = length << 16 | 3 << 14 | priv->broadcast_channel << 8 | + TCODE_STREAM_DATA << 4; + p->data_size = length; + p->data = (quadlet_t *)skb->data - 2; + p->data[0] = cpu_to_be32(priv->host->node_id << 16 | + ETHER1394_GASP_SPECIFIER_ID_HI); + p->data[1] = cpu_to_be32(ETHER1394_GASP_SPECIFIER_ID_LO << 24 | + ETHER1394_GASP_VERSION); + + p->speed_code = priv->bc_sspd; + + /* prevent hpsb_send_packet() from overriding our speed code */ + p->node_id = LOCAL_BUS | ALL_NODES; +} + +static void ether1394_free_packet(struct hpsb_packet *packet) +{ + if (packet->tcode != TCODE_STREAM_DATA) + hpsb_free_tlabel(packet); + hpsb_free_packet(packet); +} + +static void ether1394_complete_cb(void *__ptask); + +static int ether1394_send_packet(struct packet_task *ptask, unsigned int tx_len) +{ + struct eth1394_priv *priv = ptask->priv; + struct hpsb_packet *packet = NULL; + + packet = ether1394_alloc_common_packet(priv->host); + if (!packet) + return -ENOMEM; + + if (ptask->tx_type == ETH1394_GASP) { + int length = tx_len + 2 * sizeof(quadlet_t); + + ether1394_prep_gasp_packet(packet, priv, ptask->skb, length); + } else if (ether1394_prep_write_packet(packet, priv->host, + ptask->dest_node, + ptask->addr, ptask->skb->data, + tx_len)) { + hpsb_free_packet(packet); + return -EAGAIN; + } + + ptask->packet = packet; + hpsb_set_packet_complete_task(ptask->packet, ether1394_complete_cb, + ptask); + + if (hpsb_send_packet(packet) < 0) { + ether1394_free_packet(packet); + return -EIO; + } + + return 0; +} + +/* Task function to be run when a datagram transmission is completed */ +static void ether1394_dg_complete(struct packet_task *ptask, int fail) +{ + struct sk_buff *skb = ptask->skb; + struct eth1394_priv *priv = netdev_priv(skb->dev); + unsigned long flags; + + /* Statistics */ + spin_lock_irqsave(&priv->lock, flags); + if (fail) { + priv->stats.tx_dropped++; + priv->stats.tx_errors++; + } else { + priv->stats.tx_bytes += skb->len; + priv->stats.tx_packets++; + } + spin_unlock_irqrestore(&priv->lock, flags); + + dev_kfree_skb_any(skb); + kmem_cache_free(packet_task_cache, ptask); +} + +/* Callback for when a packet has been sent and the status of that packet is + * known */ +static void ether1394_complete_cb(void *__ptask) +{ + struct packet_task *ptask = (struct packet_task *)__ptask; + struct hpsb_packet *packet = ptask->packet; + int fail = 0; + + if (packet->tcode != TCODE_STREAM_DATA) + fail = hpsb_packet_success(packet); + + ether1394_free_packet(packet); + + ptask->outstanding_pkts--; + if (ptask->outstanding_pkts > 0 && !fail) { + int tx_len, err; + + /* Add the encapsulation header to the fragment */ + tx_len = ether1394_encapsulate(ptask->skb, ptask->max_payload, + &ptask->hdr); + err = ether1394_send_packet(ptask, tx_len); + if (err) { + if (err == -EAGAIN) + ETH1394_PRINT_G(KERN_ERR, "Out of tlabels\n"); + + ether1394_dg_complete(ptask, 1); + } + } else { + ether1394_dg_complete(ptask, fail); + } +} + +/* Transmit a packet (called by kernel) */ +static int ether1394_tx(struct sk_buff *skb, struct net_device *dev) +{ + struct eth1394hdr hdr_buf; + struct eth1394_priv *priv = netdev_priv(dev); + __be16 proto; + unsigned long flags; + nodeid_t dest_node; + eth1394_tx_type tx_type; + unsigned int tx_len; + unsigned int max_payload; + u16 dg_size; + u16 dgl; + struct packet_task *ptask; + struct eth1394_node_ref *node; + struct eth1394_node_info *node_info = NULL; + + ptask = kmem_cache_alloc(packet_task_cache, GFP_ATOMIC); + if (ptask == NULL) + goto fail; + + /* XXX Ignore this for now. Noticed that when MacOSX is the IRM, + * it does not set our validity bit. We need to compensate for + * that somewhere else, but not in eth1394. */ +#if 0 + if ((priv->host->csr.broadcast_channel & 0xc0000000) != 0xc0000000) + goto fail; +#endif + + skb = skb_share_check(skb, GFP_ATOMIC); + if (!skb) + goto fail; + + /* Get rid of the fake eth1394 header, but first make a copy. + * We might need to rebuild the header on tx failure. */ + memcpy(&hdr_buf, skb->data, sizeof(hdr_buf)); + skb_pull(skb, ETH1394_HLEN); + + proto = hdr_buf.h_proto; + dg_size = skb->len; + + /* Set the transmission type for the packet. ARP packets and IP + * broadcast packets are sent via GASP. */ + if (memcmp(hdr_buf.h_dest, dev->broadcast, ETH1394_ALEN) == 0 || + proto == htons(ETH_P_ARP) || + (proto == htons(ETH_P_IP) && + IN_MULTICAST(ntohl(ip_hdr(skb)->daddr)))) { + tx_type = ETH1394_GASP; + dest_node = LOCAL_BUS | ALL_NODES; + max_payload = priv->bc_maxpayload - ETHER1394_GASP_OVERHEAD; + BUG_ON(max_payload < 512 - ETHER1394_GASP_OVERHEAD); + dgl = priv->bc_dgl; + if (max_payload < dg_size + hdr_type_len[ETH1394_HDR_LF_UF]) + priv->bc_dgl++; + } else { + __be64 guid = get_unaligned((u64 *)hdr_buf.h_dest); + + node = eth1394_find_node_guid(&priv->ip_node_list, + be64_to_cpu(guid)); + if (!node) + goto fail; + + node_info = + (struct eth1394_node_info *)node->ud->device.driver_data; + if (node_info->fifo == CSR1212_INVALID_ADDR_SPACE) + goto fail; + + dest_node = node->ud->ne->nodeid; + max_payload = node_info->maxpayload; + BUG_ON(max_payload < 512 - ETHER1394_GASP_OVERHEAD); + + dgl = node_info->dgl; + if (max_payload < dg_size + hdr_type_len[ETH1394_HDR_LF_UF]) + node_info->dgl++; + tx_type = ETH1394_WRREQ; + } + + /* If this is an ARP packet, convert it */ + if (proto == htons(ETH_P_ARP)) + ether1394_arp_to_1394arp(skb, dev); + + ptask->hdr.words.word1 = 0; + ptask->hdr.words.word2 = 0; + ptask->hdr.words.word3 = 0; + ptask->hdr.words.word4 = 0; + ptask->skb = skb; + ptask->priv = priv; + ptask->tx_type = tx_type; + + if (tx_type != ETH1394_GASP) { + u64 addr; + + spin_lock_irqsave(&priv->lock, flags); + addr = node_info->fifo; + spin_unlock_irqrestore(&priv->lock, flags); + + ptask->addr = addr; + ptask->dest_node = dest_node; + } + + ptask->tx_type = tx_type; + ptask->max_payload = max_payload; + ptask->outstanding_pkts = ether1394_encapsulate_prep(max_payload, + proto, &ptask->hdr, dg_size, dgl); + + /* Add the encapsulation header to the fragment */ + tx_len = ether1394_encapsulate(skb, max_payload, &ptask->hdr); + dev->trans_start = jiffies; + if (ether1394_send_packet(ptask, tx_len)) { + if (dest_node == (LOCAL_BUS | ALL_NODES)) + goto fail; + + /* At this point we want to restore the packet. When we return + * here with NETDEV_TX_BUSY we will get another entrance in this + * routine with the same skb and we need it to look the same. + * So we pull 4 more bytes, then build the header again. */ + skb_pull(skb, 4); + ether1394_header(skb, dev, ntohs(hdr_buf.h_proto), + hdr_buf.h_dest, NULL, 0); + + /* Most failures of ether1394_send_packet are recoverable. */ + netif_stop_queue(dev); + priv->wake_node = dest_node; + schedule_work(&priv->wake); + kmem_cache_free(packet_task_cache, ptask); + return NETDEV_TX_BUSY; + } + + return NETDEV_TX_OK; +fail: + if (ptask) + kmem_cache_free(packet_task_cache, ptask); + + if (skb != NULL) + dev_kfree_skb(skb); + + spin_lock_irqsave(&priv->lock, flags); + priv->stats.tx_dropped++; + priv->stats.tx_errors++; + spin_unlock_irqrestore(&priv->lock, flags); + + /* + * FIXME: According to a patch from 2003-02-26, "returning non-zero + * causes serious problems" here, allegedly. Before that patch, + * -ERRNO was returned which is not appropriate under Linux 2.6. + * Perhaps more needs to be done? Stop the queue in serious + * conditions and restart it elsewhere? + */ + /* return NETDEV_TX_BUSY; */ + return NETDEV_TX_OK; +} + +static void ether1394_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strcpy(info->driver, driver_name); + strcpy(info->bus_info, "ieee1394"); /* FIXME provide more detail? */ +} + +static struct ethtool_ops ethtool_ops = { + .get_drvinfo = ether1394_get_drvinfo +}; + +static int __init ether1394_init_module(void) +{ + int err; + + packet_task_cache = kmem_cache_create("packet_task", + sizeof(struct packet_task), + 0, 0, NULL); + if (!packet_task_cache) + return -ENOMEM; + + hpsb_register_highlevel(ð1394_highlevel); + err = hpsb_register_protocol(ð1394_proto_driver); + if (err) { + hpsb_unregister_highlevel(ð1394_highlevel); + kmem_cache_destroy(packet_task_cache); + } + return err; +} + +static void __exit ether1394_exit_module(void) +{ + hpsb_unregister_protocol(ð1394_proto_driver); + hpsb_unregister_highlevel(ð1394_highlevel); + kmem_cache_destroy(packet_task_cache); +} + +module_init(ether1394_init_module); +module_exit(ether1394_exit_module); diff --git a/drivers/ieee1394/eth1394.h b/drivers/ieee1394/eth1394.h new file mode 100644 index 0000000..4f3e2dd --- /dev/null +++ b/drivers/ieee1394/eth1394.h @@ -0,0 +1,235 @@ +/* + * eth1394.h -- Ethernet driver for Linux IEEE-1394 Subsystem + * + * Copyright (C) 2000 Bonin Franck <boninf@free.fr> + * (C) 2001 Ben Collins <bcollins@debian.org> + * + * Mainly based on work by Emanuel Pirker and Andreas E. Bombe + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __ETH1394_H +#define __ETH1394_H + +#include <linux/netdevice.h> +#include <linux/skbuff.h> +#include <asm/byteorder.h> + +#include "ieee1394.h" +#include "ieee1394_types.h" + +/* Register for incoming packets. This is 4096 bytes, which supports up to + * S3200 (per Table 16-3 of IEEE 1394b-2002). */ +#define ETHER1394_REGION_ADDR_LEN 4096 + +/* GASP identifier numbers for IPv4 over IEEE 1394 */ +#define ETHER1394_GASP_SPECIFIER_ID 0x00005E +#define ETHER1394_GASP_SPECIFIER_ID_HI ((0x00005E >> 8) & 0xffff) +#define ETHER1394_GASP_SPECIFIER_ID_LO (0x00005E & 0xff) +#define ETHER1394_GASP_VERSION 1 + +#define ETHER1394_GASP_OVERHEAD (2 * sizeof(quadlet_t)) /* for GASP header */ + +#define ETHER1394_GASP_BUFFERS 16 + +#define NODE_SET (ALL_NODES + 1) /* Node set == 64 */ + +enum eth1394_bc_states { ETHER1394_BC_ERROR, + ETHER1394_BC_RUNNING, + ETHER1394_BC_STOPPED }; + + +/* Private structure for our ethernet driver */ +struct eth1394_priv { + struct net_device_stats stats; /* Device stats */ + struct hpsb_host *host; /* The card for this dev */ + u16 bc_maxpayload; /* Max broadcast payload */ + u8 bc_sspd; /* Max broadcast speed */ + u64 local_fifo; /* Local FIFO Address */ + spinlock_t lock; /* Private lock */ + int broadcast_channel; /* Async stream Broadcast Channel */ + enum eth1394_bc_states bc_state; /* broadcast channel state */ + struct hpsb_iso *iso; /* Async stream recv handle */ + int bc_dgl; /* Outgoing broadcast datagram label */ + struct list_head ip_node_list; /* List of IP capable nodes */ + struct unit_directory *ud_list[ALL_NODES]; /* Cached unit dir list */ + + struct work_struct wake; /* Wake up after xmit failure */ + struct net_device *wake_dev; /* Stupid backlink for .wake */ + nodeid_t wake_node; /* Destination of failed xmit */ +}; + + +/* Define a fake hardware header format for the networking core. Note that + * header size cannot exceed 16 bytes as that is the size of the header cache. + * Also, we do not need the source address in the header so we omit it and + * keep the header to under 16 bytes */ +#define ETH1394_ALEN (8) +#define ETH1394_HLEN (10) + +struct eth1394hdr { + unsigned char h_dest[ETH1394_ALEN]; /* destination eth1394 addr */ + unsigned short h_proto; /* packet type ID field */ +} __attribute__((packed)); + +static inline struct eth1394hdr *eth1394_hdr(const struct sk_buff *skb) +{ + return (struct eth1394hdr *)skb_mac_header(skb); +} + +typedef enum {ETH1394_GASP, ETH1394_WRREQ} eth1394_tx_type; + +/* IP1394 headers */ + +/* Unfragmented */ +#if defined __BIG_ENDIAN_BITFIELD +struct eth1394_uf_hdr { + u16 lf:2; + u16 res:14; + u16 ether_type; /* Ethernet packet type */ +} __attribute__((packed)); +#elif defined __LITTLE_ENDIAN_BITFIELD +struct eth1394_uf_hdr { + u16 res:14; + u16 lf:2; + u16 ether_type; +} __attribute__((packed)); +#else +#error Unknown bit field type +#endif + +/* First fragment */ +#if defined __BIG_ENDIAN_BITFIELD +struct eth1394_ff_hdr { + u16 lf:2; + u16 res1:2; + u16 dg_size:12; /* Datagram size */ + u16 ether_type; /* Ethernet packet type */ + u16 dgl; /* Datagram label */ + u16 res2; +} __attribute__((packed)); +#elif defined __LITTLE_ENDIAN_BITFIELD +struct eth1394_ff_hdr { + u16 dg_size:12; + u16 res1:2; + u16 lf:2; + u16 ether_type; + u16 dgl; + u16 res2; +} __attribute__((packed)); +#else +#error Unknown bit field type +#endif + +/* XXX: Subsequent fragments, including last */ +#if defined __BIG_ENDIAN_BITFIELD +struct eth1394_sf_hdr { + u16 lf:2; + u16 res1:2; + u16 dg_size:12; /* Datagram size */ + u16 res2:4; + u16 fg_off:12; /* Fragment offset */ + u16 dgl; /* Datagram label */ + u16 res3; +} __attribute__((packed)); +#elif defined __LITTLE_ENDIAN_BITFIELD +struct eth1394_sf_hdr { + u16 dg_size:12; + u16 res1:2; + u16 lf:2; + u16 fg_off:12; + u16 res2:4; + u16 dgl; + u16 res3; +} __attribute__((packed)); +#else +#error Unknown bit field type +#endif + +#if defined __BIG_ENDIAN_BITFIELD +struct eth1394_common_hdr { + u16 lf:2; + u16 pad1:14; +} __attribute__((packed)); +#elif defined __LITTLE_ENDIAN_BITFIELD +struct eth1394_common_hdr { + u16 pad1:14; + u16 lf:2; +} __attribute__((packed)); +#else +#error Unknown bit field type +#endif + +struct eth1394_hdr_words { + u16 word1; + u16 word2; + u16 word3; + u16 word4; +}; + +union eth1394_hdr { + struct eth1394_common_hdr common; + struct eth1394_uf_hdr uf; + struct eth1394_ff_hdr ff; + struct eth1394_sf_hdr sf; + struct eth1394_hdr_words words; +}; + +/* End of IP1394 headers */ + +/* Fragment types */ +#define ETH1394_HDR_LF_UF 0 /* unfragmented */ +#define ETH1394_HDR_LF_FF 1 /* first fragment */ +#define ETH1394_HDR_LF_LF 2 /* last fragment */ +#define ETH1394_HDR_LF_IF 3 /* interior fragment */ + +#define IP1394_HW_ADDR_LEN 16 /* As per RFC */ + +/* Our arp packet (ARPHRD_IEEE1394) */ +struct eth1394_arp { + u16 hw_type; /* 0x0018 */ + u16 proto_type; /* 0x0806 */ + u8 hw_addr_len; /* 16 */ + u8 ip_addr_len; /* 4 */ + u16 opcode; /* ARP Opcode */ + /* Above is exactly the same format as struct arphdr */ + + u64 s_uniq_id; /* Sender's 64bit EUI */ + u8 max_rec; /* Sender's max packet size */ + u8 sspd; /* Sender's max speed */ + u16 fifo_hi; /* hi 16bits of sender's FIFO addr */ + u32 fifo_lo; /* lo 32bits of sender's FIFO addr */ + u32 sip; /* Sender's IP Address */ + u32 tip; /* IP Address of requested hw addr */ +}; + +/* Network timeout */ +#define ETHER1394_TIMEOUT 100000 + +/* This is our task struct. It's used for the packet complete callback. */ +struct packet_task { + struct sk_buff *skb; + int outstanding_pkts; + eth1394_tx_type tx_type; + int max_payload; + struct hpsb_packet *packet; + struct eth1394_priv *priv; + union eth1394_hdr hdr; + u64 addr; + u16 dest_node; +}; + +#endif /* __ETH1394_H */ diff --git a/drivers/ieee1394/highlevel.c b/drivers/ieee1394/highlevel.c new file mode 100644 index 0000000..272543a --- /dev/null +++ b/drivers/ieee1394/highlevel.c @@ -0,0 +1,690 @@ +/* + * IEEE 1394 for Linux + * + * Copyright (C) 1999 Andreas E. Bombe + * + * This code is licensed under the GPL. See the file COPYING in the root + * directory of the kernel sources for details. + * + * + * Contributions: + * + * Christian Toegel <christian.toegel@gmx.at> + * unregister address space + * + * Manfred Weihs <weihs@ict.tuwien.ac.at> + * unregister address space + * + */ + +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/bitops.h> + +#include "ieee1394.h" +#include "ieee1394_types.h" +#include "hosts.h" +#include "ieee1394_core.h" +#include "highlevel.h" +#include "nodemgr.h" + + +struct hl_host_info { + struct list_head list; + struct hpsb_host *host; + size_t size; + unsigned long key; + void *data; +}; + + +static LIST_HEAD(hl_drivers); +static DECLARE_RWSEM(hl_drivers_sem); + +static LIST_HEAD(hl_irqs); +static DEFINE_RWLOCK(hl_irqs_lock); + +static DEFINE_RWLOCK(addr_space_lock); + + +static struct hl_host_info *hl_get_hostinfo(struct hpsb_highlevel *hl, + struct hpsb_host *host) +{ + struct hl_host_info *hi = NULL; + + if (!hl || !host) + return NULL; + + read_lock(&hl->host_info_lock); + list_for_each_entry(hi, &hl->host_info_list, list) { + if (hi->host == host) { + read_unlock(&hl->host_info_lock); + return hi; + } + } + read_unlock(&hl->host_info_lock); + return NULL; +} + +/** + * hpsb_get_hostinfo - retrieve a hostinfo pointer bound to this driver/host + * + * Returns a per @host and @hl driver data structure that was previously stored + * by hpsb_create_hostinfo. + */ +void *hpsb_get_hostinfo(struct hpsb_highlevel *hl, struct hpsb_host *host) +{ + struct hl_host_info *hi = hl_get_hostinfo(hl, host); + + return hi ? hi->data : NULL; +} + +/** + * hpsb_create_hostinfo - allocate a hostinfo pointer bound to this driver/host + * + * Allocate a hostinfo pointer backed by memory with @data_size and bind it to + * to this @hl driver and @host. If @data_size is zero, then the return here is + * only valid for error checking. + */ +void *hpsb_create_hostinfo(struct hpsb_highlevel *hl, struct hpsb_host *host, + size_t data_size) +{ + struct hl_host_info *hi; + void *data; + unsigned long flags; + + hi = hl_get_hostinfo(hl, host); + if (hi) { + HPSB_ERR("%s called hpsb_create_hostinfo when hostinfo already" + " exists", hl->name); + return NULL; + } + + hi = kzalloc(sizeof(*hi) + data_size, GFP_ATOMIC); + if (!hi) + return NULL; + + if (data_size) { + data = hi->data = hi + 1; + hi->size = data_size; + } else + data = hi; + + hi->host = host; + + write_lock_irqsave(&hl->host_info_lock, flags); + list_add_tail(&hi->list, &hl->host_info_list); + write_unlock_irqrestore(&hl->host_info_lock, flags); + + return data; +} + +/** + * hpsb_set_hostinfo - set the hostinfo pointer to something useful + * + * Usually follows a call to hpsb_create_hostinfo, where the size is 0. + */ +int hpsb_set_hostinfo(struct hpsb_highlevel *hl, struct hpsb_host *host, + void *data) +{ + struct hl_host_info *hi; + + hi = hl_get_hostinfo(hl, host); + if (hi) { + if (!hi->size && !hi->data) { + hi->data = data; + return 0; + } else + HPSB_ERR("%s called hpsb_set_hostinfo when hostinfo " + "already has data", hl->name); + } else + HPSB_ERR("%s called hpsb_set_hostinfo when no hostinfo exists", + hl->name); + return -EINVAL; +} + +/** + * hpsb_destroy_hostinfo - free and remove a hostinfo pointer + * + * Free and remove the hostinfo pointer bound to this @hl driver and @host. + */ +void hpsb_destroy_hostinfo(struct hpsb_highlevel *hl, struct hpsb_host *host) +{ + struct hl_host_info *hi; + + hi = hl_get_hostinfo(hl, host); + if (hi) { + unsigned long flags; + write_lock_irqsave(&hl->host_info_lock, flags); + list_del(&hi->list); + write_unlock_irqrestore(&hl->host_info_lock, flags); + kfree(hi); + } + return; +} + +/** + * hpsb_set_hostinfo_key - set an alternate lookup key for an hostinfo + * + * Sets an alternate lookup key for the hostinfo bound to this @hl driver and + * @host. + */ +void hpsb_set_hostinfo_key(struct hpsb_highlevel *hl, struct hpsb_host *host, + unsigned long key) +{ + struct hl_host_info *hi; + + hi = hl_get_hostinfo(hl, host); + if (hi) + hi->key = key; + return; +} + +/** + * hpsb_get_hostinfo_bykey - retrieve a hostinfo pointer by its alternate key + */ +void *hpsb_get_hostinfo_bykey(struct hpsb_highlevel *hl, unsigned long key) +{ + struct hl_host_info *hi; + void *data = NULL; + + if (!hl) + return NULL; + + read_lock(&hl->host_info_lock); + list_for_each_entry(hi, &hl->host_info_list, list) { + if (hi->key == key) { + data = hi->data; + break; + } + } + read_unlock(&hl->host_info_lock); + return data; +} + +static int highlevel_for_each_host_reg(struct hpsb_host *host, void *__data) +{ + struct hpsb_highlevel *hl = __data; + + hl->add_host(host); + + if (host->update_config_rom && hpsb_update_config_rom_image(host) < 0) + HPSB_ERR("Failed to generate Configuration ROM image for host " + "%s-%d", hl->name, host->id); + return 0; +} + +/** + * hpsb_register_highlevel - register highlevel driver + * + * The name pointer in @hl has to stay valid at all times because the string is + * not copied. + */ +void hpsb_register_highlevel(struct hpsb_highlevel *hl) +{ + unsigned long flags; + + hpsb_init_highlevel(hl); + INIT_LIST_HEAD(&hl->addr_list); + + down_write(&hl_drivers_sem); + list_add_tail(&hl->hl_list, &hl_drivers); + up_write(&hl_drivers_sem); + + write_lock_irqsave(&hl_irqs_lock, flags); + list_add_tail(&hl->irq_list, &hl_irqs); + write_unlock_irqrestore(&hl_irqs_lock, flags); + + if (hl->add_host) + nodemgr_for_each_host(hl, highlevel_for_each_host_reg); + return; +} + +static void __delete_addr(struct hpsb_address_serve *as) +{ + list_del(&as->host_list); + list_del(&as->hl_list); + kfree(as); +} + +static void __unregister_host(struct hpsb_highlevel *hl, struct hpsb_host *host, + int update_cr) +{ + unsigned long flags; + struct list_head *lh, *next; + struct hpsb_address_serve *as; + + /* First, let the highlevel driver unreg */ + if (hl->remove_host) + hl->remove_host(host); + + /* Remove any addresses that are matched for this highlevel driver + * and this particular host. */ + write_lock_irqsave(&addr_space_lock, flags); + list_for_each_safe (lh, next, &hl->addr_list) { + as = list_entry(lh, struct hpsb_address_serve, hl_list); + if (as->host == host) + __delete_addr(as); + } + write_unlock_irqrestore(&addr_space_lock, flags); + + /* Now update the config-rom to reflect anything removed by the + * highlevel driver. */ + if (update_cr && host->update_config_rom && + hpsb_update_config_rom_image(host) < 0) + HPSB_ERR("Failed to generate Configuration ROM image for host " + "%s-%d", hl->name, host->id); + + /* Finally remove all the host info associated between these two. */ + hpsb_destroy_hostinfo(hl, host); +} + +static int highlevel_for_each_host_unreg(struct hpsb_host *host, void *__data) +{ + struct hpsb_highlevel *hl = __data; + + __unregister_host(hl, host, 1); + return 0; +} + +/** + * hpsb_unregister_highlevel - unregister highlevel driver + */ +void hpsb_unregister_highlevel(struct hpsb_highlevel *hl) +{ + unsigned long flags; + + write_lock_irqsave(&hl_irqs_lock, flags); + list_del(&hl->irq_list); + write_unlock_irqrestore(&hl_irqs_lock, flags); + + down_write(&hl_drivers_sem); + list_del(&hl->hl_list); + up_write(&hl_drivers_sem); + + nodemgr_for_each_host(hl, highlevel_for_each_host_unreg); +} + +/** + * hpsb_allocate_and_register_addrspace - alloc' and reg' a host address space + * + * @start and @end are 48 bit pointers and have to be quadlet aligned. + * @end points to the first address behind the handled addresses. This + * function can be called multiple times for a single hpsb_highlevel @hl to + * implement sparse register sets. The requested region must not overlap any + * previously allocated region, otherwise registering will fail. + * + * It returns true for successful allocation. Address spaces can be + * unregistered with hpsb_unregister_addrspace. All remaining address spaces + * are automatically deallocated together with the hpsb_highlevel @hl. + */ +u64 hpsb_allocate_and_register_addrspace(struct hpsb_highlevel *hl, + struct hpsb_host *host, + struct hpsb_address_ops *ops, + u64 size, u64 alignment, + u64 start, u64 end) +{ + struct hpsb_address_serve *as, *a1, *a2; + struct list_head *entry; + u64 retval = CSR1212_INVALID_ADDR_SPACE; + unsigned long flags; + u64 align_mask = ~(alignment - 1); + + if ((alignment & 3) || (alignment > 0x800000000000ULL) || + (hweight64(alignment) != 1)) { + HPSB_ERR("%s called with invalid alignment: 0x%048llx", + __func__, (unsigned long long)alignment); + return retval; + } + + /* default range, + * avoids controller's posted write area (see OHCI 1.1 clause 1.5) */ + if (start == CSR1212_INVALID_ADDR_SPACE && + end == CSR1212_INVALID_ADDR_SPACE) { + start = host->middle_addr_space; + end = CSR1212_ALL_SPACE_END; + } + + if (((start|end) & ~align_mask) || (start >= end) || + (end > CSR1212_ALL_SPACE_END)) { + HPSB_ERR("%s called with invalid addresses " + "(start = %012Lx end = %012Lx)", __func__, + (unsigned long long)start,(unsigned long long)end); + return retval; + } + + as = kmalloc(sizeof(*as), GFP_KERNEL); + if (!as) + return retval; + + INIT_LIST_HEAD(&as->host_list); + INIT_LIST_HEAD(&as->hl_list); + as->op = ops; + as->host = host; + + write_lock_irqsave(&addr_space_lock, flags); + list_for_each(entry, &host->addr_space) { + u64 a1sa, a1ea; + u64 a2sa, a2ea; + + a1 = list_entry(entry, struct hpsb_address_serve, host_list); + a2 = list_entry(entry->next, struct hpsb_address_serve, + host_list); + + a1sa = a1->start & align_mask; + a1ea = (a1->end + alignment -1) & align_mask; + a2sa = a2->start & align_mask; + a2ea = (a2->end + alignment -1) & align_mask; + + if ((a2sa - a1ea >= size) && (a2sa - start >= size) && + (a2sa > start)) { + as->start = max(start, a1ea); + as->end = as->start + size; + list_add(&as->host_list, entry); + list_add_tail(&as->hl_list, &hl->addr_list); + retval = as->start; + break; + } + } + write_unlock_irqrestore(&addr_space_lock, flags); + + if (retval == CSR1212_INVALID_ADDR_SPACE) + kfree(as); + return retval; +} + +/** + * hpsb_register_addrspace - register a host address space + * + * @start and @end are 48 bit pointers and have to be quadlet aligned. + * @end points to the first address behind the handled addresses. This + * function can be called multiple times for a single hpsb_highlevel @hl to + * implement sparse register sets. The requested region must not overlap any + * previously allocated region, otherwise registering will fail. + * + * It returns true for successful allocation. Address spaces can be + * unregistered with hpsb_unregister_addrspace. All remaining address spaces + * are automatically deallocated together with the hpsb_highlevel @hl. + */ +int hpsb_register_addrspace(struct hpsb_highlevel *hl, struct hpsb_host *host, + struct hpsb_address_ops *ops, u64 start, u64 end) +{ + struct hpsb_address_serve *as; + struct list_head *lh; + int retval = 0; + unsigned long flags; + + if (((start|end) & 3) || (start >= end) || + (end > CSR1212_ALL_SPACE_END)) { + HPSB_ERR("%s called with invalid addresses", __func__); + return 0; + } + + as = kmalloc(sizeof(*as), GFP_ATOMIC); + if (!as) + return 0; + + INIT_LIST_HEAD(&as->host_list); + INIT_LIST_HEAD(&as->hl_list); + as->op = ops; + as->start = start; + as->end = end; + as->host = host; + + write_lock_irqsave(&addr_space_lock, flags); + list_for_each(lh, &host->addr_space) { + struct hpsb_address_serve *as_this = + list_entry(lh, struct hpsb_address_serve, host_list); + struct hpsb_address_serve *as_next = + list_entry(lh->next, struct hpsb_address_serve, + host_list); + + if (as_this->end > as->start) + break; + + if (as_next->start >= as->end) { + list_add(&as->host_list, lh); + list_add_tail(&as->hl_list, &hl->addr_list); + retval = 1; + break; + } + } + write_unlock_irqrestore(&addr_space_lock, flags); + + if (retval == 0) + kfree(as); + return retval; +} + +int hpsb_unregister_addrspace(struct hpsb_highlevel *hl, struct hpsb_host *host, + u64 start) +{ + int retval = 0; + struct hpsb_address_serve *as; + struct list_head *lh, *next; + unsigned long flags; + + write_lock_irqsave(&addr_space_lock, flags); + list_for_each_safe (lh, next, &hl->addr_list) { + as = list_entry(lh, struct hpsb_address_serve, hl_list); + if (as->start == start && as->host == host) { + __delete_addr(as); + retval = 1; + break; + } + } + write_unlock_irqrestore(&addr_space_lock, flags); + return retval; +} + +static struct hpsb_address_ops dummy_ops; + +/* dummy address spaces as lower and upper bounds of the host's a.s. list */ +static void init_hpsb_highlevel(struct hpsb_host *host) +{ + INIT_LIST_HEAD(&host->dummy_zero_addr.host_list); + INIT_LIST_HEAD(&host->dummy_zero_addr.hl_list); + INIT_LIST_HEAD(&host->dummy_max_addr.host_list); + INIT_LIST_HEAD(&host->dummy_max_addr.hl_list); + + host->dummy_zero_addr.op = host->dummy_max_addr.op = &dummy_ops; + + host->dummy_zero_addr.start = host->dummy_zero_addr.end = 0; + host->dummy_max_addr.start = host->dummy_max_addr.end = ((u64) 1) << 48; + + list_add_tail(&host->dummy_zero_addr.host_list, &host->addr_space); + list_add_tail(&host->dummy_max_addr.host_list, &host->addr_space); +} + +void highlevel_add_host(struct hpsb_host *host) +{ + struct hpsb_highlevel *hl; + + init_hpsb_highlevel(host); + + down_read(&hl_drivers_sem); + list_for_each_entry(hl, &hl_drivers, hl_list) { + if (hl->add_host) + hl->add_host(host); + } + up_read(&hl_drivers_sem); + if (host->update_config_rom && hpsb_update_config_rom_image(host) < 0) + HPSB_ERR("Failed to generate Configuration ROM image for host " + "%s-%d", hl->name, host->id); +} + +void highlevel_remove_host(struct hpsb_host *host) +{ + struct hpsb_highlevel *hl; + + down_read(&hl_drivers_sem); + list_for_each_entry(hl, &hl_drivers, hl_list) + __unregister_host(hl, host, 0); + up_read(&hl_drivers_sem); +} + +void highlevel_host_reset(struct hpsb_host *host) +{ + unsigned long flags; + struct hpsb_highlevel *hl; + + read_lock_irqsave(&hl_irqs_lock, flags); + list_for_each_entry(hl, &hl_irqs, irq_list) { + if (hl->host_reset) + hl->host_reset(host); + } + read_unlock_irqrestore(&hl_irqs_lock, flags); +} + +void highlevel_fcp_request(struct hpsb_host *host, int nodeid, int direction, + void *data, size_t length) +{ + unsigned long flags; + struct hpsb_highlevel *hl; + int cts = ((quadlet_t *)data)[0] >> 4; + + read_lock_irqsave(&hl_irqs_lock, flags); + list_for_each_entry(hl, &hl_irqs, irq_list) { + if (hl->fcp_request) + hl->fcp_request(host, nodeid, direction, cts, data, + length); + } + read_unlock_irqrestore(&hl_irqs_lock, flags); +} + +/* + * highlevel_read, highlevel_write, highlevel_lock, highlevel_lock64: + * + * These functions are called to handle transactions. They are called when a + * packet arrives. The flags argument contains the second word of the first + * header quadlet of the incoming packet (containing transaction label, retry + * code, transaction code and priority). These functions either return a + * response code or a negative number. In the first case a response will be + * generated. In the latter case, no response will be sent and the driver which + * handled the request will send the response itself. + */ +int highlevel_read(struct hpsb_host *host, int nodeid, void *data, u64 addr, + unsigned int length, u16 flags) +{ + struct hpsb_address_serve *as; + unsigned int partlength; + int rcode = RCODE_ADDRESS_ERROR; + + read_lock(&addr_space_lock); + list_for_each_entry(as, &host->addr_space, host_list) { + if (as->start > addr) + break; + + if (as->end > addr) { + partlength = min(as->end - addr, (u64) length); + + if (as->op->read) + rcode = as->op->read(host, nodeid, data, + addr, partlength, flags); + else + rcode = RCODE_TYPE_ERROR; + + data += partlength; + length -= partlength; + addr += partlength; + + if ((rcode != RCODE_COMPLETE) || !length) + break; + } + } + read_unlock(&addr_space_lock); + + if (length && (rcode == RCODE_COMPLETE)) + rcode = RCODE_ADDRESS_ERROR; + return rcode; +} + +int highlevel_write(struct hpsb_host *host, int nodeid, int destid, void *data, + u64 addr, unsigned int length, u16 flags) +{ + struct hpsb_address_serve *as; + unsigned int partlength; + int rcode = RCODE_ADDRESS_ERROR; + + read_lock(&addr_space_lock); + list_for_each_entry(as, &host->addr_space, host_list) { + if (as->start > addr) + break; + + if (as->end > addr) { + partlength = min(as->end - addr, (u64) length); + + if (as->op->write) + rcode = as->op->write(host, nodeid, destid, + data, addr, partlength, + flags); + else + rcode = RCODE_TYPE_ERROR; + + data += partlength; + length -= partlength; + addr += partlength; + + if ((rcode != RCODE_COMPLETE) || !length) + break; + } + } + read_unlock(&addr_space_lock); + + if (length && (rcode == RCODE_COMPLETE)) + rcode = RCODE_ADDRESS_ERROR; + return rcode; +} + +int highlevel_lock(struct hpsb_host *host, int nodeid, quadlet_t *store, + u64 addr, quadlet_t data, quadlet_t arg, int ext_tcode, + u16 flags) +{ + struct hpsb_address_serve *as; + int rcode = RCODE_ADDRESS_ERROR; + + read_lock(&addr_space_lock); + list_for_each_entry(as, &host->addr_space, host_list) { + if (as->start > addr) + break; + + if (as->end > addr) { + if (as->op->lock) + rcode = as->op->lock(host, nodeid, store, addr, + data, arg, ext_tcode, + flags); + else + rcode = RCODE_TYPE_ERROR; + break; + } + } + read_unlock(&addr_space_lock); + return rcode; +} + +int highlevel_lock64(struct hpsb_host *host, int nodeid, octlet_t *store, + u64 addr, octlet_t data, octlet_t arg, int ext_tcode, + u16 flags) +{ + struct hpsb_address_serve *as; + int rcode = RCODE_ADDRESS_ERROR; + + read_lock(&addr_space_lock); + + list_for_each_entry(as, &host->addr_space, host_list) { + if (as->start > addr) + break; + + if (as->end > addr) { + if (as->op->lock64) + rcode = as->op->lock64(host, nodeid, store, + addr, data, arg, + ext_tcode, flags); + else + rcode = RCODE_TYPE_ERROR; + break; + } + } + read_unlock(&addr_space_lock); + return rcode; +} diff --git a/drivers/ieee1394/highlevel.h b/drivers/ieee1394/highlevel.h new file mode 100644 index 0000000..bc5d085 --- /dev/null +++ b/drivers/ieee1394/highlevel.h @@ -0,0 +1,140 @@ +#ifndef IEEE1394_HIGHLEVEL_H +#define IEEE1394_HIGHLEVEL_H + +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/types.h> + +struct module; + +#include "ieee1394_types.h" + +struct hpsb_host; + +/* internal to ieee1394 core */ +struct hpsb_address_serve { + struct list_head host_list; /* per host list */ + struct list_head hl_list; /* hpsb_highlevel list */ + struct hpsb_address_ops *op; + struct hpsb_host *host; + u64 start; /* first address handled, quadlet aligned */ + u64 end; /* first address behind, quadlet aligned */ +}; + +/* Only the following structures are of interest to actual highlevel drivers. */ + +struct hpsb_highlevel { + const char *name; + + /* Any of the following pointers can legally be NULL. */ + + /* New host initialized. Will also be called during + * hpsb_register_highlevel for all hosts already installed. */ + void (*add_host)(struct hpsb_host *host); + + /* Host about to be removed. Will also be called during + * hpsb_unregister_highlevel once for each host. */ + void (*remove_host)(struct hpsb_host *host); + + /* Host experienced bus reset with possible configuration changes. + * Note that this one may occur during interrupt/bottom half handling. + * You can not expect to be able to do stock hpsb_reads. */ + void (*host_reset)(struct hpsb_host *host); + + /* A write request was received on either the FCP_COMMAND (direction = + * 0) or the FCP_RESPONSE (direction = 1) register. The cts arg + * contains the cts field (first byte of data). */ + void (*fcp_request)(struct hpsb_host *host, int nodeid, int direction, + int cts, u8 *data, size_t length); + + /* These are initialized by the subsystem when the + * hpsb_higlevel is registered. */ + struct list_head hl_list; + struct list_head irq_list; + struct list_head addr_list; + + struct list_head host_info_list; + rwlock_t host_info_lock; +}; + +struct hpsb_address_ops { + /* + * Null function pointers will make the respective operation complete + * with RCODE_TYPE_ERROR. Makes for easy to implement read-only + * registers (just leave everything but read NULL). + * + * All functions shall return appropriate IEEE 1394 rcodes. + */ + + /* These functions have to implement block reads for themselves. + * + * These functions either return a response code or a negative number. + * In the first case a response will be generated. In the latter case, + * no response will be sent and the driver which handled the request + * will send the response itself. */ + int (*read)(struct hpsb_host *host, int nodeid, quadlet_t *buffer, + u64 addr, size_t length, u16 flags); + int (*write)(struct hpsb_host *host, int nodeid, int destid, + quadlet_t *data, u64 addr, size_t length, u16 flags); + + /* Lock transactions: write results of ext_tcode operation into + * *store. */ + int (*lock)(struct hpsb_host *host, int nodeid, quadlet_t *store, + u64 addr, quadlet_t data, quadlet_t arg, int ext_tcode, + u16 flags); + int (*lock64)(struct hpsb_host *host, int nodeid, octlet_t *store, + u64 addr, octlet_t data, octlet_t arg, int ext_tcode, + u16 flags); +}; + +void highlevel_add_host(struct hpsb_host *host); +void highlevel_remove_host(struct hpsb_host *host); +void highlevel_host_reset(struct hpsb_host *host); +int highlevel_read(struct hpsb_host *host, int nodeid, void *data, u64 addr, + unsigned int length, u16 flags); +int highlevel_write(struct hpsb_host *host, int nodeid, int destid, void *data, + u64 addr, unsigned int length, u16 flags); +int highlevel_lock(struct hpsb_host *host, int nodeid, quadlet_t *store, + u64 addr, quadlet_t data, quadlet_t arg, int ext_tcode, + u16 flags); +int highlevel_lock64(struct hpsb_host *host, int nodeid, octlet_t *store, + u64 addr, octlet_t data, octlet_t arg, int ext_tcode, + u16 flags); +void highlevel_fcp_request(struct hpsb_host *host, int nodeid, int direction, + void *data, size_t length); + +/** + * hpsb_init_highlevel - initialize a struct hpsb_highlevel + * + * This is only necessary if hpsb_get_hostinfo_bykey can be called + * before hpsb_register_highlevel. + */ +static inline void hpsb_init_highlevel(struct hpsb_highlevel *hl) +{ + rwlock_init(&hl->host_info_lock); + INIT_LIST_HEAD(&hl->host_info_list); +} +void hpsb_register_highlevel(struct hpsb_highlevel *hl); +void hpsb_unregister_highlevel(struct hpsb_highlevel *hl); + +u64 hpsb_allocate_and_register_addrspace(struct hpsb_highlevel *hl, + struct hpsb_host *host, + struct hpsb_address_ops *ops, + u64 size, u64 alignment, + u64 start, u64 end); +int hpsb_register_addrspace(struct hpsb_highlevel *hl, struct hpsb_host *host, + struct hpsb_address_ops *ops, u64 start, u64 end); +int hpsb_unregister_addrspace(struct hpsb_highlevel *hl, struct hpsb_host *host, + u64 start); + +void *hpsb_get_hostinfo(struct hpsb_highlevel *hl, struct hpsb_host *host); +void *hpsb_create_hostinfo(struct hpsb_highlevel *hl, struct hpsb_host *host, + size_t data_size); +void hpsb_destroy_hostinfo(struct hpsb_highlevel *hl, struct hpsb_host *host); +void hpsb_set_hostinfo_key(struct hpsb_highlevel *hl, struct hpsb_host *host, + unsigned long key); +void *hpsb_get_hostinfo_bykey(struct hpsb_highlevel *hl, unsigned long key); +int hpsb_set_hostinfo(struct hpsb_highlevel *hl, struct hpsb_host *host, + void *data); + +#endif /* IEEE1394_HIGHLEVEL_H */ diff --git a/drivers/ieee1394/hosts.c b/drivers/ieee1394/hosts.c new file mode 100644 index 0000000..237d0c9 --- /dev/null +++ b/drivers/ieee1394/hosts.c @@ -0,0 +1,249 @@ +/* + * IEEE 1394 for Linux + * + * Low level (host adapter) management. + * + * Copyright (C) 1999 Andreas E. Bombe + * Copyright (C) 1999 Emanuel Pirker + * + * This code is licensed under the GPL. See the file COPYING in the root + * directory of the kernel sources for details. + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/list.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/mutex.h> + +#include "csr1212.h" +#include "ieee1394.h" +#include "ieee1394_types.h" +#include "hosts.h" +#include "ieee1394_core.h" +#include "highlevel.h" +#include "nodemgr.h" +#include "csr.h" +#include "config_roms.h" + + +static void delayed_reset_bus(struct work_struct *work) +{ + struct hpsb_host *host = + container_of(work, struct hpsb_host, delayed_reset.work); + int generation = host->csr.generation + 1; + + /* The generation field rolls over to 2 rather than 0 per IEEE + * 1394a-2000. */ + if (generation > 0xf || generation < 2) + generation = 2; + + CSR_SET_BUS_INFO_GENERATION(host->csr.rom, generation); + if (csr1212_generate_csr_image(host->csr.rom) != CSR1212_SUCCESS) { + /* CSR image creation failed. + * Reset generation field and do not issue a bus reset. */ + CSR_SET_BUS_INFO_GENERATION(host->csr.rom, + host->csr.generation); + return; + } + + host->csr.generation = generation; + + host->update_config_rom = 0; + if (host->driver->set_hw_config_rom) + host->driver->set_hw_config_rom(host, + host->csr.rom->bus_info_data); + + host->csr.gen_timestamp[host->csr.generation] = jiffies; + hpsb_reset_bus(host, SHORT_RESET); +} + +static int dummy_transmit_packet(struct hpsb_host *h, struct hpsb_packet *p) +{ + return 0; +} + +static int dummy_devctl(struct hpsb_host *h, enum devctl_cmd c, int arg) +{ + return -1; +} + +static int dummy_isoctl(struct hpsb_iso *iso, enum isoctl_cmd command, + unsigned long arg) +{ + return -1; +} + +static struct hpsb_host_driver dummy_driver = { + .transmit_packet = dummy_transmit_packet, + .devctl = dummy_devctl, + .isoctl = dummy_isoctl +}; + +static int alloc_hostnum_cb(struct hpsb_host *host, void *__data) +{ + int *hostnum = __data; + + if (host->id == *hostnum) + return 1; + + return 0; +} + +static DEFINE_MUTEX(host_num_alloc); + +/** + * hpsb_alloc_host - allocate a new host controller. + * @drv: the driver that will manage the host controller + * @extra: number of extra bytes to allocate for the driver + * + * Allocate a &hpsb_host and initialize the general subsystem specific + * fields. If the driver needs to store per host data, as drivers + * usually do, the amount of memory required can be specified by the + * @extra parameter. Once allocated, the driver should initialize the + * driver specific parts, enable the controller and make it available + * to the general subsystem using hpsb_add_host(). + * + * Return Value: a pointer to the &hpsb_host if successful, %NULL if + * no memory was available. + */ +struct hpsb_host *hpsb_alloc_host(struct hpsb_host_driver *drv, size_t extra, + struct device *dev) +{ + struct hpsb_host *h; + int i; + int hostnum = 0; + + h = kzalloc(sizeof(*h) + extra, GFP_KERNEL); + if (!h) + return NULL; + + h->csr.rom = csr1212_create_csr(&csr_bus_ops, CSR_BUS_INFO_SIZE, h); + if (!h->csr.rom) + goto fail; + + h->hostdata = h + 1; + h->driver = drv; + + INIT_LIST_HEAD(&h->pending_packets); + INIT_LIST_HEAD(&h->addr_space); + + for (i = 2; i < 16; i++) + h->csr.gen_timestamp[i] = jiffies - 60 * HZ; + + atomic_set(&h->generation, 0); + + INIT_DELAYED_WORK(&h->delayed_reset, delayed_reset_bus); + + init_timer(&h->timeout); + h->timeout.data = (unsigned long) h; + h->timeout.function = abort_timedouts; + h->timeout_interval = HZ / 20; /* 50ms, half of minimum SPLIT_TIMEOUT */ + + h->topology_map = h->csr.topology_map + 3; + h->speed_map = (u8 *)(h->csr.speed_map + 2); + + mutex_lock(&host_num_alloc); + while (nodemgr_for_each_host(&hostnum, alloc_hostnum_cb)) + hostnum++; + mutex_unlock(&host_num_alloc); + h->id = hostnum; + + memcpy(&h->device, &nodemgr_dev_template_host, sizeof(h->device)); + h->device.parent = dev; + set_dev_node(&h->device, dev_to_node(dev)); + dev_set_name(&h->device, "fw-host%d", h->id); + + h->host_dev.parent = &h->device; + h->host_dev.class = &hpsb_host_class; + dev_set_name(&h->host_dev, "fw-host%d", h->id); + + if (device_register(&h->device)) + goto fail; + if (device_register(&h->host_dev)) { + device_unregister(&h->device); + goto fail; + } + get_device(&h->device); + + return h; + +fail: + kfree(h); + return NULL; +} + +int hpsb_add_host(struct hpsb_host *host) +{ + if (hpsb_default_host_entry(host)) + return -ENOMEM; + + highlevel_add_host(host); + return 0; +} + +void hpsb_resume_host(struct hpsb_host *host) +{ + if (host->driver->set_hw_config_rom) + host->driver->set_hw_config_rom(host, + host->csr.rom->bus_info_data); + host->driver->devctl(host, RESET_BUS, SHORT_RESET); +} + +void hpsb_remove_host(struct hpsb_host *host) +{ + host->is_shutdown = 1; + + cancel_delayed_work(&host->delayed_reset); + flush_scheduled_work(); + + host->driver = &dummy_driver; + highlevel_remove_host(host); + + device_unregister(&host->host_dev); + device_unregister(&host->device); +} + +/** + * hpsb_update_config_rom_image - updates configuration ROM image of a host + * + * Updates the configuration ROM image of a host. rom_version must be the + * current version, otherwise it will fail with return value -1. If this + * host does not support config-rom-update, it will return -%EINVAL. + * Return value 0 indicates success. + */ +int hpsb_update_config_rom_image(struct hpsb_host *host) +{ + unsigned long reset_delay; + int next_gen = host->csr.generation + 1; + + if (!host->update_config_rom) + return -EINVAL; + + if (next_gen > 0xf) + next_gen = 2; + + /* Stop the delayed interrupt, we're about to change the config rom and + * it would be a waste to do a bus reset twice. */ + cancel_delayed_work(&host->delayed_reset); + + /* IEEE 1394a-2000 prohibits using the same generation number + * twice in a 60 second period. */ + if (time_before(jiffies, host->csr.gen_timestamp[next_gen] + 60 * HZ)) + /* Wait 60 seconds from the last time this generation number was + * used. */ + reset_delay = + (60 * HZ) + host->csr.gen_timestamp[next_gen] - jiffies; + else + /* Wait 1 second in case some other code wants to change the + * Config ROM in the near future. */ + reset_delay = HZ; + + PREPARE_DELAYED_WORK(&host->delayed_reset, delayed_reset_bus); + schedule_delayed_work(&host->delayed_reset, reset_delay); + + return 0; +} diff --git a/drivers/ieee1394/hosts.h b/drivers/ieee1394/hosts.h new file mode 100644 index 0000000..dd22995 --- /dev/null +++ b/drivers/ieee1394/hosts.h @@ -0,0 +1,201 @@ +#ifndef _IEEE1394_HOSTS_H +#define _IEEE1394_HOSTS_H + +#include <linux/device.h> +#include <linux/list.h> +#include <linux/timer.h> +#include <linux/types.h> +#include <linux/workqueue.h> +#include <asm/atomic.h> + +struct pci_dev; +struct module; + +#include "ieee1394_types.h" +#include "csr.h" +#include "highlevel.h" + +struct hpsb_packet; +struct hpsb_iso; + +struct hpsb_host { + struct list_head host_list; + + void *hostdata; + + atomic_t generation; + + struct list_head pending_packets; + struct timer_list timeout; + unsigned long timeout_interval; + + int node_count; /* number of identified nodes on this bus */ + int selfid_count; /* total number of SelfIDs received */ + int nodes_active; /* number of nodes with active link layer */ + + nodeid_t node_id; /* node ID of this host */ + nodeid_t irm_id; /* ID of this bus' isochronous resource manager */ + nodeid_t busmgr_id; /* ID of this bus' bus manager */ + + /* this nodes state */ + unsigned in_bus_reset:1; + unsigned is_shutdown:1; + unsigned resume_packet_sent:1; + + /* this nodes' duties on the bus */ + unsigned is_root:1; + unsigned is_cycmst:1; + unsigned is_irm:1; + unsigned is_busmgr:1; + + int reset_retries; + quadlet_t *topology_map; + u8 *speed_map; + + int id; + struct hpsb_host_driver *driver; + struct pci_dev *pdev; + struct device device; + struct device host_dev; + + struct delayed_work delayed_reset; + unsigned config_roms:31; + unsigned update_config_rom:1; + + struct list_head addr_space; + u64 low_addr_space; /* upper bound of physical DMA area */ + u64 middle_addr_space; /* upper bound of posted write area */ + + u8 speed[ALL_NODES]; /* speed between each node and local node */ + + /* per node tlabel allocation */ + u8 next_tl[ALL_NODES]; + struct { DECLARE_BITMAP(map, 64); } tl_pool[ALL_NODES]; + + struct csr_control csr; + + struct hpsb_address_serve dummy_zero_addr; + struct hpsb_address_serve dummy_max_addr; +}; + +enum devctl_cmd { + /* Host is requested to reset its bus and cancel all outstanding async + * requests. If arg == 1, it shall also attempt to become root on the + * bus. Return void. */ + RESET_BUS, + + /* Arg is void, return value is the hardware cycle counter value. */ + GET_CYCLE_COUNTER, + + /* Set the hardware cycle counter to the value in arg, return void. + * FIXME - setting is probably not required. */ + SET_CYCLE_COUNTER, + + /* Configure hardware for new bus ID in arg, return void. */ + SET_BUS_ID, + + /* If arg true, start sending cycle start packets, stop if arg == 0. + * Return void. */ + ACT_CYCLE_MASTER, + + /* Cancel all outstanding async requests without resetting the bus. + * Return void. */ + CANCEL_REQUESTS, +}; + +enum isoctl_cmd { + /* rawiso API - see iso.h for the meanings of these commands + * (they correspond exactly to the hpsb_iso_* API functions) + * INIT = allocate resources + * START = begin transmission/reception + * STOP = halt transmission/reception + * QUEUE/RELEASE = produce/consume packets + * SHUTDOWN = deallocate resources + */ + + XMIT_INIT, + XMIT_START, + XMIT_STOP, + XMIT_QUEUE, + XMIT_SHUTDOWN, + + RECV_INIT, + RECV_LISTEN_CHANNEL, /* multi-channel only */ + RECV_UNLISTEN_CHANNEL, /* multi-channel only */ + RECV_SET_CHANNEL_MASK, /* multi-channel only; arg is a *u64 */ + RECV_START, + RECV_STOP, + RECV_RELEASE, + RECV_SHUTDOWN, + RECV_FLUSH +}; + +enum reset_types { + /* 166 microsecond reset -- only type of reset available on + non-1394a capable controllers */ + LONG_RESET, + + /* Short (arbitrated) reset -- only available on 1394a capable + controllers */ + SHORT_RESET, + + /* Variants that set force_root before issueing the bus reset */ + LONG_RESET_FORCE_ROOT, SHORT_RESET_FORCE_ROOT, + + /* Variants that clear force_root before issueing the bus reset */ + LONG_RESET_NO_FORCE_ROOT, SHORT_RESET_NO_FORCE_ROOT +}; + +struct hpsb_host_driver { + struct module *owner; + const char *name; + + /* The hardware driver may optionally support a function that is used + * to set the hardware ConfigROM if the hardware supports handling + * reads to the ConfigROM on its own. */ + void (*set_hw_config_rom)(struct hpsb_host *host, + quadlet_t *config_rom); + + /* This function shall implement packet transmission based on + * packet->type. It shall CRC both parts of the packet (unless + * packet->type == raw) and do byte-swapping as necessary or instruct + * the hardware to do so. It can return immediately after the packet + * was queued for sending. After sending, hpsb_sent_packet() has to be + * called. Return 0 on success, negative errno on failure. + * NOTE: The function must be callable in interrupt context. + */ + int (*transmit_packet)(struct hpsb_host *host, + struct hpsb_packet *packet); + + /* This function requests miscellanous services from the driver, see + * above for command codes and expected actions. Return -1 for unknown + * command, though that should never happen. + */ + int (*devctl)(struct hpsb_host *host, enum devctl_cmd command, int arg); + + /* ISO transmission/reception functions. Return 0 on success, -1 + * (or -EXXX errno code) on failure. If the low-level driver does not + * support the new ISO API, set isoctl to NULL. + */ + int (*isoctl)(struct hpsb_iso *iso, enum isoctl_cmd command, + unsigned long arg); + + /* This function is mainly to redirect local CSR reads/locks to the iso + * management registers (bus manager id, bandwidth available, channels + * available) to the hardware registers in OHCI. reg is 0,1,2,3 for bus + * mgr, bwdth avail, ch avail hi, ch avail lo respectively (the same ids + * as OHCI uses). data and compare are the new data and expected data + * respectively, return value is the old value. + */ + quadlet_t (*hw_csr_reg) (struct hpsb_host *host, int reg, + quadlet_t data, quadlet_t compare); +}; + +struct hpsb_host *hpsb_alloc_host(struct hpsb_host_driver *drv, size_t extra, + struct device *dev); +int hpsb_add_host(struct hpsb_host *host); +void hpsb_resume_host(struct hpsb_host *host); +void hpsb_remove_host(struct hpsb_host *host); +int hpsb_update_config_rom_image(struct hpsb_host *host); + +#endif /* _IEEE1394_HOSTS_H */ diff --git a/drivers/ieee1394/ieee1394-ioctl.h b/drivers/ieee1394/ieee1394-ioctl.h new file mode 100644 index 0000000..46878fe --- /dev/null +++ b/drivers/ieee1394/ieee1394-ioctl.h @@ -0,0 +1,106 @@ +/* + * Base file for all ieee1394 ioctl's. + * Linux-1394 has allocated base '#' with a range of 0x00-0x3f. + */ + +#ifndef __IEEE1394_IOCTL_H +#define __IEEE1394_IOCTL_H + +#include <linux/ioctl.h> +#include <linux/types.h> + +/* DV1394 Gets 10 */ + +/* Get the driver ready to transmit video. pass a struct dv1394_init* as + * the parameter (see below), or NULL to get default parameters */ +#define DV1394_IOC_INIT _IOW('#', 0x06, struct dv1394_init) + +/* Stop transmitting video and free the ringbuffer */ +#define DV1394_IOC_SHUTDOWN _IO ('#', 0x07) + +/* Submit N new frames to be transmitted, where the index of the first new + * frame is first_clear_buffer, and the index of the last new frame is + * (first_clear_buffer + N) % n_frames */ +#define DV1394_IOC_SUBMIT_FRAMES _IO ('#', 0x08) + +/* Block until N buffers are clear (pass N as the parameter) Because we + * re-transmit the last frame on underrun, there will at most be n_frames + * - 1 clear frames at any time */ +#define DV1394_IOC_WAIT_FRAMES _IO ('#', 0x09) + +/* Capture new frames that have been received, where the index of the + * first new frame is first_clear_buffer, and the index of the last new + * frame is (first_clear_buffer + N) % n_frames */ +#define DV1394_IOC_RECEIVE_FRAMES _IO ('#', 0x0a) + +/* Tell card to start receiving DMA */ +#define DV1394_IOC_START_RECEIVE _IO ('#', 0x0b) + +/* Pass a struct dv1394_status* as the parameter */ +#define DV1394_IOC_GET_STATUS _IOR('#', 0x0c, struct dv1394_status) + + +/* Video1394 Gets 10 */ + +#define VIDEO1394_IOC_LISTEN_CHANNEL \ + _IOWR('#', 0x10, struct video1394_mmap) +#define VIDEO1394_IOC_UNLISTEN_CHANNEL \ + _IOW ('#', 0x11, int) +#define VIDEO1394_IOC_LISTEN_QUEUE_BUFFER \ + _IOW ('#', 0x12, struct video1394_wait) +#define VIDEO1394_IOC_LISTEN_WAIT_BUFFER \ + _IOWR('#', 0x13, struct video1394_wait) +#define VIDEO1394_IOC_TALK_CHANNEL \ + _IOWR('#', 0x14, struct video1394_mmap) +#define VIDEO1394_IOC_UNTALK_CHANNEL \ + _IOW ('#', 0x15, int) +/* + * This one is broken: it really wanted + * "sizeof (struct video1394_wait) + sizeof (struct video1394_queue_variable)" + * but got just a "size_t" + */ +#define VIDEO1394_IOC_TALK_QUEUE_BUFFER \ + _IOW ('#', 0x16, size_t) +#define VIDEO1394_IOC_TALK_WAIT_BUFFER \ + _IOW ('#', 0x17, struct video1394_wait) +#define VIDEO1394_IOC_LISTEN_POLL_BUFFER \ + _IOWR('#', 0x18, struct video1394_wait) + + +/* Raw1394's ISO interface */ +#define RAW1394_IOC_ISO_XMIT_INIT \ + _IOW ('#', 0x1a, struct raw1394_iso_status) +#define RAW1394_IOC_ISO_RECV_INIT \ + _IOWR('#', 0x1b, struct raw1394_iso_status) +#define RAW1394_IOC_ISO_RECV_START \ + _IOC (_IOC_WRITE, '#', 0x1c, sizeof(int) * 3) +#define RAW1394_IOC_ISO_XMIT_START \ + _IOC (_IOC_WRITE, '#', 0x1d, sizeof(int) * 2) +#define RAW1394_IOC_ISO_XMIT_RECV_STOP \ + _IO ('#', 0x1e) +#define RAW1394_IOC_ISO_GET_STATUS \ + _IOR ('#', 0x1f, struct raw1394_iso_status) +#define RAW1394_IOC_ISO_SHUTDOWN \ + _IO ('#', 0x20) +#define RAW1394_IOC_ISO_QUEUE_ACTIVITY \ + _IO ('#', 0x21) +#define RAW1394_IOC_ISO_RECV_LISTEN_CHANNEL \ + _IOW ('#', 0x22, unsigned char) +#define RAW1394_IOC_ISO_RECV_UNLISTEN_CHANNEL \ + _IOW ('#', 0x23, unsigned char) +#define RAW1394_IOC_ISO_RECV_SET_CHANNEL_MASK \ + _IOW ('#', 0x24, __u64) +#define RAW1394_IOC_ISO_RECV_PACKETS \ + _IOW ('#', 0x25, struct raw1394_iso_packets) +#define RAW1394_IOC_ISO_RECV_RELEASE_PACKETS \ + _IOW ('#', 0x26, unsigned int) +#define RAW1394_IOC_ISO_XMIT_PACKETS \ + _IOW ('#', 0x27, struct raw1394_iso_packets) +#define RAW1394_IOC_ISO_XMIT_SYNC \ + _IO ('#', 0x28) +#define RAW1394_IOC_ISO_RECV_FLUSH \ + _IO ('#', 0x29) +#define RAW1394_IOC_GET_CYCLE_TIMER \ + _IOR ('#', 0x30, struct raw1394_cycle_timer) + +#endif /* __IEEE1394_IOCTL_H */ diff --git a/drivers/ieee1394/ieee1394.h b/drivers/ieee1394/ieee1394.h new file mode 100644 index 0000000..4049207 --- /dev/null +++ b/drivers/ieee1394/ieee1394.h @@ -0,0 +1,219 @@ +/* + * Generic IEEE 1394 definitions + */ + +#ifndef _IEEE1394_IEEE1394_H +#define _IEEE1394_IEEE1394_H + +#define TCODE_WRITEQ 0x0 +#define TCODE_WRITEB 0x1 +#define TCODE_WRITE_RESPONSE 0x2 +#define TCODE_READQ 0x4 +#define TCODE_READB 0x5 +#define TCODE_READQ_RESPONSE 0x6 +#define TCODE_READB_RESPONSE 0x7 +#define TCODE_CYCLE_START 0x8 +#define TCODE_LOCK_REQUEST 0x9 +#define TCODE_ISO_DATA 0xa +#define TCODE_STREAM_DATA 0xa +#define TCODE_LOCK_RESPONSE 0xb + +#define RCODE_COMPLETE 0x0 +#define RCODE_CONFLICT_ERROR 0x4 +#define RCODE_DATA_ERROR 0x5 +#define RCODE_TYPE_ERROR 0x6 +#define RCODE_ADDRESS_ERROR 0x7 + +#define EXTCODE_MASK_SWAP 0x1 +#define EXTCODE_COMPARE_SWAP 0x2 +#define EXTCODE_FETCH_ADD 0x3 +#define EXTCODE_LITTLE_ADD 0x4 +#define EXTCODE_BOUNDED_ADD 0x5 +#define EXTCODE_WRAP_ADD 0x6 + +#define ACK_COMPLETE 0x1 +#define ACK_PENDING 0x2 +#define ACK_BUSY_X 0x4 +#define ACK_BUSY_A 0x5 +#define ACK_BUSY_B 0x6 +#define ACK_TARDY 0xb +#define ACK_CONFLICT_ERROR 0xc +#define ACK_DATA_ERROR 0xd +#define ACK_TYPE_ERROR 0xe +#define ACK_ADDRESS_ERROR 0xf + +/* Non-standard "ACK codes" for internal use */ +#define ACKX_NONE (-1) +#define ACKX_SEND_ERROR (-2) +#define ACKX_ABORTED (-3) +#define ACKX_TIMEOUT (-4) + +#define IEEE1394_SPEED_100 0x00 +#define IEEE1394_SPEED_200 0x01 +#define IEEE1394_SPEED_400 0x02 +#define IEEE1394_SPEED_800 0x03 +#define IEEE1394_SPEED_1600 0x04 +#define IEEE1394_SPEED_3200 0x05 + +/* The current highest tested speed supported by the subsystem */ +#define IEEE1394_SPEED_MAX IEEE1394_SPEED_800 + +/* Maps speed values above to a string representation */ +extern const char *hpsb_speedto_str[]; + +/* 1394a cable PHY packets */ +#define SELFID_PWRCL_NO_POWER 0x0 +#define SELFID_PWRCL_PROVIDE_15W 0x1 +#define SELFID_PWRCL_PROVIDE_30W 0x2 +#define SELFID_PWRCL_PROVIDE_45W 0x3 +#define SELFID_PWRCL_USE_1W 0x4 +#define SELFID_PWRCL_USE_3W 0x5 +#define SELFID_PWRCL_USE_6W 0x6 +#define SELFID_PWRCL_USE_10W 0x7 + +#define SELFID_PORT_CHILD 0x3 +#define SELFID_PORT_PARENT 0x2 +#define SELFID_PORT_NCONN 0x1 +#define SELFID_PORT_NONE 0x0 + +#define SELFID_SPEED_UNKNOWN 0x3 /* 1394b PHY */ + +#define PHYPACKET_LINKON 0x40000000 +#define PHYPACKET_PHYCONFIG_R 0x00800000 +#define PHYPACKET_PHYCONFIG_T 0x00400000 +#define EXTPHYPACKET_TYPE_PING 0x00000000 +#define EXTPHYPACKET_TYPE_REMOTEACCESS_BASE 0x00040000 +#define EXTPHYPACKET_TYPE_REMOTEACCESS_PAGED 0x00140000 +#define EXTPHYPACKET_TYPE_REMOTEREPLY_BASE 0x000C0000 +#define EXTPHYPACKET_TYPE_REMOTEREPLY_PAGED 0x001C0000 +#define EXTPHYPACKET_TYPE_REMOTECOMMAND 0x00200000 +#define EXTPHYPACKET_TYPE_REMOTECONFIRMATION 0x00280000 +#define EXTPHYPACKET_TYPE_RESUME 0x003C0000 + +#define EXTPHYPACKET_TYPEMASK 0xC0FC0000 + +#define PHYPACKET_PORT_SHIFT 24 +#define PHYPACKET_GAPCOUNT_SHIFT 16 + +/* 1394a PHY register map bitmasks */ +#define PHY_00_PHYSICAL_ID 0xFC +#define PHY_00_R 0x02 /* Root */ +#define PHY_00_PS 0x01 /* Power Status*/ +#define PHY_01_RHB 0x80 /* Root Hold-Off */ +#define PHY_01_IBR 0x80 /* Initiate Bus Reset */ +#define PHY_01_GAP_COUNT 0x3F +#define PHY_02_EXTENDED 0xE0 /* 0x7 for 1394a-compliant PHY */ +#define PHY_02_TOTAL_PORTS 0x1F +#define PHY_03_MAX_SPEED 0xE0 +#define PHY_03_DELAY 0x0F +#define PHY_04_LCTRL 0x80 /* Link Active Report Control */ +#define PHY_04_CONTENDER 0x40 +#define PHY_04_JITTER 0x38 +#define PHY_04_PWR_CLASS 0x07 /* Power Class */ +#define PHY_05_WATCHDOG 0x80 +#define PHY_05_ISBR 0x40 /* Initiate Short Bus Reset */ +#define PHY_05_LOOP 0x20 /* Loop Detect */ +#define PHY_05_PWR_FAIL 0x10 /* Cable Power Failure Detect */ +#define PHY_05_TIMEOUT 0x08 /* Arbitration State Machine Timeout */ +#define PHY_05_PORT_EVENT 0x04 /* Port Event Detect */ +#define PHY_05_ENAB_ACCEL 0x02 /* Enable Arbitration Acceleration */ +#define PHY_05_ENAB_MULTI 0x01 /* Ena. Multispeed Packet Concatenation */ + +#include <asm/byteorder.h> + +#ifdef __BIG_ENDIAN_BITFIELD + +struct selfid { + u32 packet_identifier:2; /* always binary 10 */ + u32 phy_id:6; + /* byte */ + u32 extended:1; /* if true is struct ext_selfid */ + u32 link_active:1; + u32 gap_count:6; + /* byte */ + u32 speed:2; + u32 phy_delay:2; + u32 contender:1; + u32 power_class:3; + /* byte */ + u32 port0:2; + u32 port1:2; + u32 port2:2; + u32 initiated_reset:1; + u32 more_packets:1; +} __attribute__((packed)); + +struct ext_selfid { + u32 packet_identifier:2; /* always binary 10 */ + u32 phy_id:6; + /* byte */ + u32 extended:1; /* if false is struct selfid */ + u32 seq_nr:3; + u32 reserved:2; + u32 porta:2; + /* byte */ + u32 portb:2; + u32 portc:2; + u32 portd:2; + u32 porte:2; + /* byte */ + u32 portf:2; + u32 portg:2; + u32 porth:2; + u32 reserved2:1; + u32 more_packets:1; +} __attribute__((packed)); + +#elif defined __LITTLE_ENDIAN_BITFIELD /* __BIG_ENDIAN_BITFIELD */ + +/* + * Note: these mean to be bit fields of a big endian SelfID as seen on a little + * endian machine. Without swapping. + */ + +struct selfid { + u32 phy_id:6; + u32 packet_identifier:2; /* always binary 10 */ + /* byte */ + u32 gap_count:6; + u32 link_active:1; + u32 extended:1; /* if true is struct ext_selfid */ + /* byte */ + u32 power_class:3; + u32 contender:1; + u32 phy_delay:2; + u32 speed:2; + /* byte */ + u32 more_packets:1; + u32 initiated_reset:1; + u32 port2:2; + u32 port1:2; + u32 port0:2; +} __attribute__((packed)); + +struct ext_selfid { + u32 phy_id:6; + u32 packet_identifier:2; /* always binary 10 */ + /* byte */ + u32 porta:2; + u32 reserved:2; + u32 seq_nr:3; + u32 extended:1; /* if false is struct selfid */ + /* byte */ + u32 porte:2; + u32 portd:2; + u32 portc:2; + u32 portb:2; + /* byte */ + u32 more_packets:1; + u32 reserved2:1; + u32 porth:2; + u32 portg:2; + u32 portf:2; +} __attribute__((packed)); + +#else +#error What? PDP endian? +#endif /* __BIG_ENDIAN_BITFIELD */ + +#endif /* _IEEE1394_IEEE1394_H */ diff --git a/drivers/ieee1394/ieee1394_core.c b/drivers/ieee1394/ieee1394_core.c new file mode 100644 index 0000000..dcdb71a --- /dev/null +++ b/drivers/ieee1394/ieee1394_core.c @@ -0,0 +1,1379 @@ +/* + * IEEE 1394 for Linux + * + * Core support: hpsb_packet management, packet handling and forwarding to + * highlevel or lowlevel code + * + * Copyright (C) 1999, 2000 Andreas E. Bombe + * 2002 Manfred Weihs <weihs@ict.tuwien.ac.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> + * loopback functionality in hpsb_send_packet + * allow highlevel drivers to disable automatic response generation + * and to generate responses themselves (deferred) + * + */ + +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/bitops.h> +#include <linux/kdev_t.h> +#include <linux/freezer.h> +#include <linux/suspend.h> +#include <linux/kthread.h> +#include <linux/preempt.h> +#include <linux/time.h> + +#include <asm/system.h> +#include <asm/byteorder.h> + +#include "ieee1394_types.h" +#include "ieee1394.h" +#include "hosts.h" +#include "ieee1394_core.h" +#include "highlevel.h" +#include "ieee1394_transactions.h" +#include "csr.h" +#include "nodemgr.h" +#include "dma.h" +#include "iso.h" +#include "config_roms.h" + +/* + * Disable the nodemgr detection and config rom reading functionality. + */ +static int disable_nodemgr; +module_param(disable_nodemgr, int, 0444); +MODULE_PARM_DESC(disable_nodemgr, "Disable nodemgr functionality."); + +/* Disable Isochronous Resource Manager functionality */ +int hpsb_disable_irm = 0; +module_param_named(disable_irm, hpsb_disable_irm, bool, 0444); +MODULE_PARM_DESC(disable_irm, + "Disable Isochronous Resource Manager functionality."); + +/* We are GPL, so treat us special */ +MODULE_LICENSE("GPL"); + +/* Some globals used */ +const char *hpsb_speedto_str[] = { "S100", "S200", "S400", "S800", "S1600", "S3200" }; +struct class *hpsb_protocol_class; + +#ifdef CONFIG_IEEE1394_VERBOSEDEBUG +static void dump_packet(const char *text, quadlet_t *data, int size, int speed) +{ + int i; + + size /= 4; + size = (size > 4 ? 4 : size); + + printk(KERN_DEBUG "ieee1394: %s", text); + if (speed > -1 && speed < 6) + printk(" at %s", hpsb_speedto_str[speed]); + printk(":"); + for (i = 0; i < size; i++) + printk(" %08x", data[i]); + printk("\n"); +} +#else +#define dump_packet(a,b,c,d) do {} while (0) +#endif + +static void abort_requests(struct hpsb_host *host); +static void queue_packet_complete(struct hpsb_packet *packet); + + +/** + * hpsb_set_packet_complete_task - set task that runs when a packet completes + * @packet: the packet whose completion we want the task added to + * @routine: function to call + * @data: data (if any) to pass to the above function + * + * Set the task that runs when a packet completes. You cannot call this more + * than once on a single packet before it is sent. + * + * Typically, the complete @routine is responsible to call hpsb_free_packet(). + */ +void hpsb_set_packet_complete_task(struct hpsb_packet *packet, + void (*routine)(void *), void *data) +{ + WARN_ON(packet->complete_routine != NULL); + packet->complete_routine = routine; + packet->complete_data = data; + return; +} + +/** + * hpsb_alloc_packet - allocate new packet structure + * @data_size: size of the data block to be allocated, in bytes + * + * This function allocates, initializes and returns a new &struct hpsb_packet. + * It can be used in interrupt context. A header block is always included and + * initialized with zeros. Its size is big enough to contain all possible 1394 + * headers. The data block is only allocated if @data_size is not zero. + * + * For packets for which responses will be received the @data_size has to be big + * enough to contain the response's data block since no further allocation + * occurs at response matching time. + * + * The packet's generation value will be set to the current generation number + * for ease of use. Remember to overwrite it with your own recorded generation + * number if you can not be sure that your code will not race with a bus reset. + * + * Return value: A pointer to a &struct hpsb_packet or NULL on allocation + * failure. + */ +struct hpsb_packet *hpsb_alloc_packet(size_t data_size) +{ + struct hpsb_packet *packet; + + data_size = ((data_size + 3) & ~3); + + packet = kzalloc(sizeof(*packet) + data_size, GFP_ATOMIC); + if (!packet) + return NULL; + + packet->state = hpsb_unused; + packet->generation = -1; + INIT_LIST_HEAD(&packet->driver_list); + INIT_LIST_HEAD(&packet->queue); + atomic_set(&packet->refcnt, 1); + + if (data_size) { + packet->data = packet->embedded_data; + packet->allocated_data_size = data_size; + } + return packet; +} + +/** + * hpsb_free_packet - free packet and data associated with it + * @packet: packet to free (is NULL safe) + * + * Frees @packet->data only if it was allocated through hpsb_alloc_packet(). + */ +void hpsb_free_packet(struct hpsb_packet *packet) +{ + if (packet && atomic_dec_and_test(&packet->refcnt)) { + BUG_ON(!list_empty(&packet->driver_list) || + !list_empty(&packet->queue)); + kfree(packet); + } +} + +/** + * hpsb_reset_bus - initiate bus reset on the given host + * @host: host controller whose bus to reset + * @type: one of enum reset_types + * + * Returns 1 if bus reset already in progress, 0 otherwise. + */ +int hpsb_reset_bus(struct hpsb_host *host, int type) +{ + if (!host->in_bus_reset) { + host->driver->devctl(host, RESET_BUS, type); + return 0; + } else { + return 1; + } +} + +/** + * hpsb_read_cycle_timer - read cycle timer register and system time + * @host: host whose isochronous cycle timer register is read + * @cycle_timer: address of bitfield to return the register contents + * @local_time: address to return the system time + * + * The format of * @cycle_timer, is described in OHCI 1.1 clause 5.13. This + * format is also read from non-OHCI controllers. * @local_time contains the + * system time in microseconds since the Epoch, read at the moment when the + * cycle timer was read. + * + * Return value: 0 for success or error number otherwise. + */ +int hpsb_read_cycle_timer(struct hpsb_host *host, u32 *cycle_timer, + u64 *local_time) +{ + int ctr; + struct timeval tv; + unsigned long flags; + + if (!host || !cycle_timer || !local_time) + return -EINVAL; + + preempt_disable(); + local_irq_save(flags); + + ctr = host->driver->devctl(host, GET_CYCLE_COUNTER, 0); + if (ctr) + do_gettimeofday(&tv); + + local_irq_restore(flags); + preempt_enable(); + + if (!ctr) + return -EIO; + *cycle_timer = ctr; + *local_time = tv.tv_sec * 1000000ULL + tv.tv_usec; + return 0; +} + +/** + * hpsb_bus_reset - notify a bus reset to the core + * + * For host driver module usage. Safe to use in interrupt context, although + * quite complex; so you may want to run it in the bottom rather than top half. + * + * Returns 1 if bus reset already in progress, 0 otherwise. + */ +int hpsb_bus_reset(struct hpsb_host *host) +{ + if (host->in_bus_reset) { + HPSB_NOTICE("%s called while bus reset already in progress", + __func__); + return 1; + } + + abort_requests(host); + host->in_bus_reset = 1; + host->irm_id = -1; + host->is_irm = 0; + host->busmgr_id = -1; + host->is_busmgr = 0; + host->is_cycmst = 0; + host->node_count = 0; + host->selfid_count = 0; + + return 0; +} + + +/* + * Verify num_of_selfids SelfIDs and return number of nodes. Return zero in + * case verification failed. + */ +static int check_selfids(struct hpsb_host *host) +{ + int nodeid = -1; + int rest_of_selfids = host->selfid_count; + struct selfid *sid = (struct selfid *)host->topology_map; + struct ext_selfid *esid; + int esid_seq = 23; + + host->nodes_active = 0; + + while (rest_of_selfids--) { + if (!sid->extended) { + nodeid++; + esid_seq = 0; + + if (sid->phy_id != nodeid) { + HPSB_INFO("SelfIDs failed monotony check with " + "%d", sid->phy_id); + return 0; + } + + if (sid->link_active) { + host->nodes_active++; + if (sid->contender) + host->irm_id = LOCAL_BUS | sid->phy_id; + } + } else { + esid = (struct ext_selfid *)sid; + + if ((esid->phy_id != nodeid) + || (esid->seq_nr != esid_seq)) { + HPSB_INFO("SelfIDs failed monotony check with " + "%d/%d", esid->phy_id, esid->seq_nr); + return 0; + } + esid_seq++; + } + sid++; + } + + esid = (struct ext_selfid *)(sid - 1); + while (esid->extended) { + if ((esid->porta == SELFID_PORT_PARENT) || + (esid->portb == SELFID_PORT_PARENT) || + (esid->portc == SELFID_PORT_PARENT) || + (esid->portd == SELFID_PORT_PARENT) || + (esid->porte == SELFID_PORT_PARENT) || + (esid->portf == SELFID_PORT_PARENT) || + (esid->portg == SELFID_PORT_PARENT) || + (esid->porth == SELFID_PORT_PARENT)) { + HPSB_INFO("SelfIDs failed root check on " + "extended SelfID"); + return 0; + } + esid--; + } + + sid = (struct selfid *)esid; + if ((sid->port0 == SELFID_PORT_PARENT) || + (sid->port1 == SELFID_PORT_PARENT) || + (sid->port2 == SELFID_PORT_PARENT)) { + HPSB_INFO("SelfIDs failed root check"); + return 0; + } + + host->node_count = nodeid + 1; + return 1; +} + +static void build_speed_map(struct hpsb_host *host, int nodecount) +{ + u8 cldcnt[nodecount]; + u8 *map = host->speed_map; + u8 *speedcap = host->speed; + struct selfid *sid; + struct ext_selfid *esid; + int i, j, n; + + for (i = 0; i < (nodecount * 64); i += 64) { + for (j = 0; j < nodecount; j++) { + map[i+j] = IEEE1394_SPEED_MAX; + } + } + + for (i = 0; i < nodecount; i++) { + cldcnt[i] = 0; + } + + /* find direct children count and speed */ + for (sid = (struct selfid *)&host->topology_map[host->selfid_count-1], + n = nodecount - 1; + (void *)sid >= (void *)host->topology_map; sid--) { + if (sid->extended) { + esid = (struct ext_selfid *)sid; + + if (esid->porta == SELFID_PORT_CHILD) cldcnt[n]++; + if (esid->portb == SELFID_PORT_CHILD) cldcnt[n]++; + if (esid->portc == SELFID_PORT_CHILD) cldcnt[n]++; + if (esid->portd == SELFID_PORT_CHILD) cldcnt[n]++; + if (esid->porte == SELFID_PORT_CHILD) cldcnt[n]++; + if (esid->portf == SELFID_PORT_CHILD) cldcnt[n]++; + if (esid->portg == SELFID_PORT_CHILD) cldcnt[n]++; + if (esid->porth == SELFID_PORT_CHILD) cldcnt[n]++; + } else { + if (sid->port0 == SELFID_PORT_CHILD) cldcnt[n]++; + if (sid->port1 == SELFID_PORT_CHILD) cldcnt[n]++; + if (sid->port2 == SELFID_PORT_CHILD) cldcnt[n]++; + + speedcap[n] = sid->speed; + if (speedcap[n] > host->csr.lnk_spd) + speedcap[n] = host->csr.lnk_spd; + n--; + } + } + + /* set self mapping */ + for (i = 0; i < nodecount; i++) { + map[64*i + i] = speedcap[i]; + } + + /* fix up direct children count to total children count; + * also fix up speedcaps for sibling and parent communication */ + for (i = 1; i < nodecount; i++) { + for (j = cldcnt[i], n = i - 1; j > 0; j--) { + cldcnt[i] += cldcnt[n]; + speedcap[n] = min(speedcap[n], speedcap[i]); + n -= cldcnt[n] + 1; + } + } + + for (n = 0; n < nodecount; n++) { + for (i = n - cldcnt[n]; i <= n; i++) { + for (j = 0; j < (n - cldcnt[n]); j++) { + map[j*64 + i] = map[i*64 + j] = + min(map[i*64 + j], speedcap[n]); + } + for (j = n + 1; j < nodecount; j++) { + map[j*64 + i] = map[i*64 + j] = + min(map[i*64 + j], speedcap[n]); + } + } + } + +#if SELFID_SPEED_UNKNOWN != IEEE1394_SPEED_MAX + /* assume maximum speed for 1394b PHYs, nodemgr will correct it */ + for (n = 0; n < nodecount; n++) + if (speedcap[n] == SELFID_SPEED_UNKNOWN) + speedcap[n] = IEEE1394_SPEED_MAX; +#endif +} + + +/** + * hpsb_selfid_received - hand over received selfid packet to the core + * + * For host driver module usage. Safe to use in interrupt context. + * + * The host driver should have done a successful complement check (second + * quadlet is complement of first) beforehand. + */ +void hpsb_selfid_received(struct hpsb_host *host, quadlet_t sid) +{ + if (host->in_bus_reset) { + HPSB_VERBOSE("Including SelfID 0x%x", sid); + host->topology_map[host->selfid_count++] = sid; + } else { + HPSB_NOTICE("Spurious SelfID packet (0x%08x) received from bus %d", + sid, NODEID_TO_BUS(host->node_id)); + } +} + +/** + * hpsb_selfid_complete - notify completion of SelfID stage to the core + * + * For host driver module usage. Safe to use in interrupt context, although + * quite complex; so you may want to run it in the bottom rather than top half. + * + * Notify completion of SelfID stage to the core and report new physical ID + * and whether host is root now. + */ +void hpsb_selfid_complete(struct hpsb_host *host, int phyid, int isroot) +{ + if (!host->in_bus_reset) + HPSB_NOTICE("SelfID completion called outside of bus reset!"); + + host->node_id = LOCAL_BUS | phyid; + host->is_root = isroot; + + if (!check_selfids(host)) { + if (host->reset_retries++ < 20) { + /* selfid stage did not complete without error */ + HPSB_NOTICE("Error in SelfID stage, resetting"); + host->in_bus_reset = 0; + /* this should work from ohci1394 now... */ + hpsb_reset_bus(host, LONG_RESET); + return; + } else { + HPSB_NOTICE("Stopping out-of-control reset loop"); + HPSB_NOTICE("Warning - topology map and speed map will not be valid"); + host->reset_retries = 0; + } + } else { + host->reset_retries = 0; + build_speed_map(host, host->node_count); + } + + HPSB_VERBOSE("selfid_complete called with successful SelfID stage " + "... irm_id: 0x%X node_id: 0x%X",host->irm_id,host->node_id); + + /* irm_id is kept up to date by check_selfids() */ + if (host->irm_id == host->node_id) { + host->is_irm = 1; + } else { + host->is_busmgr = 0; + host->is_irm = 0; + } + + if (isroot) { + host->driver->devctl(host, ACT_CYCLE_MASTER, 1); + host->is_cycmst = 1; + } + atomic_inc(&host->generation); + host->in_bus_reset = 0; + highlevel_host_reset(host); +} + +static DEFINE_SPINLOCK(pending_packets_lock); + +/** + * hpsb_packet_sent - notify core of sending a packet + * + * For host driver module usage. Safe to call from within a transmit packet + * routine. + * + * Notify core of sending a packet. Ackcode is the ack code returned for async + * transmits or ACKX_SEND_ERROR if the transmission failed completely; ACKX_NONE + * for other cases (internal errors that don't justify a panic). + */ +void hpsb_packet_sent(struct hpsb_host *host, struct hpsb_packet *packet, + int ackcode) +{ + unsigned long flags; + + spin_lock_irqsave(&pending_packets_lock, flags); + + packet->ack_code = ackcode; + + if (packet->no_waiter || packet->state == hpsb_complete) { + /* if packet->no_waiter, must not have a tlabel allocated */ + spin_unlock_irqrestore(&pending_packets_lock, flags); + hpsb_free_packet(packet); + return; + } + + atomic_dec(&packet->refcnt); /* drop HC's reference */ + /* here the packet must be on the host->pending_packets queue */ + + if (ackcode != ACK_PENDING || !packet->expect_response) { + packet->state = hpsb_complete; + list_del_init(&packet->queue); + spin_unlock_irqrestore(&pending_packets_lock, flags); + queue_packet_complete(packet); + return; + } + + packet->state = hpsb_pending; + packet->sendtime = jiffies; + + spin_unlock_irqrestore(&pending_packets_lock, flags); + + mod_timer(&host->timeout, jiffies + host->timeout_interval); +} + +/** + * hpsb_send_phy_config - transmit a PHY configuration packet on the bus + * @host: host that PHY config packet gets sent through + * @rootid: root whose force_root bit should get set (-1 = don't set force_root) + * @gapcnt: gap count value to set (-1 = don't set gap count) + * + * This function sends a PHY config packet on the bus through the specified + * host. + * + * Return value: 0 for success or negative error number otherwise. + */ +int hpsb_send_phy_config(struct hpsb_host *host, int rootid, int gapcnt) +{ + struct hpsb_packet *packet; + quadlet_t d = 0; + int retval = 0; + + if (rootid >= ALL_NODES || rootid < -1 || gapcnt > 0x3f || gapcnt < -1 || + (rootid == -1 && gapcnt == -1)) { + HPSB_DEBUG("Invalid Parameter: rootid = %d gapcnt = %d", + rootid, gapcnt); + return -EINVAL; + } + + if (rootid != -1) + d |= PHYPACKET_PHYCONFIG_R | rootid << PHYPACKET_PORT_SHIFT; + if (gapcnt != -1) + d |= PHYPACKET_PHYCONFIG_T | gapcnt << PHYPACKET_GAPCOUNT_SHIFT; + + packet = hpsb_make_phypacket(host, d); + if (!packet) + return -ENOMEM; + + packet->generation = get_hpsb_generation(host); + retval = hpsb_send_packet_and_wait(packet); + hpsb_free_packet(packet); + + return retval; +} + +/** + * hpsb_send_packet - transmit a packet on the bus + * @packet: packet to send + * + * The packet is sent through the host specified in the packet->host field. + * Before sending, the packet's transmit speed is automatically determined + * using the local speed map when it is an async, non-broadcast packet. + * + * Possibilities for failure are that host is either not initialized, in bus + * reset, the packet's generation number doesn't match the current generation + * number or the host reports a transmit error. + * + * Return value: 0 on success, negative errno on failure. + */ +int hpsb_send_packet(struct hpsb_packet *packet) +{ + struct hpsb_host *host = packet->host; + + if (host->is_shutdown) + return -EINVAL; + if (host->in_bus_reset || + (packet->generation != get_hpsb_generation(host))) + return -EAGAIN; + + packet->state = hpsb_queued; + + /* This just seems silly to me */ + WARN_ON(packet->no_waiter && packet->expect_response); + + if (!packet->no_waiter || packet->expect_response) { + unsigned long flags; + + atomic_inc(&packet->refcnt); + /* Set the initial "sendtime" to 10 seconds from now, to + prevent premature expiry. If a packet takes more than + 10 seconds to hit the wire, we have bigger problems :) */ + packet->sendtime = jiffies + 10 * HZ; + spin_lock_irqsave(&pending_packets_lock, flags); + list_add_tail(&packet->queue, &host->pending_packets); + spin_unlock_irqrestore(&pending_packets_lock, flags); + } + + if (packet->node_id == host->node_id) { + /* it is a local request, so handle it locally */ + + quadlet_t *data; + size_t size = packet->data_size + packet->header_size; + + data = kmalloc(size, GFP_ATOMIC); + if (!data) { + HPSB_ERR("unable to allocate memory for concatenating header and data"); + return -ENOMEM; + } + + memcpy(data, packet->header, packet->header_size); + + if (packet->data_size) + memcpy(((u8*)data) + packet->header_size, packet->data, packet->data_size); + + dump_packet("send packet local", packet->header, packet->header_size, -1); + + hpsb_packet_sent(host, packet, packet->expect_response ? ACK_PENDING : ACK_COMPLETE); + hpsb_packet_received(host, data, size, 0); + + kfree(data); + + return 0; + } + + if (packet->type == hpsb_async && + NODEID_TO_NODE(packet->node_id) != ALL_NODES) + packet->speed_code = + host->speed[NODEID_TO_NODE(packet->node_id)]; + + dump_packet("send packet", packet->header, packet->header_size, packet->speed_code); + + return host->driver->transmit_packet(host, packet); +} + +/* We could just use complete() directly as the packet complete + * callback, but this is more typesafe, in the sense that we get a + * compiler error if the prototype for complete() changes. */ + +static void complete_packet(void *data) +{ + complete((struct completion *) data); +} + +/** + * hpsb_send_packet_and_wait - enqueue packet, block until transaction completes + * @packet: packet to send + * + * Return value: 0 on success, negative errno on failure. + */ +int hpsb_send_packet_and_wait(struct hpsb_packet *packet) +{ + struct completion done; + int retval; + + init_completion(&done); + hpsb_set_packet_complete_task(packet, complete_packet, &done); + retval = hpsb_send_packet(packet); + if (retval == 0) + wait_for_completion(&done); + + return retval; +} + +static void send_packet_nocare(struct hpsb_packet *packet) +{ + if (hpsb_send_packet(packet) < 0) { + hpsb_free_packet(packet); + } +} + +static size_t packet_size_to_data_size(size_t packet_size, size_t header_size, + size_t buffer_size, int tcode) +{ + size_t ret = packet_size <= header_size ? 0 : packet_size - header_size; + + if (unlikely(ret > buffer_size)) + ret = buffer_size; + + if (unlikely(ret + header_size != packet_size)) + HPSB_ERR("unexpected packet size %zd (tcode %d), bug?", + packet_size, tcode); + return ret; +} + +static void handle_packet_response(struct hpsb_host *host, int tcode, + quadlet_t *data, size_t size) +{ + struct hpsb_packet *packet; + int tlabel = (data[0] >> 10) & 0x3f; + size_t header_size; + unsigned long flags; + + spin_lock_irqsave(&pending_packets_lock, flags); + + list_for_each_entry(packet, &host->pending_packets, queue) + if (packet->tlabel == tlabel && + packet->node_id == (data[1] >> 16)) + goto found; + + spin_unlock_irqrestore(&pending_packets_lock, flags); + HPSB_DEBUG("unsolicited response packet received - %s", + "no tlabel match"); + dump_packet("contents", data, 16, -1); + return; + +found: + switch (packet->tcode) { + case TCODE_WRITEQ: + case TCODE_WRITEB: + if (unlikely(tcode != TCODE_WRITE_RESPONSE)) + break; + header_size = 12; + size = 0; + goto dequeue; + + case TCODE_READQ: + if (unlikely(tcode != TCODE_READQ_RESPONSE)) + break; + header_size = 16; + size = 0; + goto dequeue; + + case TCODE_READB: + if (unlikely(tcode != TCODE_READB_RESPONSE)) + break; + header_size = 16; + size = packet_size_to_data_size(size, header_size, + packet->allocated_data_size, + tcode); + goto dequeue; + + case TCODE_LOCK_REQUEST: + if (unlikely(tcode != TCODE_LOCK_RESPONSE)) + break; + header_size = 16; + size = packet_size_to_data_size(min(size, (size_t)(16 + 8)), + header_size, + packet->allocated_data_size, + tcode); + goto dequeue; + } + + spin_unlock_irqrestore(&pending_packets_lock, flags); + HPSB_DEBUG("unsolicited response packet received - %s", + "tcode mismatch"); + dump_packet("contents", data, 16, -1); + return; + +dequeue: + list_del_init(&packet->queue); + spin_unlock_irqrestore(&pending_packets_lock, flags); + + if (packet->state == hpsb_queued) { + packet->sendtime = jiffies; + packet->ack_code = ACK_PENDING; + } + packet->state = hpsb_complete; + + memcpy(packet->header, data, header_size); + if (size) + memcpy(packet->data, data + 4, size); + + queue_packet_complete(packet); +} + + +static struct hpsb_packet *create_reply_packet(struct hpsb_host *host, + quadlet_t *data, size_t dsize) +{ + struct hpsb_packet *p; + + p = hpsb_alloc_packet(dsize); + if (unlikely(p == NULL)) { + /* FIXME - send data_error response */ + HPSB_ERR("out of memory, cannot send response packet"); + return NULL; + } + + p->type = hpsb_async; + p->state = hpsb_unused; + p->host = host; + p->node_id = data[1] >> 16; + p->tlabel = (data[0] >> 10) & 0x3f; + p->no_waiter = 1; + + p->generation = get_hpsb_generation(host); + + if (dsize % 4) + p->data[dsize / 4] = 0; + + return p; +} + +#define PREP_ASYNC_HEAD_RCODE(tc) \ + packet->tcode = tc; \ + packet->header[0] = (packet->node_id << 16) | (packet->tlabel << 10) \ + | (1 << 8) | (tc << 4); \ + packet->header[1] = (packet->host->node_id << 16) | (rcode << 12); \ + packet->header[2] = 0 + +static void fill_async_readquad_resp(struct hpsb_packet *packet, int rcode, + quadlet_t data) +{ + PREP_ASYNC_HEAD_RCODE(TCODE_READQ_RESPONSE); + packet->header[3] = data; + packet->header_size = 16; + packet->data_size = 0; +} + +static void fill_async_readblock_resp(struct hpsb_packet *packet, int rcode, + int length) +{ + if (rcode != RCODE_COMPLETE) + length = 0; + + PREP_ASYNC_HEAD_RCODE(TCODE_READB_RESPONSE); + packet->header[3] = length << 16; + packet->header_size = 16; + packet->data_size = length + (length % 4 ? 4 - (length % 4) : 0); +} + +static void fill_async_write_resp(struct hpsb_packet *packet, int rcode) +{ + PREP_ASYNC_HEAD_RCODE(TCODE_WRITE_RESPONSE); + packet->header_size = 12; + packet->data_size = 0; +} + +static void fill_async_lock_resp(struct hpsb_packet *packet, int rcode, int extcode, + int length) +{ + if (rcode != RCODE_COMPLETE) + length = 0; + + PREP_ASYNC_HEAD_RCODE(TCODE_LOCK_RESPONSE); + packet->header[3] = (length << 16) | extcode; + packet->header_size = 16; + packet->data_size = length; +} + +static void handle_incoming_packet(struct hpsb_host *host, int tcode, + quadlet_t *data, size_t size, + int write_acked) +{ + struct hpsb_packet *packet; + int length, rcode, extcode; + quadlet_t buffer; + nodeid_t source = data[1] >> 16; + nodeid_t dest = data[0] >> 16; + u16 flags = (u16) data[0]; + u64 addr; + + /* FIXME? + * Out-of-bounds lengths are left for highlevel_read|write to cap. */ + + switch (tcode) { + case TCODE_WRITEQ: + addr = (((u64)(data[1] & 0xffff)) << 32) | data[2]; + rcode = highlevel_write(host, source, dest, data + 3, + addr, 4, flags); + goto handle_write_request; + + case TCODE_WRITEB: + addr = (((u64)(data[1] & 0xffff)) << 32) | data[2]; + rcode = highlevel_write(host, source, dest, data + 4, + addr, data[3] >> 16, flags); +handle_write_request: + if (rcode < 0 || write_acked || + NODEID_TO_NODE(data[0] >> 16) == NODE_MASK) + return; + /* not a broadcast write, reply */ + packet = create_reply_packet(host, data, 0); + if (packet) { + fill_async_write_resp(packet, rcode); + send_packet_nocare(packet); + } + return; + + case TCODE_READQ: + addr = (((u64)(data[1] & 0xffff)) << 32) | data[2]; + rcode = highlevel_read(host, source, &buffer, addr, 4, flags); + if (rcode < 0) + return; + + packet = create_reply_packet(host, data, 0); + if (packet) { + fill_async_readquad_resp(packet, rcode, buffer); + send_packet_nocare(packet); + } + return; + + case TCODE_READB: + length = data[3] >> 16; + packet = create_reply_packet(host, data, length); + if (!packet) + return; + + addr = (((u64)(data[1] & 0xffff)) << 32) | data[2]; + rcode = highlevel_read(host, source, packet->data, addr, + length, flags); + if (rcode < 0) { + hpsb_free_packet(packet); + return; + } + fill_async_readblock_resp(packet, rcode, length); + send_packet_nocare(packet); + return; + + case TCODE_LOCK_REQUEST: + length = data[3] >> 16; + extcode = data[3] & 0xffff; + addr = (((u64)(data[1] & 0xffff)) << 32) | data[2]; + + packet = create_reply_packet(host, data, 8); + if (!packet) + return; + + if (extcode == 0 || extcode >= 7) { + /* let switch default handle error */ + length = 0; + } + + switch (length) { + case 4: + rcode = highlevel_lock(host, source, packet->data, addr, + data[4], 0, extcode, flags); + fill_async_lock_resp(packet, rcode, extcode, 4); + break; + case 8: + if (extcode != EXTCODE_FETCH_ADD && + extcode != EXTCODE_LITTLE_ADD) { + rcode = highlevel_lock(host, source, + packet->data, addr, + data[5], data[4], + extcode, flags); + fill_async_lock_resp(packet, rcode, extcode, 4); + } else { + rcode = highlevel_lock64(host, source, + (octlet_t *)packet->data, addr, + *(octlet_t *)(data + 4), 0ULL, + extcode, flags); + fill_async_lock_resp(packet, rcode, extcode, 8); + } + break; + case 16: + rcode = highlevel_lock64(host, source, + (octlet_t *)packet->data, addr, + *(octlet_t *)(data + 6), + *(octlet_t *)(data + 4), + extcode, flags); + fill_async_lock_resp(packet, rcode, extcode, 8); + break; + default: + rcode = RCODE_TYPE_ERROR; + fill_async_lock_resp(packet, rcode, extcode, 0); + } + + if (rcode < 0) + hpsb_free_packet(packet); + else + send_packet_nocare(packet); + return; + } +} + +/** + * hpsb_packet_received - hand over received packet to the core + * + * For host driver module usage. + * + * The contents of data are expected to be the full packet but with the CRCs + * left out (data block follows header immediately), with the header (i.e. the + * first four quadlets) in machine byte order and the data block in big endian. + * *@data can be safely overwritten after this call. + * + * If the packet is a write request, @write_acked is to be set to true if it was + * ack_complete'd already, false otherwise. This argument is ignored for any + * other packet type. + */ +void hpsb_packet_received(struct hpsb_host *host, quadlet_t *data, size_t size, + int write_acked) +{ + int tcode; + + if (unlikely(host->in_bus_reset)) { + HPSB_DEBUG("received packet during reset; ignoring"); + return; + } + + dump_packet("received packet", data, size, -1); + + tcode = (data[0] >> 4) & 0xf; + + switch (tcode) { + case TCODE_WRITE_RESPONSE: + case TCODE_READQ_RESPONSE: + case TCODE_READB_RESPONSE: + case TCODE_LOCK_RESPONSE: + handle_packet_response(host, tcode, data, size); + break; + + case TCODE_WRITEQ: + case TCODE_WRITEB: + case TCODE_READQ: + case TCODE_READB: + case TCODE_LOCK_REQUEST: + handle_incoming_packet(host, tcode, data, size, write_acked); + break; + + case TCODE_CYCLE_START: + /* simply ignore this packet if it is passed on */ + break; + + default: + HPSB_DEBUG("received packet with bogus transaction code %d", + tcode); + break; + } +} + +static void abort_requests(struct hpsb_host *host) +{ + struct hpsb_packet *packet, *p; + struct list_head tmp; + unsigned long flags; + + host->driver->devctl(host, CANCEL_REQUESTS, 0); + + INIT_LIST_HEAD(&tmp); + spin_lock_irqsave(&pending_packets_lock, flags); + list_splice_init(&host->pending_packets, &tmp); + spin_unlock_irqrestore(&pending_packets_lock, flags); + + list_for_each_entry_safe(packet, p, &tmp, queue) { + list_del_init(&packet->queue); + packet->state = hpsb_complete; + packet->ack_code = ACKX_ABORTED; + queue_packet_complete(packet); + } +} + +void abort_timedouts(unsigned long __opaque) +{ + struct hpsb_host *host = (struct hpsb_host *)__opaque; + struct hpsb_packet *packet, *p; + struct list_head tmp; + unsigned long flags, expire, j; + + spin_lock_irqsave(&host->csr.lock, flags); + expire = host->csr.expire; + spin_unlock_irqrestore(&host->csr.lock, flags); + + j = jiffies; + INIT_LIST_HEAD(&tmp); + spin_lock_irqsave(&pending_packets_lock, flags); + + list_for_each_entry_safe(packet, p, &host->pending_packets, queue) { + if (time_before(packet->sendtime + expire, j)) + list_move_tail(&packet->queue, &tmp); + else + /* Since packets are added to the tail, the oldest + * ones are first, always. When we get to one that + * isn't timed out, the rest aren't either. */ + break; + } + if (!list_empty(&host->pending_packets)) + mod_timer(&host->timeout, j + host->timeout_interval); + + spin_unlock_irqrestore(&pending_packets_lock, flags); + + list_for_each_entry_safe(packet, p, &tmp, queue) { + list_del_init(&packet->queue); + packet->state = hpsb_complete; + packet->ack_code = ACKX_TIMEOUT; + queue_packet_complete(packet); + } +} + +static struct task_struct *khpsbpkt_thread; +static LIST_HEAD(hpsbpkt_queue); + +static void queue_packet_complete(struct hpsb_packet *packet) +{ + unsigned long flags; + + if (packet->no_waiter) { + hpsb_free_packet(packet); + return; + } + if (packet->complete_routine != NULL) { + spin_lock_irqsave(&pending_packets_lock, flags); + list_add_tail(&packet->queue, &hpsbpkt_queue); + spin_unlock_irqrestore(&pending_packets_lock, flags); + wake_up_process(khpsbpkt_thread); + } + return; +} + +/* + * Kernel thread which handles packets that are completed. This way the + * packet's "complete" function is asynchronously run in process context. + * Only packets which have a "complete" function may be sent here. + */ +static int hpsbpkt_thread(void *__hi) +{ + struct hpsb_packet *packet, *p; + struct list_head tmp; + int may_schedule; + + while (!kthread_should_stop()) { + + INIT_LIST_HEAD(&tmp); + spin_lock_irq(&pending_packets_lock); + list_splice_init(&hpsbpkt_queue, &tmp); + spin_unlock_irq(&pending_packets_lock); + + list_for_each_entry_safe(packet, p, &tmp, queue) { + list_del_init(&packet->queue); + packet->complete_routine(packet->complete_data); + } + + set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irq(&pending_packets_lock); + may_schedule = list_empty(&hpsbpkt_queue); + spin_unlock_irq(&pending_packets_lock); + if (may_schedule) + schedule(); + __set_current_state(TASK_RUNNING); + } + return 0; +} + +static int __init ieee1394_init(void) +{ + int i, ret; + + /* non-fatal error */ + if (hpsb_init_config_roms()) { + HPSB_ERR("Failed to initialize some config rom entries.\n"); + HPSB_ERR("Some features may not be available\n"); + } + + khpsbpkt_thread = kthread_run(hpsbpkt_thread, NULL, "khpsbpkt"); + if (IS_ERR(khpsbpkt_thread)) { + HPSB_ERR("Failed to start hpsbpkt thread!\n"); + ret = PTR_ERR(khpsbpkt_thread); + goto exit_cleanup_config_roms; + } + + if (register_chrdev_region(IEEE1394_CORE_DEV, 256, "ieee1394")) { + HPSB_ERR("unable to register character device major %d!\n", IEEE1394_MAJOR); + ret = -ENODEV; + goto exit_release_kernel_thread; + } + + ret = bus_register(&ieee1394_bus_type); + if (ret < 0) { + HPSB_INFO("bus register failed"); + goto release_chrdev; + } + + for (i = 0; fw_bus_attrs[i]; i++) { + ret = bus_create_file(&ieee1394_bus_type, fw_bus_attrs[i]); + if (ret < 0) { + while (i >= 0) { + bus_remove_file(&ieee1394_bus_type, + fw_bus_attrs[i--]); + } + bus_unregister(&ieee1394_bus_type); + goto release_chrdev; + } + } + + ret = class_register(&hpsb_host_class); + if (ret < 0) + goto release_all_bus; + + hpsb_protocol_class = class_create(THIS_MODULE, "ieee1394_protocol"); + if (IS_ERR(hpsb_protocol_class)) { + ret = PTR_ERR(hpsb_protocol_class); + goto release_class_host; + } + + ret = init_csr(); + if (ret) { + HPSB_INFO("init csr failed"); + ret = -ENOMEM; + goto release_class_protocol; + } + + if (disable_nodemgr) { + HPSB_INFO("nodemgr and IRM functionality disabled"); + /* We shouldn't contend for IRM with nodemgr disabled, since + nodemgr implements functionality required of ieee1394a-2000 + IRMs */ + hpsb_disable_irm = 1; + + return 0; + } + + if (hpsb_disable_irm) { + HPSB_INFO("IRM functionality disabled"); + } + + ret = init_ieee1394_nodemgr(); + if (ret < 0) { + HPSB_INFO("init nodemgr failed"); + goto cleanup_csr; + } + + return 0; + +cleanup_csr: + cleanup_csr(); +release_class_protocol: + class_destroy(hpsb_protocol_class); +release_class_host: + class_unregister(&hpsb_host_class); +release_all_bus: + for (i = 0; fw_bus_attrs[i]; i++) + bus_remove_file(&ieee1394_bus_type, fw_bus_attrs[i]); + bus_unregister(&ieee1394_bus_type); +release_chrdev: + unregister_chrdev_region(IEEE1394_CORE_DEV, 256); +exit_release_kernel_thread: + kthread_stop(khpsbpkt_thread); +exit_cleanup_config_roms: + hpsb_cleanup_config_roms(); + return ret; +} + +static void __exit ieee1394_cleanup(void) +{ + int i; + + if (!disable_nodemgr) + cleanup_ieee1394_nodemgr(); + + cleanup_csr(); + + class_destroy(hpsb_protocol_class); + class_unregister(&hpsb_host_class); + for (i = 0; fw_bus_attrs[i]; i++) + bus_remove_file(&ieee1394_bus_type, fw_bus_attrs[i]); + bus_unregister(&ieee1394_bus_type); + + kthread_stop(khpsbpkt_thread); + + hpsb_cleanup_config_roms(); + + unregister_chrdev_region(IEEE1394_CORE_DEV, 256); +} + +module_init(ieee1394_init); +module_exit(ieee1394_cleanup); + +/* Exported symbols */ + +/** hosts.c **/ +EXPORT_SYMBOL(hpsb_alloc_host); +EXPORT_SYMBOL(hpsb_add_host); +EXPORT_SYMBOL(hpsb_resume_host); +EXPORT_SYMBOL(hpsb_remove_host); +EXPORT_SYMBOL(hpsb_update_config_rom_image); + +/** ieee1394_core.c **/ +EXPORT_SYMBOL(hpsb_speedto_str); +EXPORT_SYMBOL(hpsb_protocol_class); +EXPORT_SYMBOL(hpsb_set_packet_complete_task); +EXPORT_SYMBOL(hpsb_alloc_packet); +EXPORT_SYMBOL(hpsb_free_packet); +EXPORT_SYMBOL(hpsb_send_packet); +EXPORT_SYMBOL(hpsb_reset_bus); +EXPORT_SYMBOL(hpsb_read_cycle_timer); +EXPORT_SYMBOL(hpsb_bus_reset); +EXPORT_SYMBOL(hpsb_selfid_received); +EXPORT_SYMBOL(hpsb_selfid_complete); +EXPORT_SYMBOL(hpsb_packet_sent); +EXPORT_SYMBOL(hpsb_packet_received); +EXPORT_SYMBOL_GPL(hpsb_disable_irm); + +/** ieee1394_transactions.c **/ +EXPORT_SYMBOL(hpsb_get_tlabel); +EXPORT_SYMBOL(hpsb_free_tlabel); +EXPORT_SYMBOL(hpsb_make_readpacket); +EXPORT_SYMBOL(hpsb_make_writepacket); +EXPORT_SYMBOL(hpsb_make_streampacket); +EXPORT_SYMBOL(hpsb_make_lockpacket); +EXPORT_SYMBOL(hpsb_make_lock64packet); +EXPORT_SYMBOL(hpsb_make_phypacket); +EXPORT_SYMBOL(hpsb_read); +EXPORT_SYMBOL(hpsb_write); +EXPORT_SYMBOL(hpsb_packet_success); + +/** highlevel.c **/ +EXPORT_SYMBOL(hpsb_register_highlevel); +EXPORT_SYMBOL(hpsb_unregister_highlevel); +EXPORT_SYMBOL(hpsb_register_addrspace); +EXPORT_SYMBOL(hpsb_unregister_addrspace); +EXPORT_SYMBOL(hpsb_allocate_and_register_addrspace); +EXPORT_SYMBOL(hpsb_get_hostinfo); +EXPORT_SYMBOL(hpsb_create_hostinfo); +EXPORT_SYMBOL(hpsb_destroy_hostinfo); +EXPORT_SYMBOL(hpsb_set_hostinfo_key); +EXPORT_SYMBOL(hpsb_get_hostinfo_bykey); +EXPORT_SYMBOL(hpsb_set_hostinfo); + +/** nodemgr.c **/ +EXPORT_SYMBOL(hpsb_node_fill_packet); +EXPORT_SYMBOL(hpsb_node_write); +EXPORT_SYMBOL(__hpsb_register_protocol); +EXPORT_SYMBOL(hpsb_unregister_protocol); + +/** csr.c **/ +EXPORT_SYMBOL(hpsb_update_config_rom); + +/** dma.c **/ +EXPORT_SYMBOL(dma_prog_region_init); +EXPORT_SYMBOL(dma_prog_region_alloc); +EXPORT_SYMBOL(dma_prog_region_free); +EXPORT_SYMBOL(dma_region_init); +EXPORT_SYMBOL(dma_region_alloc); +EXPORT_SYMBOL(dma_region_free); +EXPORT_SYMBOL(dma_region_sync_for_cpu); +EXPORT_SYMBOL(dma_region_sync_for_device); +EXPORT_SYMBOL(dma_region_mmap); +EXPORT_SYMBOL(dma_region_offset_to_bus); + +/** iso.c **/ +EXPORT_SYMBOL(hpsb_iso_xmit_init); +EXPORT_SYMBOL(hpsb_iso_recv_init); +EXPORT_SYMBOL(hpsb_iso_xmit_start); +EXPORT_SYMBOL(hpsb_iso_recv_start); +EXPORT_SYMBOL(hpsb_iso_recv_listen_channel); +EXPORT_SYMBOL(hpsb_iso_recv_unlisten_channel); +EXPORT_SYMBOL(hpsb_iso_recv_set_channel_mask); +EXPORT_SYMBOL(hpsb_iso_stop); +EXPORT_SYMBOL(hpsb_iso_shutdown); +EXPORT_SYMBOL(hpsb_iso_xmit_queue_packet); +EXPORT_SYMBOL(hpsb_iso_xmit_sync); +EXPORT_SYMBOL(hpsb_iso_recv_release_packets); +EXPORT_SYMBOL(hpsb_iso_n_ready); +EXPORT_SYMBOL(hpsb_iso_packet_sent); +EXPORT_SYMBOL(hpsb_iso_packet_received); +EXPORT_SYMBOL(hpsb_iso_wake); +EXPORT_SYMBOL(hpsb_iso_recv_flush); + +/** csr1212.c **/ +EXPORT_SYMBOL(csr1212_attach_keyval_to_directory); +EXPORT_SYMBOL(csr1212_detach_keyval_from_directory); +EXPORT_SYMBOL(csr1212_get_keyval); +EXPORT_SYMBOL(csr1212_new_directory); +EXPORT_SYMBOL(csr1212_parse_keyval); +EXPORT_SYMBOL(csr1212_read); +EXPORT_SYMBOL(csr1212_release_keyval); diff --git a/drivers/ieee1394/ieee1394_core.h b/drivers/ieee1394/ieee1394_core.h new file mode 100644 index 0000000..21d50f7 --- /dev/null +++ b/drivers/ieee1394/ieee1394_core.h @@ -0,0 +1,168 @@ +#ifndef _IEEE1394_CORE_H +#define _IEEE1394_CORE_H + +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/list.h> +#include <linux/types.h> +#include <asm/atomic.h> + +#include "hosts.h" +#include "ieee1394_types.h" + +struct hpsb_packet { + /* This struct is basically read-only for hosts with the exception of + * the data buffer contents and driver_list. */ + + /* This can be used for host driver internal linking. + * + * NOTE: This must be left in init state when the driver is done + * with it (e.g. by using list_del_init()), since the core does + * some sanity checks to make sure the packet is not on a + * driver_list when free'ing it. */ + struct list_head driver_list; + + nodeid_t node_id; + + /* hpsb_raw = send as-is, do not CRC (but still byte-swap it) */ + enum { hpsb_async, hpsb_raw } __attribute__((packed)) type; + + /* Okay, this is core internal and a no care for hosts. + * queued = queued for sending + * pending = sent, waiting for response + * complete = processing completed, successful or not + */ + enum { + hpsb_unused, hpsb_queued, hpsb_pending, hpsb_complete + } __attribute__((packed)) state; + + /* These are core-internal. */ + signed char tlabel; + signed char ack_code; + unsigned char tcode; + + unsigned expect_response:1; + unsigned no_waiter:1; + + /* Speed to transmit with: 0 = 100Mbps, 1 = 200Mbps, 2 = 400Mbps */ + unsigned speed_code:2; + + struct hpsb_host *host; + unsigned int generation; + + atomic_t refcnt; + struct list_head queue; + + /* Function (and possible data to pass to it) to call when this + * packet is completed. */ + void (*complete_routine)(void *); + void *complete_data; + + /* Store jiffies for implementing bus timeouts. */ + unsigned long sendtime; + + /* Core-internal. */ + size_t allocated_data_size; /* as allocated */ + + /* Sizes are in bytes. To be set by caller of hpsb_alloc_packet. */ + size_t data_size; /* as filled in */ + size_t header_size; /* as filled in, not counting the CRC */ + + /* Buffers */ + quadlet_t *data; /* can be DMA-mapped */ + quadlet_t header[5]; + quadlet_t embedded_data[0]; /* keep as last member */ +}; + +void hpsb_set_packet_complete_task(struct hpsb_packet *packet, + void (*routine)(void *), void *data); +static inline struct hpsb_packet *driver_packet(struct list_head *l) +{ + return list_entry(l, struct hpsb_packet, driver_list); +} +void abort_timedouts(unsigned long __opaque); +struct hpsb_packet *hpsb_alloc_packet(size_t data_size); +void hpsb_free_packet(struct hpsb_packet *packet); + +/** + * get_hpsb_generation - generation counter for the complete 1394 subsystem + * + * Generation gets incremented on every change in the subsystem (notably on bus + * resets). Use the functions, not the variable. + */ +static inline unsigned int get_hpsb_generation(struct hpsb_host *host) +{ + return atomic_read(&host->generation); +} + +int hpsb_send_phy_config(struct hpsb_host *host, int rootid, int gapcnt); +int hpsb_send_packet(struct hpsb_packet *packet); +int hpsb_send_packet_and_wait(struct hpsb_packet *packet); +int hpsb_reset_bus(struct hpsb_host *host, int type); +int hpsb_read_cycle_timer(struct hpsb_host *host, u32 *cycle_timer, + u64 *local_time); + +int hpsb_bus_reset(struct hpsb_host *host); +void hpsb_selfid_received(struct hpsb_host *host, quadlet_t sid); +void hpsb_selfid_complete(struct hpsb_host *host, int phyid, int isroot); +void hpsb_packet_sent(struct hpsb_host *host, struct hpsb_packet *packet, + int ackcode); +void hpsb_packet_received(struct hpsb_host *host, quadlet_t *data, size_t size, + int write_acked); + +/* + * CHARACTER DEVICE DISPATCHING + * + * All ieee1394 character device drivers share the same major number + * (major 171). The 256 minor numbers are allocated to the various + * task-specific interfaces (raw1394, video1394, dv1394, etc) in + * blocks of 16. + * + * The core ieee1394.o module allocates the device number region + * 171:0-255, the various drivers must then cdev_add() their cdev + * objects to handle their respective sub-regions. + * + * Minor device number block allocations: + * + * Block 0 ( 0- 15) raw1394 + * Block 1 ( 16- 31) video1394 + * Block 2 ( 32- 47) dv1394 + * + * Blocks 3-14 free for future allocation + * + * Block 15 (240-255) reserved for drivers under development, etc. + */ + +#define IEEE1394_MAJOR 171 + +#define IEEE1394_MINOR_BLOCK_RAW1394 0 +#define IEEE1394_MINOR_BLOCK_VIDEO1394 1 +#define IEEE1394_MINOR_BLOCK_DV1394 2 +#define IEEE1394_MINOR_BLOCK_EXPERIMENTAL 15 + +#define IEEE1394_CORE_DEV MKDEV(IEEE1394_MAJOR, 0) +#define IEEE1394_RAW1394_DEV MKDEV(IEEE1394_MAJOR, \ + IEEE1394_MINOR_BLOCK_RAW1394 * 16) +#define IEEE1394_VIDEO1394_DEV MKDEV(IEEE1394_MAJOR, \ + IEEE1394_MINOR_BLOCK_VIDEO1394 * 16) +#define IEEE1394_DV1394_DEV MKDEV(IEEE1394_MAJOR, \ + IEEE1394_MINOR_BLOCK_DV1394 * 16) +#define IEEE1394_EXPERIMENTAL_DEV MKDEV(IEEE1394_MAJOR, \ + IEEE1394_MINOR_BLOCK_EXPERIMENTAL * 16) + +/** + * ieee1394_file_to_instance - get the index within a minor number block + */ +static inline unsigned char ieee1394_file_to_instance(struct file *file) +{ + return file->f_path.dentry->d_inode->i_cindex; +} + +extern int hpsb_disable_irm; + +/* Our sysfs bus entry */ +extern struct bus_type ieee1394_bus_type; +extern struct class hpsb_host_class; +extern struct class *hpsb_protocol_class; + +#endif /* _IEEE1394_CORE_H */ diff --git a/drivers/ieee1394/ieee1394_hotplug.h b/drivers/ieee1394/ieee1394_hotplug.h new file mode 100644 index 0000000..dd5500e --- /dev/null +++ b/drivers/ieee1394/ieee1394_hotplug.h @@ -0,0 +1,19 @@ +#ifndef _IEEE1394_HOTPLUG_H +#define _IEEE1394_HOTPLUG_H + +/* Unit spec id and sw version entry for some protocols */ +#define AVC_UNIT_SPEC_ID_ENTRY 0x0000A02D +#define AVC_SW_VERSION_ENTRY 0x00010001 +#define CAMERA_UNIT_SPEC_ID_ENTRY 0x0000A02D +#define CAMERA_SW_VERSION_ENTRY 0x00000100 + +/* /include/linux/mod_devicetable.h defines: + * IEEE1394_MATCH_VENDOR_ID + * IEEE1394_MATCH_MODEL_ID + * IEEE1394_MATCH_SPECIFIER_ID + * IEEE1394_MATCH_VERSION + * struct ieee1394_device_id + */ +#include <linux/mod_devicetable.h> + +#endif /* _IEEE1394_HOTPLUG_H */ diff --git a/drivers/ieee1394/ieee1394_transactions.c b/drivers/ieee1394/ieee1394_transactions.c new file mode 100644 index 0000000..10c3d9f --- /dev/null +++ b/drivers/ieee1394/ieee1394_transactions.c @@ -0,0 +1,572 @@ +/* + * IEEE 1394 for Linux + * + * Transaction support. + * + * Copyright (C) 1999 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/bitops.h> +#include <linux/compiler.h> +#include <linux/hardirq.h> +#include <linux/spinlock.h> +#include <linux/string.h> +#include <linux/sched.h> /* because linux/wait.h is broken if CONFIG_SMP=n */ +#include <linux/wait.h> + +#include <asm/bug.h> +#include <asm/errno.h> +#include <asm/system.h> + +#include "ieee1394.h" +#include "ieee1394_types.h" +#include "hosts.h" +#include "ieee1394_core.h" +#include "ieee1394_transactions.h" + +#define PREP_ASYNC_HEAD_ADDRESS(tc) \ + packet->tcode = tc; \ + packet->header[0] = (packet->node_id << 16) | (packet->tlabel << 10) \ + | (1 << 8) | (tc << 4); \ + packet->header[1] = (packet->host->node_id << 16) | (addr >> 32); \ + packet->header[2] = addr & 0xffffffff + +#ifndef HPSB_DEBUG_TLABELS +static +#endif +DEFINE_SPINLOCK(hpsb_tlabel_lock); + +static DECLARE_WAIT_QUEUE_HEAD(tlabel_wq); + +static void fill_async_readquad(struct hpsb_packet *packet, u64 addr) +{ + PREP_ASYNC_HEAD_ADDRESS(TCODE_READQ); + packet->header_size = 12; + packet->data_size = 0; + packet->expect_response = 1; +} + +static void fill_async_readblock(struct hpsb_packet *packet, u64 addr, + int length) +{ + PREP_ASYNC_HEAD_ADDRESS(TCODE_READB); + packet->header[3] = length << 16; + packet->header_size = 16; + packet->data_size = 0; + packet->expect_response = 1; +} + +static void fill_async_writequad(struct hpsb_packet *packet, u64 addr, + quadlet_t data) +{ + PREP_ASYNC_HEAD_ADDRESS(TCODE_WRITEQ); + packet->header[3] = data; + packet->header_size = 16; + packet->data_size = 0; + packet->expect_response = 1; +} + +static void fill_async_writeblock(struct hpsb_packet *packet, u64 addr, + int length) +{ + PREP_ASYNC_HEAD_ADDRESS(TCODE_WRITEB); + packet->header[3] = length << 16; + packet->header_size = 16; + packet->expect_response = 1; + packet->data_size = length + (length % 4 ? 4 - (length % 4) : 0); +} + +static void fill_async_lock(struct hpsb_packet *packet, u64 addr, int extcode, + int length) +{ + PREP_ASYNC_HEAD_ADDRESS(TCODE_LOCK_REQUEST); + packet->header[3] = (length << 16) | extcode; + packet->header_size = 16; + packet->data_size = length; + packet->expect_response = 1; +} + +static void fill_phy_packet(struct hpsb_packet *packet, quadlet_t data) +{ + packet->header[0] = data; + packet->header[1] = ~data; + packet->header_size = 8; + packet->data_size = 0; + packet->expect_response = 0; + packet->type = hpsb_raw; /* No CRC added */ + packet->speed_code = IEEE1394_SPEED_100; /* Force speed to be 100Mbps */ +} + +static void fill_async_stream_packet(struct hpsb_packet *packet, int length, + int channel, int tag, int sync) +{ + packet->header[0] = (length << 16) | (tag << 14) | (channel << 8) + | (TCODE_STREAM_DATA << 4) | sync; + + packet->header_size = 4; + packet->data_size = length; + packet->type = hpsb_async; + packet->tcode = TCODE_ISO_DATA; +} + +/* same as hpsb_get_tlabel, except that it returns immediately */ +static int hpsb_get_tlabel_atomic(struct hpsb_packet *packet) +{ + unsigned long flags, *tp; + u8 *next; + int tlabel, n = NODEID_TO_NODE(packet->node_id); + + /* Broadcast transactions are complete once the request has been sent. + * Use the same transaction label for all broadcast transactions. */ + if (unlikely(n == ALL_NODES)) { + packet->tlabel = 0; + return 0; + } + tp = packet->host->tl_pool[n].map; + next = &packet->host->next_tl[n]; + + spin_lock_irqsave(&hpsb_tlabel_lock, flags); + tlabel = find_next_zero_bit(tp, 64, *next); + if (tlabel > 63) + tlabel = find_first_zero_bit(tp, 64); + if (tlabel > 63) { + spin_unlock_irqrestore(&hpsb_tlabel_lock, flags); + return -EAGAIN; + } + __set_bit(tlabel, tp); + *next = (tlabel + 1) & 63; + spin_unlock_irqrestore(&hpsb_tlabel_lock, flags); + + packet->tlabel = tlabel; + return 0; +} + +/** + * hpsb_get_tlabel - allocate a transaction label + * @packet: the packet whose tlabel and tl_pool we set + * + * Every asynchronous transaction on the 1394 bus needs a transaction + * label to match the response to the request. This label has to be + * different from any other transaction label in an outstanding request to + * the same node to make matching possible without ambiguity. + * + * There are 64 different tlabels, so an allocated tlabel has to be freed + * with hpsb_free_tlabel() after the transaction is complete (unless it's + * reused again for the same target node). + * + * Return value: Zero on success, otherwise non-zero. A non-zero return + * generally means there are no available tlabels. If this is called out + * of interrupt or atomic context, then it will sleep until can return a + * tlabel or a signal is received. + */ +int hpsb_get_tlabel(struct hpsb_packet *packet) +{ + if (irqs_disabled() || in_atomic()) + return hpsb_get_tlabel_atomic(packet); + + /* NB: The macro wait_event_interruptible() is called 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. */ + return wait_event_interruptible(tlabel_wq, + !hpsb_get_tlabel_atomic(packet)); +} + +/** + * hpsb_free_tlabel - free an allocated transaction label + * @packet: packet whose tlabel and tl_pool needs to be cleared + * + * Frees the transaction label allocated with hpsb_get_tlabel(). The + * tlabel has to be freed after the transaction is complete (i.e. response + * was received for a split transaction or packet was sent for a unified + * transaction). + * + * A tlabel must not be freed twice. + */ +void hpsb_free_tlabel(struct hpsb_packet *packet) +{ + unsigned long flags, *tp; + int tlabel, n = NODEID_TO_NODE(packet->node_id); + + if (unlikely(n == ALL_NODES)) + return; + tp = packet->host->tl_pool[n].map; + tlabel = packet->tlabel; + BUG_ON(tlabel > 63 || tlabel < 0); + + spin_lock_irqsave(&hpsb_tlabel_lock, flags); + BUG_ON(!__test_and_clear_bit(tlabel, tp)); + spin_unlock_irqrestore(&hpsb_tlabel_lock, flags); + + wake_up_interruptible(&tlabel_wq); +} + +/** + * hpsb_packet_success - Make sense of the ack and reply codes + * + * Make sense of the ack and reply codes and return more convenient error codes: + * 0 = success. -%EBUSY = node is busy, try again. -%EAGAIN = error which can + * probably resolved by retry. -%EREMOTEIO = node suffers from an internal + * error. -%EACCES = this transaction is not allowed on requested address. + * -%EINVAL = invalid address at node. + */ +int hpsb_packet_success(struct hpsb_packet *packet) +{ + switch (packet->ack_code) { + case ACK_PENDING: + switch ((packet->header[1] >> 12) & 0xf) { + case RCODE_COMPLETE: + return 0; + case RCODE_CONFLICT_ERROR: + return -EAGAIN; + case RCODE_DATA_ERROR: + return -EREMOTEIO; + case RCODE_TYPE_ERROR: + return -EACCES; + case RCODE_ADDRESS_ERROR: + return -EINVAL; + default: + HPSB_ERR("received reserved rcode %d from node %d", + (packet->header[1] >> 12) & 0xf, + packet->node_id); + return -EAGAIN; + } + + case ACK_BUSY_X: + case ACK_BUSY_A: + case ACK_BUSY_B: + return -EBUSY; + + case ACK_TYPE_ERROR: + return -EACCES; + + case ACK_COMPLETE: + if (packet->tcode == TCODE_WRITEQ + || packet->tcode == TCODE_WRITEB) { + return 0; + } else { + HPSB_ERR("impossible ack_complete from node %d " + "(tcode %d)", packet->node_id, packet->tcode); + return -EAGAIN; + } + + case ACK_DATA_ERROR: + if (packet->tcode == TCODE_WRITEB + || packet->tcode == TCODE_LOCK_REQUEST) { + return -EAGAIN; + } else { + HPSB_ERR("impossible ack_data_error from node %d " + "(tcode %d)", packet->node_id, packet->tcode); + return -EAGAIN; + } + + case ACK_ADDRESS_ERROR: + return -EINVAL; + + case ACK_TARDY: + case ACK_CONFLICT_ERROR: + case ACKX_NONE: + case ACKX_SEND_ERROR: + case ACKX_ABORTED: + case ACKX_TIMEOUT: + /* error while sending */ + return -EAGAIN; + + default: + HPSB_ERR("got invalid ack %d from node %d (tcode %d)", + packet->ack_code, packet->node_id, packet->tcode); + return -EAGAIN; + } +} + +struct hpsb_packet *hpsb_make_readpacket(struct hpsb_host *host, nodeid_t node, + u64 addr, size_t length) +{ + struct hpsb_packet *packet; + + if (length == 0) + return NULL; + + packet = hpsb_alloc_packet(length); + if (!packet) + return NULL; + + packet->host = host; + packet->node_id = node; + + if (hpsb_get_tlabel(packet)) { + hpsb_free_packet(packet); + return NULL; + } + + if (length == 4) + fill_async_readquad(packet, addr); + else + fill_async_readblock(packet, addr, length); + + return packet; +} + +struct hpsb_packet *hpsb_make_writepacket(struct hpsb_host *host, nodeid_t node, + u64 addr, quadlet_t * buffer, + size_t length) +{ + struct hpsb_packet *packet; + + if (length == 0) + return NULL; + + packet = hpsb_alloc_packet(length); + if (!packet) + return NULL; + + if (length % 4) { /* zero padding bytes */ + packet->data[length >> 2] = 0; + } + packet->host = host; + packet->node_id = node; + + if (hpsb_get_tlabel(packet)) { + hpsb_free_packet(packet); + return NULL; + } + + if (length == 4) { + fill_async_writequad(packet, addr, buffer ? *buffer : 0); + } else { + fill_async_writeblock(packet, addr, length); + if (buffer) + memcpy(packet->data, buffer, length); + } + + return packet; +} + +struct hpsb_packet *hpsb_make_streampacket(struct hpsb_host *host, u8 * buffer, + int length, int channel, int tag, + int sync) +{ + struct hpsb_packet *packet; + + if (length == 0) + return NULL; + + packet = hpsb_alloc_packet(length); + if (!packet) + return NULL; + + if (length % 4) { /* zero padding bytes */ + packet->data[length >> 2] = 0; + } + packet->host = host; + + /* Because it is too difficult to determine all PHY speeds and link + * speeds here, we use S100... */ + packet->speed_code = IEEE1394_SPEED_100; + + /* ...and prevent hpsb_send_packet() from overriding it. */ + packet->node_id = LOCAL_BUS | ALL_NODES; + + if (hpsb_get_tlabel(packet)) { + hpsb_free_packet(packet); + return NULL; + } + + fill_async_stream_packet(packet, length, channel, tag, sync); + if (buffer) + memcpy(packet->data, buffer, length); + + return packet; +} + +struct hpsb_packet *hpsb_make_lockpacket(struct hpsb_host *host, nodeid_t node, + u64 addr, int extcode, + quadlet_t * data, quadlet_t arg) +{ + struct hpsb_packet *p; + u32 length; + + p = hpsb_alloc_packet(8); + if (!p) + return NULL; + + p->host = host; + p->node_id = node; + if (hpsb_get_tlabel(p)) { + hpsb_free_packet(p); + return NULL; + } + + switch (extcode) { + case EXTCODE_FETCH_ADD: + case EXTCODE_LITTLE_ADD: + length = 4; + if (data) + p->data[0] = *data; + break; + default: + length = 8; + if (data) { + p->data[0] = arg; + p->data[1] = *data; + } + break; + } + fill_async_lock(p, addr, extcode, length); + + return p; +} + +struct hpsb_packet *hpsb_make_lock64packet(struct hpsb_host *host, + nodeid_t node, u64 addr, int extcode, + octlet_t * data, octlet_t arg) +{ + struct hpsb_packet *p; + u32 length; + + p = hpsb_alloc_packet(16); + if (!p) + return NULL; + + p->host = host; + p->node_id = node; + if (hpsb_get_tlabel(p)) { + hpsb_free_packet(p); + return NULL; + } + + switch (extcode) { + case EXTCODE_FETCH_ADD: + case EXTCODE_LITTLE_ADD: + length = 8; + if (data) { + p->data[0] = *data >> 32; + p->data[1] = *data & 0xffffffff; + } + break; + default: + length = 16; + if (data) { + p->data[0] = arg >> 32; + p->data[1] = arg & 0xffffffff; + p->data[2] = *data >> 32; + p->data[3] = *data & 0xffffffff; + } + break; + } + fill_async_lock(p, addr, extcode, length); + + return p; +} + +struct hpsb_packet *hpsb_make_phypacket(struct hpsb_host *host, quadlet_t data) +{ + struct hpsb_packet *p; + + p = hpsb_alloc_packet(0); + if (!p) + return NULL; + + p->host = host; + fill_phy_packet(p, data); + + return p; +} + +/* + * FIXME - these functions should probably read from / write to user space to + * avoid in kernel buffers for user space callers + */ + +/** + * hpsb_read - generic read function + * + * Recognizes the local node ID and act accordingly. Automatically uses a + * quadlet read request if @length == 4 and and a block read request otherwise. + * It does not yet support lengths that are not a multiple of 4. + * + * You must explicitly specifiy the @generation for which the node ID is valid, + * to avoid sending packets to the wrong nodes when we race with a bus reset. + */ +int hpsb_read(struct hpsb_host *host, nodeid_t node, unsigned int generation, + u64 addr, quadlet_t * buffer, size_t length) +{ + struct hpsb_packet *packet; + int retval = 0; + + if (length == 0) + return -EINVAL; + + BUG_ON(in_interrupt()); // We can't be called in an interrupt, yet + + packet = hpsb_make_readpacket(host, node, addr, length); + + if (!packet) { + return -ENOMEM; + } + + packet->generation = generation; + retval = hpsb_send_packet_and_wait(packet); + if (retval < 0) + goto hpsb_read_fail; + + retval = hpsb_packet_success(packet); + + if (retval == 0) { + if (length == 4) { + *buffer = packet->header[3]; + } else { + memcpy(buffer, packet->data, length); + } + } + + hpsb_read_fail: + hpsb_free_tlabel(packet); + hpsb_free_packet(packet); + + return retval; +} + +/** + * hpsb_write - generic write function + * + * Recognizes the local node ID and act accordingly. Automatically uses a + * quadlet write request if @length == 4 and and a block write request + * otherwise. It does not yet support lengths that are not a multiple of 4. + * + * You must explicitly specifiy the @generation for which the node ID is valid, + * to avoid sending packets to the wrong nodes when we race with a bus reset. + */ +int hpsb_write(struct hpsb_host *host, nodeid_t node, unsigned int generation, + u64 addr, quadlet_t * buffer, size_t length) +{ + struct hpsb_packet *packet; + int retval; + + if (length == 0) + return -EINVAL; + + BUG_ON(in_interrupt()); // We can't be called in an interrupt, yet + + packet = hpsb_make_writepacket(host, node, addr, buffer, length); + + if (!packet) + return -ENOMEM; + + packet->generation = generation; + retval = hpsb_send_packet_and_wait(packet); + if (retval < 0) + goto hpsb_write_fail; + + retval = hpsb_packet_success(packet); + + hpsb_write_fail: + hpsb_free_tlabel(packet); + hpsb_free_packet(packet); + + return retval; +} diff --git a/drivers/ieee1394/ieee1394_transactions.h b/drivers/ieee1394/ieee1394_transactions.h new file mode 100644 index 0000000..d2d5bc3 --- /dev/null +++ b/drivers/ieee1394/ieee1394_transactions.h @@ -0,0 +1,38 @@ +#ifndef _IEEE1394_TRANSACTIONS_H +#define _IEEE1394_TRANSACTIONS_H + +#include <linux/types.h> + +#include "ieee1394_types.h" + +struct hpsb_packet; +struct hpsb_host; + +int hpsb_get_tlabel(struct hpsb_packet *packet); +void hpsb_free_tlabel(struct hpsb_packet *packet); +struct hpsb_packet *hpsb_make_readpacket(struct hpsb_host *host, nodeid_t node, + u64 addr, size_t length); +struct hpsb_packet *hpsb_make_lockpacket(struct hpsb_host *host, nodeid_t node, + u64 addr, int extcode, quadlet_t *data, + quadlet_t arg); +struct hpsb_packet *hpsb_make_lock64packet(struct hpsb_host *host, + nodeid_t node, u64 addr, int extcode, + octlet_t *data, octlet_t arg); +struct hpsb_packet *hpsb_make_phypacket(struct hpsb_host *host, quadlet_t data); +struct hpsb_packet *hpsb_make_writepacket(struct hpsb_host *host, + nodeid_t node, u64 addr, + quadlet_t *buffer, size_t length); +struct hpsb_packet *hpsb_make_streampacket(struct hpsb_host *host, u8 *buffer, + int length, int channel, int tag, + int sync); +int hpsb_packet_success(struct hpsb_packet *packet); +int hpsb_read(struct hpsb_host *host, nodeid_t node, unsigned int generation, + u64 addr, quadlet_t *buffer, size_t length); +int hpsb_write(struct hpsb_host *host, nodeid_t node, unsigned int generation, + u64 addr, quadlet_t *buffer, size_t length); + +#ifdef HPSB_DEBUG_TLABELS +extern spinlock_t hpsb_tlabel_lock; +#endif + +#endif /* _IEEE1394_TRANSACTIONS_H */ diff --git a/drivers/ieee1394/ieee1394_types.h b/drivers/ieee1394/ieee1394_types.h new file mode 100644 index 0000000..9803aaa --- /dev/null +++ b/drivers/ieee1394/ieee1394_types.h @@ -0,0 +1,69 @@ +#ifndef _IEEE1394_TYPES_H +#define _IEEE1394_TYPES_H + +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/types.h> +#include <asm/byteorder.h> + +typedef u32 quadlet_t; +typedef u64 octlet_t; +typedef u16 nodeid_t; + +typedef u8 byte_t; +typedef u64 nodeaddr_t; +typedef u16 arm_length_t; + +#define BUS_MASK 0xffc0 +#define BUS_SHIFT 6 +#define NODE_MASK 0x003f +#define LOCAL_BUS 0xffc0 +#define ALL_NODES 0x003f + +#define NODEID_TO_BUS(nodeid) ((nodeid & BUS_MASK) >> BUS_SHIFT) +#define NODEID_TO_NODE(nodeid) (nodeid & NODE_MASK) + +/* Can be used to consistently print a node/bus ID. */ +#define NODE_BUS_FMT "%d-%02d:%04d" +#define NODE_BUS_ARGS(__host, __nodeid) \ + __host->id, NODEID_TO_NODE(__nodeid), NODEID_TO_BUS(__nodeid) + +#define HPSB_PRINT(level, fmt, args...) \ + printk(level "ieee1394: " fmt "\n" , ## args) + +#define HPSB_DEBUG(fmt, args...) HPSB_PRINT(KERN_DEBUG, fmt , ## args) +#define HPSB_INFO(fmt, args...) HPSB_PRINT(KERN_INFO, fmt , ## args) +#define HPSB_NOTICE(fmt, args...) HPSB_PRINT(KERN_NOTICE, fmt , ## args) +#define HPSB_WARN(fmt, args...) HPSB_PRINT(KERN_WARNING, fmt , ## args) +#define HPSB_ERR(fmt, args...) HPSB_PRINT(KERN_ERR, fmt , ## args) + +#ifdef CONFIG_IEEE1394_VERBOSEDEBUG +#define HPSB_VERBOSE(fmt, args...) HPSB_PRINT(KERN_DEBUG, fmt , ## args) +#define HPSB_DEBUG_TLABELS +#else +#define HPSB_VERBOSE(fmt, args...) do {} while (0) +#endif + +#ifdef __BIG_ENDIAN + +static inline void *memcpy_le32(u32 *dest, const u32 *__src, size_t count) +{ + void *tmp = dest; + u32 *src = (u32 *)__src; + + count /= 4; + while (count--) + *dest++ = swab32p(src++); + return tmp; +} + +#else + +static __inline__ void *memcpy_le32(u32 *dest, const u32 *src, size_t count) +{ + return memcpy(dest, src, count); +} + +#endif /* __BIG_ENDIAN */ + +#endif /* _IEEE1394_TYPES_H */ diff --git a/drivers/ieee1394/init_ohci1394_dma.c b/drivers/ieee1394/init_ohci1394_dma.c new file mode 100644 index 0000000..ddaab6e --- /dev/null +++ b/drivers/ieee1394/init_ohci1394_dma.c @@ -0,0 +1,285 @@ +/* + * init_ohci1394_dma.c - Initializes physical DMA on all OHCI 1394 controllers + * + * Copyright (C) 2006-2007 Bernhard Kaindl <bk@suse.de> + * + * Derived from drivers/ieee1394/ohci1394.c and arch/x86/kernel/early-quirks.c + * this file has functions to: + * - scan the PCI very early on boot for all OHCI 1394-compliant controllers + * - reset and initialize them and make them join the IEEE1394 bus and + * - enable physical DMA on them to allow remote debugging + * + * All code and data is marked as __init and __initdata, respective as + * during boot, all OHCI1394 controllers may be claimed by the firewire + * stack and at this point, this code should not touch them anymore. + * + * To use physical DMA after the initialization of the firewire stack, + * be sure that the stack enables it and (re-)attach after the bus reset + * which may be caused by the firewire stack initialization. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/interrupt.h> /* for ohci1394.h */ +#include <linux/delay.h> +#include <linux/pci.h> /* for PCI defines */ +#include <linux/init_ohci1394_dma.h> +#include <asm/pci-direct.h> /* for direct PCI config space access */ +#include <asm/fixmap.h> + +#include "ieee1394_types.h" +#include "ohci1394.h" + +int __initdata init_ohci1394_dma_early; + +/* Reads a PHY register of an OHCI-1394 controller */ +static inline u8 __init get_phy_reg(struct ti_ohci *ohci, u8 addr) +{ + int i; + quadlet_t r; + + reg_write(ohci, OHCI1394_PhyControl, (addr << 8) | 0x00008000); + + for (i = 0; i < OHCI_LOOP_COUNT; i++) { + if (reg_read(ohci, OHCI1394_PhyControl) & 0x80000000) + break; + mdelay(1); + } + r = reg_read(ohci, OHCI1394_PhyControl); + + return (r & 0x00ff0000) >> 16; +} + +/* Writes to a PHY register of an OHCI-1394 controller */ +static inline void __init set_phy_reg(struct ti_ohci *ohci, u8 addr, u8 data) +{ + int i; + + reg_write(ohci, OHCI1394_PhyControl, (addr << 8) | data | 0x00004000); + + for (i = 0; i < OHCI_LOOP_COUNT; i++) { + u32 r = reg_read(ohci, OHCI1394_PhyControl); + if (!(r & 0x00004000)) + break; + mdelay(1); + } +} + +/* Resets an OHCI-1394 controller (for sane state before initialization) */ +static inline void __init init_ohci1394_soft_reset(struct ti_ohci *ohci) { + int i; + + reg_write(ohci, OHCI1394_HCControlSet, OHCI1394_HCControl_softReset); + + for (i = 0; i < OHCI_LOOP_COUNT; i++) { + if (!(reg_read(ohci, OHCI1394_HCControlSet) + & OHCI1394_HCControl_softReset)) + break; + mdelay(1); + } +} + +/* Basic OHCI-1394 register and port inititalization */ +static inline void __init init_ohci1394_initialize(struct ti_ohci *ohci) +{ + quadlet_t bus_options; + int num_ports, i; + + /* Put some defaults to these undefined bus options */ + bus_options = reg_read(ohci, OHCI1394_BusOptions); + bus_options |= 0x60000000; /* Enable CMC and ISC */ + bus_options &= ~0x00ff0000; /* XXX: Set cyc_clk_acc to zero for now */ + bus_options &= ~0x18000000; /* Disable PMC and BMC */ + reg_write(ohci, OHCI1394_BusOptions, bus_options); + + /* Set the bus number */ + reg_write(ohci, OHCI1394_NodeID, 0x0000ffc0); + + /* Enable posted writes */ + reg_write(ohci, OHCI1394_HCControlSet, + OHCI1394_HCControl_postedWriteEnable); + + /* Clear link control register */ + reg_write(ohci, OHCI1394_LinkControlClear, 0xffffffff); + + /* enable phys */ + reg_write(ohci, OHCI1394_LinkControlSet, + OHCI1394_LinkControl_RcvPhyPkt); + + /* Don't accept phy packets into AR request context */ + reg_write(ohci, OHCI1394_LinkControlClear, 0x00000400); + + /* Clear the Isochonouys interrupt masks */ + reg_write(ohci, OHCI1394_IsoRecvIntMaskClear, 0xffffffff); + reg_write(ohci, OHCI1394_IsoRecvIntEventClear, 0xffffffff); + reg_write(ohci, OHCI1394_IsoXmitIntMaskClear, 0xffffffff); + reg_write(ohci, OHCI1394_IsoXmitIntEventClear, 0xffffffff); + + /* Accept asyncronous transfer requests from all nodes for now */ + reg_write(ohci,OHCI1394_AsReqFilterHiSet, 0x80000000); + + /* Specify asyncronous transfer retries */ + reg_write(ohci, OHCI1394_ATRetries, + OHCI1394_MAX_AT_REQ_RETRIES | + (OHCI1394_MAX_AT_RESP_RETRIES<<4) | + (OHCI1394_MAX_PHYS_RESP_RETRIES<<8)); + + /* We don't want hardware swapping */ + reg_write(ohci, OHCI1394_HCControlClear, OHCI1394_HCControl_noByteSwap); + + /* Enable link */ + reg_write(ohci, OHCI1394_HCControlSet, OHCI1394_HCControl_linkEnable); + + /* If anything is connected to a port, make sure it is enabled */ + num_ports = get_phy_reg(ohci, 2) & 0xf; + for (i = 0; i < num_ports; i++) { + unsigned int status; + + set_phy_reg(ohci, 7, i); + status = get_phy_reg(ohci, 8); + + if (status & 0x20) + set_phy_reg(ohci, 8, status & ~1); + } +} + +/** + * init_ohci1394_wait_for_busresets - wait until bus resets are completed + * + * OHCI1394 initialization itself and any device going on- or offline + * and any cable issue cause a IEEE1394 bus reset. The OHCI1394 spec + * specifies that physical DMA is disabled on each bus reset and it + * has to be enabled after each bus reset when needed. We resort + * to polling here because on early boot, we have no interrupts. + */ +static inline void __init init_ohci1394_wait_for_busresets(struct ti_ohci *ohci) +{ + int i, events; + + for (i=0; i < 9; i++) { + mdelay(200); + events = reg_read(ohci, OHCI1394_IntEventSet); + if (events & OHCI1394_busReset) + reg_write(ohci, OHCI1394_IntEventClear, + OHCI1394_busReset); + } +} + +/** + * init_ohci1394_enable_physical_dma - Enable physical DMA for remote debugging + * This enables remote DMA access over IEEE1394 from every host for the low + * 4GB of address space. DMA accesses above 4GB are not available currently. + */ +static inline void __init init_ohci1394_enable_physical_dma(struct ti_ohci *hci) +{ + reg_write(hci, OHCI1394_PhyReqFilterHiSet, 0xffffffff); + reg_write(hci, OHCI1394_PhyReqFilterLoSet, 0xffffffff); + reg_write(hci, OHCI1394_PhyUpperBound, 0xffff0000); +} + +/** + * init_ohci1394_reset_and_init_dma - init controller and enable DMA + * This initializes the given controller and enables physical DMA engine in it. + */ +static inline void __init init_ohci1394_reset_and_init_dma(struct ti_ohci *ohci) +{ + /* Start off with a soft reset, clears everything to a sane state. */ + init_ohci1394_soft_reset(ohci); + + /* Accessing some registers without LPS enabled may cause lock up */ + reg_write(ohci, OHCI1394_HCControlSet, OHCI1394_HCControl_LPS); + + /* Disable and clear interrupts */ + reg_write(ohci, OHCI1394_IntEventClear, 0xffffffff); + reg_write(ohci, OHCI1394_IntMaskClear, 0xffffffff); + + mdelay(50); /* Wait 50msec to make sure we have full link enabled */ + + init_ohci1394_initialize(ohci); + /* + * The initialization causes at least one IEEE1394 bus reset. Enabling + * physical DMA only works *after* *all* bus resets have calmed down: + */ + init_ohci1394_wait_for_busresets(ohci); + + /* We had to wait and do this now if we want to debug early problems */ + init_ohci1394_enable_physical_dma(ohci); +} + +/** + * init_ohci1394_controller - Map the registers of the controller and init DMA + * This maps the registers of the specified controller and initializes it + */ +static inline void __init init_ohci1394_controller(int num, int slot, int func) +{ + unsigned long ohci_base; + struct ti_ohci ohci; + + printk(KERN_INFO "init_ohci1394_dma: initializing OHCI-1394" + " at %02x:%02x.%x\n", num, slot, func); + + ohci_base = read_pci_config(num, slot, func, PCI_BASE_ADDRESS_0+(0<<2)) + & PCI_BASE_ADDRESS_MEM_MASK; + + set_fixmap_nocache(FIX_OHCI1394_BASE, ohci_base); + + ohci.registers = (void *)fix_to_virt(FIX_OHCI1394_BASE); + + init_ohci1394_reset_and_init_dma(&ohci); +} + +/** + * debug_init_ohci1394_dma - scan for OHCI1394 controllers and init DMA on them + * Scans the whole PCI space for OHCI1394 controllers and inits DMA on them + */ +void __init init_ohci1394_dma_on_all_controllers(void) +{ + int num, slot, func; + + if (!early_pci_allowed()) + return; + + /* Poor man's PCI discovery, the only thing we can do at early boot */ + for (num = 0; num < 32; num++) { + for (slot = 0; slot < 32; slot++) { + for (func = 0; func < 8; func++) { + u32 class = read_pci_config(num,slot,func, + PCI_CLASS_REVISION); + if ((class == 0xffffffff)) + continue; /* No device at this func */ + + if (class>>8 != PCI_CLASS_SERIAL_FIREWIRE_OHCI) + continue; /* Not an OHCI-1394 device */ + + init_ohci1394_controller(num, slot, func); + break; /* Assume one controller per device */ + } + } + } + printk(KERN_INFO "init_ohci1394_dma: finished initializing OHCI DMA\n"); +} + +/** + * setup_init_ohci1394_early - enables early OHCI1394 DMA initialization + */ +static int __init setup_ohci1394_dma(char *opt) +{ + if (!strcmp(opt, "early")) + init_ohci1394_dma_early = 1; + return 0; +} + +/* passing ohci1394_dma=early on boot causes early OHCI1394 DMA initialization */ +early_param("ohci1394_dma", setup_ohci1394_dma); diff --git a/drivers/ieee1394/iso.c b/drivers/ieee1394/iso.c new file mode 100644 index 0000000..1cf6487 --- /dev/null +++ b/drivers/ieee1394/iso.c @@ -0,0 +1,568 @@ +/* + * IEEE 1394 for Linux + * + * kernel ISO transmission/reception + * + * Copyright (C) 2002 Maas Digital LLC + * + * This code is licensed under the GPL. See the file COPYING in the root + * directory of the kernel sources for details. + */ + +#include <linux/pci.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/slab.h> + +#include "hosts.h" +#include "iso.h" + +/** + * hpsb_iso_stop - stop DMA + */ +void hpsb_iso_stop(struct hpsb_iso *iso) +{ + if (!(iso->flags & HPSB_ISO_DRIVER_STARTED)) + return; + + iso->host->driver->isoctl(iso, iso->type == HPSB_ISO_XMIT ? + XMIT_STOP : RECV_STOP, 0); + iso->flags &= ~HPSB_ISO_DRIVER_STARTED; +} + +/** + * hpsb_iso_shutdown - deallocate buffer and DMA context + */ +void hpsb_iso_shutdown(struct hpsb_iso *iso) +{ + if (iso->flags & HPSB_ISO_DRIVER_INIT) { + hpsb_iso_stop(iso); + iso->host->driver->isoctl(iso, iso->type == HPSB_ISO_XMIT ? + XMIT_SHUTDOWN : RECV_SHUTDOWN, 0); + iso->flags &= ~HPSB_ISO_DRIVER_INIT; + } + + dma_region_free(&iso->data_buf); + kfree(iso); +} + +static struct hpsb_iso *hpsb_iso_common_init(struct hpsb_host *host, + enum hpsb_iso_type type, + unsigned int data_buf_size, + unsigned int buf_packets, + int channel, int dma_mode, + int irq_interval, + void (*callback) (struct hpsb_iso + *)) +{ + struct hpsb_iso *iso; + int dma_direction; + + /* make sure driver supports the ISO API */ + if (!host->driver->isoctl) { + printk(KERN_INFO + "ieee1394: host driver '%s' does not support the rawiso API\n", + host->driver->name); + return NULL; + } + + /* sanitize parameters */ + + if (buf_packets < 2) + buf_packets = 2; + + if ((dma_mode < HPSB_ISO_DMA_DEFAULT) + || (dma_mode > HPSB_ISO_DMA_PACKET_PER_BUFFER)) + dma_mode = HPSB_ISO_DMA_DEFAULT; + + if ((irq_interval < 0) || (irq_interval > buf_packets / 4)) + irq_interval = buf_packets / 4; + if (irq_interval == 0) /* really interrupt for each packet */ + irq_interval = 1; + + if (channel < -1 || channel >= 64) + return NULL; + + /* channel = -1 is OK for multi-channel recv but not for xmit */ + if (type == HPSB_ISO_XMIT && channel < 0) + return NULL; + + /* allocate and write the struct hpsb_iso */ + + iso = + kmalloc(sizeof(*iso) + + buf_packets * sizeof(struct hpsb_iso_packet_info), + GFP_KERNEL); + if (!iso) + return NULL; + + iso->infos = (struct hpsb_iso_packet_info *)(iso + 1); + + iso->type = type; + iso->host = host; + iso->hostdata = NULL; + iso->callback = callback; + init_waitqueue_head(&iso->waitq); + iso->channel = channel; + iso->irq_interval = irq_interval; + iso->dma_mode = dma_mode; + dma_region_init(&iso->data_buf); + iso->buf_size = PAGE_ALIGN(data_buf_size); + iso->buf_packets = buf_packets; + iso->pkt_dma = 0; + iso->first_packet = 0; + spin_lock_init(&iso->lock); + + if (iso->type == HPSB_ISO_XMIT) { + iso->n_ready_packets = iso->buf_packets; + dma_direction = PCI_DMA_TODEVICE; + } else { + iso->n_ready_packets = 0; + dma_direction = PCI_DMA_FROMDEVICE; + } + + atomic_set(&iso->overflows, 0); + iso->bytes_discarded = 0; + iso->flags = 0; + iso->prebuffer = 0; + + /* allocate the packet buffer */ + if (dma_region_alloc + (&iso->data_buf, iso->buf_size, host->pdev, dma_direction)) + goto err; + + return iso; + + err: + hpsb_iso_shutdown(iso); + return NULL; +} + +/** + * hpsb_iso_n_ready - returns number of packets ready to send or receive + */ +int hpsb_iso_n_ready(struct hpsb_iso *iso) +{ + unsigned long flags; + int val; + + spin_lock_irqsave(&iso->lock, flags); + val = iso->n_ready_packets; + spin_unlock_irqrestore(&iso->lock, flags); + + return val; +} + +/** + * hpsb_iso_xmit_init - allocate the buffer and DMA context + */ +struct hpsb_iso *hpsb_iso_xmit_init(struct hpsb_host *host, + unsigned int data_buf_size, + unsigned int buf_packets, + int channel, + int speed, + int irq_interval, + void (*callback) (struct hpsb_iso *)) +{ + struct hpsb_iso *iso = hpsb_iso_common_init(host, HPSB_ISO_XMIT, + data_buf_size, buf_packets, + channel, + HPSB_ISO_DMA_DEFAULT, + irq_interval, callback); + if (!iso) + return NULL; + + iso->speed = speed; + + /* tell the driver to start working */ + if (host->driver->isoctl(iso, XMIT_INIT, 0)) + goto err; + + iso->flags |= HPSB_ISO_DRIVER_INIT; + return iso; + + err: + hpsb_iso_shutdown(iso); + return NULL; +} + +/** + * hpsb_iso_recv_init - allocate the buffer and DMA context + * + * Note, if channel = -1, multi-channel receive is enabled. + */ +struct hpsb_iso *hpsb_iso_recv_init(struct hpsb_host *host, + unsigned int data_buf_size, + unsigned int buf_packets, + int channel, + int dma_mode, + int irq_interval, + void (*callback) (struct hpsb_iso *)) +{ + struct hpsb_iso *iso = hpsb_iso_common_init(host, HPSB_ISO_RECV, + data_buf_size, buf_packets, + channel, dma_mode, + irq_interval, callback); + if (!iso) + return NULL; + + /* tell the driver to start working */ + if (host->driver->isoctl(iso, RECV_INIT, 0)) + goto err; + + iso->flags |= HPSB_ISO_DRIVER_INIT; + return iso; + + err: + hpsb_iso_shutdown(iso); + return NULL; +} + +/** + * hpsb_iso_recv_listen_channel + * + * multi-channel only + */ +int hpsb_iso_recv_listen_channel(struct hpsb_iso *iso, unsigned char channel) +{ + if (iso->type != HPSB_ISO_RECV || iso->channel != -1 || channel >= 64) + return -EINVAL; + return iso->host->driver->isoctl(iso, RECV_LISTEN_CHANNEL, channel); +} + +/** + * hpsb_iso_recv_unlisten_channel + * + * multi-channel only + */ +int hpsb_iso_recv_unlisten_channel(struct hpsb_iso *iso, unsigned char channel) +{ + if (iso->type != HPSB_ISO_RECV || iso->channel != -1 || channel >= 64) + return -EINVAL; + return iso->host->driver->isoctl(iso, RECV_UNLISTEN_CHANNEL, channel); +} + +/** + * hpsb_iso_recv_set_channel_mask + * + * multi-channel only + */ +int hpsb_iso_recv_set_channel_mask(struct hpsb_iso *iso, u64 mask) +{ + if (iso->type != HPSB_ISO_RECV || iso->channel != -1) + return -EINVAL; + return iso->host->driver->isoctl(iso, RECV_SET_CHANNEL_MASK, + (unsigned long)&mask); +} + +/** + * hpsb_iso_recv_flush - check for arrival of new packets + * + * check for arrival of new packets immediately (even if irq_interval + * has not yet been reached) + */ +int hpsb_iso_recv_flush(struct hpsb_iso *iso) +{ + if (iso->type != HPSB_ISO_RECV) + return -EINVAL; + return iso->host->driver->isoctl(iso, RECV_FLUSH, 0); +} + +static int do_iso_xmit_start(struct hpsb_iso *iso, int cycle) +{ + int retval = iso->host->driver->isoctl(iso, XMIT_START, cycle); + if (retval) + return retval; + + iso->flags |= HPSB_ISO_DRIVER_STARTED; + return retval; +} + +/** + * hpsb_iso_xmit_start - start DMA + */ +int hpsb_iso_xmit_start(struct hpsb_iso *iso, int cycle, int prebuffer) +{ + if (iso->type != HPSB_ISO_XMIT) + return -1; + + if (iso->flags & HPSB_ISO_DRIVER_STARTED) + return 0; + + if (cycle < -1) + cycle = -1; + else if (cycle >= 8000) + cycle %= 8000; + + iso->xmit_cycle = cycle; + + if (prebuffer < 0) + prebuffer = iso->buf_packets - 1; + else if (prebuffer == 0) + prebuffer = 1; + + if (prebuffer >= iso->buf_packets) + prebuffer = iso->buf_packets - 1; + + iso->prebuffer = prebuffer; + + /* remember the starting cycle; DMA will commence from xmit_queue_packets() + once enough packets have been buffered */ + iso->start_cycle = cycle; + + return 0; +} + +/** + * hpsb_iso_recv_start - start DMA + */ +int hpsb_iso_recv_start(struct hpsb_iso *iso, int cycle, int tag_mask, int sync) +{ + int retval = 0; + int isoctl_args[3]; + + if (iso->type != HPSB_ISO_RECV) + return -1; + + if (iso->flags & HPSB_ISO_DRIVER_STARTED) + return 0; + + if (cycle < -1) + cycle = -1; + else if (cycle >= 8000) + cycle %= 8000; + + isoctl_args[0] = cycle; + + if (tag_mask < 0) + /* match all tags */ + tag_mask = 0xF; + isoctl_args[1] = tag_mask; + + isoctl_args[2] = sync; + + retval = + iso->host->driver->isoctl(iso, RECV_START, + (unsigned long)&isoctl_args[0]); + if (retval) + return retval; + + iso->flags |= HPSB_ISO_DRIVER_STARTED; + return retval; +} + +/* check to make sure the user has not supplied bogus values of offset/len + * that would cause the kernel to access memory outside the buffer */ +static int hpsb_iso_check_offset_len(struct hpsb_iso *iso, + unsigned int offset, unsigned short len, + unsigned int *out_offset, + unsigned short *out_len) +{ + if (offset >= iso->buf_size) + return -EFAULT; + + /* make sure the packet does not go beyond the end of the buffer */ + if (offset + len > iso->buf_size) + return -EFAULT; + + /* check for wrap-around */ + if (offset + len < offset) + return -EFAULT; + + /* now we can trust 'offset' and 'length' */ + *out_offset = offset; + *out_len = len; + + return 0; +} + +/** + * hpsb_iso_xmit_queue_packet - queue a packet for transmission. + * + * @offset is relative to the beginning of the DMA buffer, where the packet's + * data payload should already have been placed. + */ +int hpsb_iso_xmit_queue_packet(struct hpsb_iso *iso, u32 offset, u16 len, + u8 tag, u8 sy) +{ + struct hpsb_iso_packet_info *info; + unsigned long flags; + int rv; + + if (iso->type != HPSB_ISO_XMIT) + return -EINVAL; + + /* is there space in the buffer? */ + if (iso->n_ready_packets <= 0) { + return -EBUSY; + } + + info = &iso->infos[iso->first_packet]; + + /* check for bogus offset/length */ + if (hpsb_iso_check_offset_len + (iso, offset, len, &info->offset, &info->len)) + return -EFAULT; + + info->tag = tag; + info->sy = sy; + + spin_lock_irqsave(&iso->lock, flags); + + rv = iso->host->driver->isoctl(iso, XMIT_QUEUE, (unsigned long)info); + if (rv) + goto out; + + /* increment cursors */ + iso->first_packet = (iso->first_packet + 1) % iso->buf_packets; + iso->xmit_cycle = (iso->xmit_cycle + 1) % 8000; + iso->n_ready_packets--; + + if (iso->prebuffer != 0) { + iso->prebuffer--; + if (iso->prebuffer <= 0) { + iso->prebuffer = 0; + rv = do_iso_xmit_start(iso, iso->start_cycle); + } + } + + out: + spin_unlock_irqrestore(&iso->lock, flags); + return rv; +} + +/** + * hpsb_iso_xmit_sync - wait until all queued packets have been transmitted + */ +int hpsb_iso_xmit_sync(struct hpsb_iso *iso) +{ + if (iso->type != HPSB_ISO_XMIT) + return -EINVAL; + + return wait_event_interruptible(iso->waitq, + hpsb_iso_n_ready(iso) == + iso->buf_packets); +} + +/** + * hpsb_iso_packet_sent + * + * Available to low-level drivers. + * + * Call after a packet has been transmitted to the bus (interrupt context is + * OK). @cycle is the _exact_ cycle the packet was sent on. @error should be + * non-zero if some sort of error occurred when sending the packet. + */ +void hpsb_iso_packet_sent(struct hpsb_iso *iso, int cycle, int error) +{ + unsigned long flags; + spin_lock_irqsave(&iso->lock, flags); + + /* predict the cycle of the next packet to be queued */ + + /* jump ahead by the number of packets that are already buffered */ + cycle += iso->buf_packets - iso->n_ready_packets; + cycle %= 8000; + + iso->xmit_cycle = cycle; + iso->n_ready_packets++; + iso->pkt_dma = (iso->pkt_dma + 1) % iso->buf_packets; + + if (iso->n_ready_packets == iso->buf_packets || error != 0) { + /* the buffer has run empty! */ + atomic_inc(&iso->overflows); + } + + spin_unlock_irqrestore(&iso->lock, flags); +} + +/** + * hpsb_iso_packet_received + * + * Available to low-level drivers. + * + * Call after a packet has been received (interrupt context is OK). + */ +void hpsb_iso_packet_received(struct hpsb_iso *iso, u32 offset, u16 len, + u16 total_len, u16 cycle, u8 channel, u8 tag, + u8 sy) +{ + unsigned long flags; + spin_lock_irqsave(&iso->lock, flags); + + if (iso->n_ready_packets == iso->buf_packets) { + /* overflow! */ + atomic_inc(&iso->overflows); + /* Record size of this discarded packet */ + iso->bytes_discarded += total_len; + } else { + struct hpsb_iso_packet_info *info = &iso->infos[iso->pkt_dma]; + info->offset = offset; + info->len = len; + info->total_len = total_len; + info->cycle = cycle; + info->channel = channel; + info->tag = tag; + info->sy = sy; + + iso->pkt_dma = (iso->pkt_dma + 1) % iso->buf_packets; + iso->n_ready_packets++; + } + + spin_unlock_irqrestore(&iso->lock, flags); +} + +/** + * hpsb_iso_recv_release_packets - release packets, reuse buffer + * + * @n_packets have been read out of the buffer, re-use the buffer space + */ +int hpsb_iso_recv_release_packets(struct hpsb_iso *iso, unsigned int n_packets) +{ + unsigned long flags; + unsigned int i; + int rv = 0; + + if (iso->type != HPSB_ISO_RECV) + return -1; + + spin_lock_irqsave(&iso->lock, flags); + for (i = 0; i < n_packets; i++) { + rv = iso->host->driver->isoctl(iso, RECV_RELEASE, + (unsigned long)&iso->infos[iso-> + first_packet]); + if (rv) + break; + + iso->first_packet = (iso->first_packet + 1) % iso->buf_packets; + iso->n_ready_packets--; + + /* release memory from packets discarded when queue was full */ + if (iso->n_ready_packets == 0) { /* Release only after all prior packets handled */ + if (iso->bytes_discarded != 0) { + struct hpsb_iso_packet_info inf; + inf.total_len = iso->bytes_discarded; + iso->host->driver->isoctl(iso, RECV_RELEASE, + (unsigned long)&inf); + iso->bytes_discarded = 0; + } + } + } + spin_unlock_irqrestore(&iso->lock, flags); + return rv; +} + +/** + * hpsb_iso_wake + * + * Available to low-level drivers. + * + * Call to wake waiting processes after buffer space has opened up. + */ +void hpsb_iso_wake(struct hpsb_iso *iso) +{ + wake_up_interruptible(&iso->waitq); + + if (iso->callback) + iso->callback(iso); +} diff --git a/drivers/ieee1394/iso.h b/drivers/ieee1394/iso.h new file mode 100644 index 0000000..b5de5f2 --- /dev/null +++ b/drivers/ieee1394/iso.h @@ -0,0 +1,194 @@ +/* + * IEEE 1394 for Linux + * + * kernel ISO transmission/reception + * + * Copyright (C) 2002 Maas Digital LLC + * + * This code is licensed under the GPL. See the file COPYING in the root + * directory of the kernel sources for details. + */ + +#ifndef IEEE1394_ISO_H +#define IEEE1394_ISO_H + +#include <linux/spinlock_types.h> +#include <asm/atomic.h> +#include <asm/types.h> + +#include "dma.h" + +struct hpsb_host; + +/* high-level ISO interface */ + +/* + * This API sends and receives isochronous packets on a large, + * virtually-contiguous kernel memory buffer. The buffer may be mapped + * into a user-space process for zero-copy transmission and reception. + * + * There are no explicit boundaries between packets in the buffer. A + * packet may be transmitted or received at any location. However, + * low-level drivers may impose certain restrictions on alignment or + * size of packets. (e.g. in OHCI no packet may cross a page boundary, + * and packets should be quadlet-aligned) + */ + +/* Packet descriptor - the API maintains a ring buffer of these packet + * descriptors in kernel memory (hpsb_iso.infos[]). */ +struct hpsb_iso_packet_info { + /* offset of data payload relative to the first byte of the buffer */ + __u32 offset; + + /* length of the data payload, in bytes (not including the isochronous + * header) */ + __u16 len; + + /* (recv only) the cycle number (mod 8000) on which the packet was + * received */ + __u16 cycle; + + /* (recv only) channel on which the packet was received */ + __u8 channel; + + /* 2-bit 'tag' and 4-bit 'sy' fields of the isochronous header */ + __u8 tag; + __u8 sy; + + /* length in bytes of the packet including header/trailer. + * MUST be at structure end, since the first part of this structure is + * also defined in raw1394.h (i.e. struct raw1394_iso_packet_info), is + * copied to userspace and is accessed there through libraw1394. */ + __u16 total_len; +}; + +enum hpsb_iso_type { HPSB_ISO_RECV = 0, HPSB_ISO_XMIT = 1 }; + +/* The mode of the dma when receiving iso data. Must be supported by chip */ +enum raw1394_iso_dma_recv_mode { + HPSB_ISO_DMA_DEFAULT = -1, + HPSB_ISO_DMA_OLD_ABI = 0, + HPSB_ISO_DMA_BUFFERFILL = 1, + HPSB_ISO_DMA_PACKET_PER_BUFFER = 2 +}; + +struct hpsb_iso { + enum hpsb_iso_type type; + + /* pointer to low-level driver and its private data */ + struct hpsb_host *host; + void *hostdata; + + /* a function to be called (from interrupt context) after + * outgoing packets have been sent, or incoming packets have + * arrived */ + void (*callback)(struct hpsb_iso*); + + /* wait for buffer space */ + wait_queue_head_t waitq; + + int speed; /* IEEE1394_SPEED_100, 200, or 400 */ + int channel; /* -1 if multichannel */ + int dma_mode; /* dma receive mode */ + + + /* greatest # of packets between interrupts - controls + * the maximum latency of the buffer */ + int irq_interval; + + /* the buffer for packet data payloads */ + struct dma_region data_buf; + + /* size of data_buf, in bytes (always a multiple of PAGE_SIZE) */ + unsigned int buf_size; + + /* # of packets in the ringbuffer */ + unsigned int buf_packets; + + /* protects packet cursors */ + spinlock_t lock; + + /* the index of the next packet that will be produced + or consumed by the user */ + int first_packet; + + /* the index of the next packet that will be transmitted + or received by the 1394 hardware */ + int pkt_dma; + + /* how many packets, starting at first_packet: + * (transmit) are ready to be filled with data + * (receive) contain received data */ + int n_ready_packets; + + /* how many times the buffer has overflowed or underflowed */ + atomic_t overflows; + /* how many cycles were skipped for a given context */ + atomic_t skips; + + /* Current number of bytes lost in discarded packets */ + int bytes_discarded; + + /* private flags to track initialization progress */ +#define HPSB_ISO_DRIVER_INIT (1<<0) +#define HPSB_ISO_DRIVER_STARTED (1<<1) + unsigned int flags; + + /* # of packets left to prebuffer (xmit only) */ + int prebuffer; + + /* starting cycle for DMA (xmit only) */ + int start_cycle; + + /* cycle at which next packet will be transmitted, + * -1 if not known */ + int xmit_cycle; + + /* ringbuffer of packet descriptors in regular kernel memory + * XXX Keep this last, since we use over-allocated memory from + * this entry to fill this field. */ + struct hpsb_iso_packet_info *infos; +}; + +/* functions available to high-level drivers (e.g. raw1394) */ + +struct hpsb_iso* hpsb_iso_xmit_init(struct hpsb_host *host, + unsigned int data_buf_size, + unsigned int buf_packets, + int channel, + int speed, + int irq_interval, + void (*callback)(struct hpsb_iso*)); +struct hpsb_iso* hpsb_iso_recv_init(struct hpsb_host *host, + unsigned int data_buf_size, + unsigned int buf_packets, + int channel, + int dma_mode, + int irq_interval, + void (*callback)(struct hpsb_iso*)); +int hpsb_iso_recv_listen_channel(struct hpsb_iso *iso, unsigned char channel); +int hpsb_iso_recv_unlisten_channel(struct hpsb_iso *iso, unsigned char channel); +int hpsb_iso_recv_set_channel_mask(struct hpsb_iso *iso, u64 mask); +int hpsb_iso_xmit_start(struct hpsb_iso *iso, int start_on_cycle, + int prebuffer); +int hpsb_iso_recv_start(struct hpsb_iso *iso, int start_on_cycle, + int tag_mask, int sync); +void hpsb_iso_stop(struct hpsb_iso *iso); +void hpsb_iso_shutdown(struct hpsb_iso *iso); +int hpsb_iso_xmit_queue_packet(struct hpsb_iso *iso, u32 offset, u16 len, + u8 tag, u8 sy); +int hpsb_iso_xmit_sync(struct hpsb_iso *iso); +int hpsb_iso_recv_release_packets(struct hpsb_iso *recv, + unsigned int n_packets); +int hpsb_iso_recv_flush(struct hpsb_iso *iso); +int hpsb_iso_n_ready(struct hpsb_iso *iso); + +/* the following are callbacks available to low-level drivers */ + +void hpsb_iso_packet_sent(struct hpsb_iso *iso, int cycle, int error); +void hpsb_iso_packet_received(struct hpsb_iso *iso, u32 offset, u16 len, + u16 total_len, u16 cycle, u8 channel, u8 tag, + u8 sy); +void hpsb_iso_wake(struct hpsb_iso *iso); + +#endif /* IEEE1394_ISO_H */ diff --git a/drivers/ieee1394/nodemgr.c b/drivers/ieee1394/nodemgr.c new file mode 100644 index 0000000..79ef5fd --- /dev/null +++ b/drivers/ieee1394/nodemgr.c @@ -0,0 +1,1905 @@ +/* + * Node information (ConfigROM) collection and management. + * + * Copyright (C) 2000 Andreas E. Bombe + * 2001-2003 Ben Collins <bcollins@debian.net> + * + * This code is licensed under the GPL. See the file COPYING in the root + * directory of the kernel sources for details. + */ + +#include <linux/bitmap.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/kthread.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/mutex.h> +#include <linux/freezer.h> +#include <linux/semaphore.h> +#include <asm/atomic.h> + +#include "csr.h" +#include "highlevel.h" +#include "hosts.h" +#include "ieee1394.h" +#include "ieee1394_core.h" +#include "ieee1394_hotplug.h" +#include "ieee1394_types.h" +#include "ieee1394_transactions.h" +#include "nodemgr.h" + +static int ignore_drivers; +module_param(ignore_drivers, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(ignore_drivers, "Disable automatic probing for drivers."); + +struct nodemgr_csr_info { + struct hpsb_host *host; + nodeid_t nodeid; + unsigned int generation; + unsigned int speed_unverified:1; +}; + + +/* + * Correct the speed map entry. This is necessary + * - for nodes with link speed < phy speed, + * - for 1394b nodes with negotiated phy port speed < IEEE1394_SPEED_MAX. + * A possible speed is determined by trial and error, using quadlet reads. + */ +static int nodemgr_check_speed(struct nodemgr_csr_info *ci, u64 addr, + quadlet_t *buffer) +{ + quadlet_t q; + u8 i, *speed, old_speed, good_speed; + int error; + + speed = &(ci->host->speed[NODEID_TO_NODE(ci->nodeid)]); + old_speed = *speed; + good_speed = IEEE1394_SPEED_MAX + 1; + + /* Try every speed from S100 to old_speed. + * If we did it the other way around, a too low speed could be caught + * if the retry succeeded for some other reason, e.g. because the link + * just finished its initialization. */ + for (i = IEEE1394_SPEED_100; i <= old_speed; i++) { + *speed = i; + error = hpsb_read(ci->host, ci->nodeid, ci->generation, addr, + &q, sizeof(quadlet_t)); + if (error) + break; + *buffer = q; + good_speed = i; + } + if (good_speed <= IEEE1394_SPEED_MAX) { + HPSB_DEBUG("Speed probe of node " NODE_BUS_FMT " yields %s", + NODE_BUS_ARGS(ci->host, ci->nodeid), + hpsb_speedto_str[good_speed]); + *speed = good_speed; + ci->speed_unverified = 0; + return 0; + } + *speed = old_speed; + return error; +} + +static int nodemgr_bus_read(struct csr1212_csr *csr, u64 addr, u16 length, + void *buffer, void *__ci) +{ + struct nodemgr_csr_info *ci = (struct nodemgr_csr_info*)__ci; + int i, error; + + for (i = 1; ; i++) { + error = hpsb_read(ci->host, ci->nodeid, ci->generation, addr, + buffer, length); + if (!error) { + ci->speed_unverified = 0; + break; + } + /* Give up after 3rd failure. */ + if (i == 3) + break; + + /* The ieee1394_core guessed the node's speed capability from + * the self ID. Check whether a lower speed works. */ + if (ci->speed_unverified && length == sizeof(quadlet_t)) { + error = nodemgr_check_speed(ci, addr, buffer); + if (!error) + break; + } + if (msleep_interruptible(334)) + return -EINTR; + } + return error; +} + +#define OUI_FREECOM_TECHNOLOGIES_GMBH 0x0001db + +static int nodemgr_get_max_rom(quadlet_t *bus_info_data, void *__ci) +{ + /* Freecom FireWire Hard Drive firmware bug */ + if (be32_to_cpu(bus_info_data[3]) >> 8 == OUI_FREECOM_TECHNOLOGIES_GMBH) + return 0; + + return (be32_to_cpu(bus_info_data[2]) >> 8) & 0x3; +} + +static struct csr1212_bus_ops nodemgr_csr_ops = { + .bus_read = nodemgr_bus_read, + .get_max_rom = nodemgr_get_max_rom +}; + + +/* + * Basically what we do here is start off retrieving the bus_info block. + * From there will fill in some info about the node, verify it is of IEEE + * 1394 type, and that the crc checks out ok. After that we start off with + * the root directory, and subdirectories. To do this, we retrieve the + * quadlet header for a directory, find out the length, and retrieve the + * complete directory entry (be it a leaf or a directory). We then process + * it and add the info to our structure for that particular node. + * + * We verify CRC's along the way for each directory/block/leaf. The entire + * node structure is generic, and simply stores the information in a way + * that's easy to parse by the protocol interface. + */ + +/* + * The nodemgr relies heavily on the Driver Model for device callbacks and + * driver/device mappings. The old nodemgr used to handle all this itself, + * but now we are much simpler because of the LDM. + */ + +struct host_info { + struct hpsb_host *host; + struct list_head list; + struct task_struct *thread; +}; + +static int nodemgr_bus_match(struct device * dev, struct device_driver * drv); +static int nodemgr_uevent(struct device *dev, struct kobj_uevent_env *env); + +struct bus_type ieee1394_bus_type = { + .name = "ieee1394", + .match = nodemgr_bus_match, +}; + +static void host_cls_release(struct device *dev) +{ + put_device(&container_of((dev), struct hpsb_host, host_dev)->device); +} + +struct class hpsb_host_class = { + .name = "ieee1394_host", + .dev_release = host_cls_release, +}; + +static void ne_cls_release(struct device *dev) +{ + put_device(&container_of((dev), struct node_entry, node_dev)->device); +} + +static struct class nodemgr_ne_class = { + .name = "ieee1394_node", + .dev_release = ne_cls_release, +}; + +static void ud_cls_release(struct device *dev) +{ + put_device(&container_of((dev), struct unit_directory, unit_dev)->device); +} + +/* The name here is only so that unit directory hotplug works with old + * style hotplug, which only ever did unit directories anyway. + */ +static struct class nodemgr_ud_class = { + .name = "ieee1394", + .dev_release = ud_cls_release, + .dev_uevent = nodemgr_uevent, +}; + +static struct hpsb_highlevel nodemgr_highlevel; + + +static void nodemgr_release_ud(struct device *dev) +{ + struct unit_directory *ud = container_of(dev, struct unit_directory, device); + + if (ud->vendor_name_kv) + csr1212_release_keyval(ud->vendor_name_kv); + if (ud->model_name_kv) + csr1212_release_keyval(ud->model_name_kv); + + kfree(ud); +} + +static void nodemgr_release_ne(struct device *dev) +{ + struct node_entry *ne = container_of(dev, struct node_entry, device); + + if (ne->vendor_name_kv) + csr1212_release_keyval(ne->vendor_name_kv); + + kfree(ne); +} + + +static void nodemgr_release_host(struct device *dev) +{ + struct hpsb_host *host = container_of(dev, struct hpsb_host, device); + + csr1212_destroy_csr(host->csr.rom); + + kfree(host); +} + +static int nodemgr_ud_platform_data; + +static struct device nodemgr_dev_template_ud = { + .bus = &ieee1394_bus_type, + .release = nodemgr_release_ud, + .platform_data = &nodemgr_ud_platform_data, +}; + +static struct device nodemgr_dev_template_ne = { + .bus = &ieee1394_bus_type, + .release = nodemgr_release_ne, +}; + +/* This dummy driver prevents the host devices from being scanned. We have no + * useful drivers for them yet, and there would be a deadlock possible if the + * driver core scans the host device while the host's low-level driver (i.e. + * the host's parent device) is being removed. */ +static struct device_driver nodemgr_mid_layer_driver = { + .bus = &ieee1394_bus_type, + .name = "nodemgr", + .owner = THIS_MODULE, +}; + +struct device nodemgr_dev_template_host = { + .bus = &ieee1394_bus_type, + .release = nodemgr_release_host, +}; + + +#define fw_attr(class, class_type, field, type, format_string) \ +static ssize_t fw_show_##class##_##field (struct device *dev, struct device_attribute *attr, char *buf)\ +{ \ + class_type *class; \ + class = container_of(dev, class_type, device); \ + return sprintf(buf, format_string, (type)class->field); \ +} \ +static struct device_attribute dev_attr_##class##_##field = { \ + .attr = {.name = __stringify(field), .mode = S_IRUGO }, \ + .show = fw_show_##class##_##field, \ +}; + +#define fw_attr_td(class, class_type, td_kv) \ +static ssize_t fw_show_##class##_##td_kv (struct device *dev, struct device_attribute *attr, char *buf)\ +{ \ + int len; \ + class_type *class = container_of(dev, class_type, device); \ + len = (class->td_kv->value.leaf.len - 2) * sizeof(quadlet_t); \ + memcpy(buf, \ + CSR1212_TEXTUAL_DESCRIPTOR_LEAF_DATA(class->td_kv), \ + len); \ + while (buf[len - 1] == '\0') \ + len--; \ + buf[len++] = '\n'; \ + buf[len] = '\0'; \ + return len; \ +} \ +static struct device_attribute dev_attr_##class##_##td_kv = { \ + .attr = {.name = __stringify(td_kv), .mode = S_IRUGO }, \ + .show = fw_show_##class##_##td_kv, \ +}; + + +#define fw_drv_attr(field, type, format_string) \ +static ssize_t fw_drv_show_##field (struct device_driver *drv, char *buf) \ +{ \ + struct hpsb_protocol_driver *driver; \ + driver = container_of(drv, struct hpsb_protocol_driver, driver); \ + return sprintf(buf, format_string, (type)driver->field);\ +} \ +static struct driver_attribute driver_attr_drv_##field = { \ + .attr = {.name = __stringify(field), .mode = S_IRUGO }, \ + .show = fw_drv_show_##field, \ +}; + + +static ssize_t fw_show_ne_bus_options(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct node_entry *ne = container_of(dev, struct node_entry, device); + + return sprintf(buf, "IRMC(%d) CMC(%d) ISC(%d) BMC(%d) PMC(%d) GEN(%d) " + "LSPD(%d) MAX_REC(%d) MAX_ROM(%d) CYC_CLK_ACC(%d)\n", + ne->busopt.irmc, + ne->busopt.cmc, ne->busopt.isc, ne->busopt.bmc, + ne->busopt.pmc, ne->busopt.generation, ne->busopt.lnkspd, + ne->busopt.max_rec, + ne->busopt.max_rom, + ne->busopt.cyc_clk_acc); +} +static DEVICE_ATTR(bus_options,S_IRUGO,fw_show_ne_bus_options,NULL); + + +#ifdef HPSB_DEBUG_TLABELS +static ssize_t fw_show_ne_tlabels_free(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct node_entry *ne = container_of(dev, struct node_entry, device); + unsigned long flags; + unsigned long *tp = ne->host->tl_pool[NODEID_TO_NODE(ne->nodeid)].map; + int tf; + + spin_lock_irqsave(&hpsb_tlabel_lock, flags); + tf = 64 - bitmap_weight(tp, 64); + spin_unlock_irqrestore(&hpsb_tlabel_lock, flags); + + return sprintf(buf, "%d\n", tf); +} +static DEVICE_ATTR(tlabels_free,S_IRUGO,fw_show_ne_tlabels_free,NULL); + + +static ssize_t fw_show_ne_tlabels_mask(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct node_entry *ne = container_of(dev, struct node_entry, device); + unsigned long flags; + unsigned long *tp = ne->host->tl_pool[NODEID_TO_NODE(ne->nodeid)].map; + u64 tm; + + spin_lock_irqsave(&hpsb_tlabel_lock, flags); +#if (BITS_PER_LONG <= 32) + tm = ((u64)tp[0] << 32) + tp[1]; +#else + tm = tp[0]; +#endif + spin_unlock_irqrestore(&hpsb_tlabel_lock, flags); + + return sprintf(buf, "0x%016llx\n", (unsigned long long)tm); +} +static DEVICE_ATTR(tlabels_mask, S_IRUGO, fw_show_ne_tlabels_mask, NULL); +#endif /* HPSB_DEBUG_TLABELS */ + + +static ssize_t fw_set_ignore_driver(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct unit_directory *ud = container_of(dev, struct unit_directory, device); + int state = simple_strtoul(buf, NULL, 10); + + if (state == 1) { + ud->ignore_driver = 1; + device_release_driver(dev); + } else if (state == 0) + ud->ignore_driver = 0; + + return count; +} +static ssize_t fw_get_ignore_driver(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct unit_directory *ud = container_of(dev, struct unit_directory, device); + + return sprintf(buf, "%d\n", ud->ignore_driver); +} +static DEVICE_ATTR(ignore_driver, S_IWUSR | S_IRUGO, fw_get_ignore_driver, fw_set_ignore_driver); + + +static ssize_t fw_set_rescan(struct bus_type *bus, const char *buf, + size_t count) +{ + int error = 0; + + if (simple_strtoul(buf, NULL, 10) == 1) + error = bus_rescan_devices(&ieee1394_bus_type); + return error ? error : count; +} +static ssize_t fw_get_rescan(struct bus_type *bus, char *buf) +{ + return sprintf(buf, "You can force a rescan of the bus for " + "drivers by writing a 1 to this file\n"); +} +static BUS_ATTR(rescan, S_IWUSR | S_IRUGO, fw_get_rescan, fw_set_rescan); + + +static ssize_t fw_set_ignore_drivers(struct bus_type *bus, const char *buf, size_t count) +{ + int state = simple_strtoul(buf, NULL, 10); + + if (state == 1) + ignore_drivers = 1; + else if (state == 0) + ignore_drivers = 0; + + return count; +} +static ssize_t fw_get_ignore_drivers(struct bus_type *bus, char *buf) +{ + return sprintf(buf, "%d\n", ignore_drivers); +} +static BUS_ATTR(ignore_drivers, S_IWUSR | S_IRUGO, fw_get_ignore_drivers, fw_set_ignore_drivers); + + +struct bus_attribute *const fw_bus_attrs[] = { + &bus_attr_rescan, + &bus_attr_ignore_drivers, + NULL +}; + + +fw_attr(ne, struct node_entry, capabilities, unsigned int, "0x%06x\n") +fw_attr(ne, struct node_entry, nodeid, unsigned int, "0x%04x\n") + +fw_attr(ne, struct node_entry, vendor_id, unsigned int, "0x%06x\n") +fw_attr_td(ne, struct node_entry, vendor_name_kv) + +fw_attr(ne, struct node_entry, guid, unsigned long long, "0x%016Lx\n") +fw_attr(ne, struct node_entry, guid_vendor_id, unsigned int, "0x%06x\n") +fw_attr(ne, struct node_entry, in_limbo, int, "%d\n"); + +static struct device_attribute *const fw_ne_attrs[] = { + &dev_attr_ne_guid, + &dev_attr_ne_guid_vendor_id, + &dev_attr_ne_capabilities, + &dev_attr_ne_vendor_id, + &dev_attr_ne_nodeid, + &dev_attr_bus_options, +#ifdef HPSB_DEBUG_TLABELS + &dev_attr_tlabels_free, + &dev_attr_tlabels_mask, +#endif +}; + + + +fw_attr(ud, struct unit_directory, address, unsigned long long, "0x%016Lx\n") +fw_attr(ud, struct unit_directory, length, int, "%d\n") +/* These are all dependent on the value being provided */ +fw_attr(ud, struct unit_directory, vendor_id, unsigned int, "0x%06x\n") +fw_attr(ud, struct unit_directory, model_id, unsigned int, "0x%06x\n") +fw_attr(ud, struct unit_directory, specifier_id, unsigned int, "0x%06x\n") +fw_attr(ud, struct unit_directory, version, unsigned int, "0x%06x\n") +fw_attr_td(ud, struct unit_directory, vendor_name_kv) +fw_attr_td(ud, struct unit_directory, model_name_kv) + +static struct device_attribute *const fw_ud_attrs[] = { + &dev_attr_ud_address, + &dev_attr_ud_length, + &dev_attr_ignore_driver, +}; + + +fw_attr(host, struct hpsb_host, node_count, int, "%d\n") +fw_attr(host, struct hpsb_host, selfid_count, int, "%d\n") +fw_attr(host, struct hpsb_host, nodes_active, int, "%d\n") +fw_attr(host, struct hpsb_host, in_bus_reset, int, "%d\n") +fw_attr(host, struct hpsb_host, is_root, int, "%d\n") +fw_attr(host, struct hpsb_host, is_cycmst, int, "%d\n") +fw_attr(host, struct hpsb_host, is_irm, int, "%d\n") +fw_attr(host, struct hpsb_host, is_busmgr, int, "%d\n") + +static struct device_attribute *const fw_host_attrs[] = { + &dev_attr_host_node_count, + &dev_attr_host_selfid_count, + &dev_attr_host_nodes_active, + &dev_attr_host_in_bus_reset, + &dev_attr_host_is_root, + &dev_attr_host_is_cycmst, + &dev_attr_host_is_irm, + &dev_attr_host_is_busmgr, +}; + + +static ssize_t fw_show_drv_device_ids(struct device_driver *drv, char *buf) +{ + struct hpsb_protocol_driver *driver; + struct ieee1394_device_id *id; + int length = 0; + char *scratch = buf; + + driver = container_of(drv, struct hpsb_protocol_driver, driver); + id = driver->id_table; + if (!id) + return 0; + + for (; id->match_flags != 0; id++) { + int need_coma = 0; + + if (id->match_flags & IEEE1394_MATCH_VENDOR_ID) { + length += sprintf(scratch, "vendor_id=0x%06x", id->vendor_id); + scratch = buf + length; + need_coma++; + } + + if (id->match_flags & IEEE1394_MATCH_MODEL_ID) { + length += sprintf(scratch, "%smodel_id=0x%06x", + need_coma++ ? "," : "", + id->model_id); + scratch = buf + length; + } + + if (id->match_flags & IEEE1394_MATCH_SPECIFIER_ID) { + length += sprintf(scratch, "%sspecifier_id=0x%06x", + need_coma++ ? "," : "", + id->specifier_id); + scratch = buf + length; + } + + if (id->match_flags & IEEE1394_MATCH_VERSION) { + length += sprintf(scratch, "%sversion=0x%06x", + need_coma++ ? "," : "", + id->version); + scratch = buf + length; + } + + if (need_coma) { + *scratch++ = '\n'; + length++; + } + } + + return length; +} +static DRIVER_ATTR(device_ids,S_IRUGO,fw_show_drv_device_ids,NULL); + + +fw_drv_attr(name, const char *, "%s\n") + +static struct driver_attribute *const fw_drv_attrs[] = { + &driver_attr_drv_name, + &driver_attr_device_ids, +}; + + +static void nodemgr_create_drv_files(struct hpsb_protocol_driver *driver) +{ + struct device_driver *drv = &driver->driver; + int i; + + for (i = 0; i < ARRAY_SIZE(fw_drv_attrs); i++) + if (driver_create_file(drv, fw_drv_attrs[i])) + goto fail; + return; +fail: + HPSB_ERR("Failed to add sysfs attribute"); +} + + +static void nodemgr_remove_drv_files(struct hpsb_protocol_driver *driver) +{ + struct device_driver *drv = &driver->driver; + int i; + + for (i = 0; i < ARRAY_SIZE(fw_drv_attrs); i++) + driver_remove_file(drv, fw_drv_attrs[i]); +} + + +static void nodemgr_create_ne_dev_files(struct node_entry *ne) +{ + struct device *dev = &ne->device; + int i; + + for (i = 0; i < ARRAY_SIZE(fw_ne_attrs); i++) + if (device_create_file(dev, fw_ne_attrs[i])) + goto fail; + return; +fail: + HPSB_ERR("Failed to add sysfs attribute"); +} + + +static void nodemgr_create_host_dev_files(struct hpsb_host *host) +{ + struct device *dev = &host->device; + int i; + + for (i = 0; i < ARRAY_SIZE(fw_host_attrs); i++) + if (device_create_file(dev, fw_host_attrs[i])) + goto fail; + return; +fail: + HPSB_ERR("Failed to add sysfs attribute"); +} + + +static struct node_entry *find_entry_by_nodeid(struct hpsb_host *host, + nodeid_t nodeid); + +static void nodemgr_update_host_dev_links(struct hpsb_host *host) +{ + struct device *dev = &host->device; + struct node_entry *ne; + + sysfs_remove_link(&dev->kobj, "irm_id"); + sysfs_remove_link(&dev->kobj, "busmgr_id"); + sysfs_remove_link(&dev->kobj, "host_id"); + + if ((ne = find_entry_by_nodeid(host, host->irm_id)) && + sysfs_create_link(&dev->kobj, &ne->device.kobj, "irm_id")) + goto fail; + if ((ne = find_entry_by_nodeid(host, host->busmgr_id)) && + sysfs_create_link(&dev->kobj, &ne->device.kobj, "busmgr_id")) + goto fail; + if ((ne = find_entry_by_nodeid(host, host->node_id)) && + sysfs_create_link(&dev->kobj, &ne->device.kobj, "host_id")) + goto fail; + return; +fail: + HPSB_ERR("Failed to update sysfs attributes for host %d", host->id); +} + +static void nodemgr_create_ud_dev_files(struct unit_directory *ud) +{ + struct device *dev = &ud->device; + int i; + + for (i = 0; i < ARRAY_SIZE(fw_ud_attrs); i++) + if (device_create_file(dev, fw_ud_attrs[i])) + goto fail; + if (ud->flags & UNIT_DIRECTORY_SPECIFIER_ID) + if (device_create_file(dev, &dev_attr_ud_specifier_id)) + goto fail; + if (ud->flags & UNIT_DIRECTORY_VERSION) + if (device_create_file(dev, &dev_attr_ud_version)) + goto fail; + if (ud->flags & UNIT_DIRECTORY_VENDOR_ID) { + if (device_create_file(dev, &dev_attr_ud_vendor_id)) + goto fail; + if (ud->vendor_name_kv && + device_create_file(dev, &dev_attr_ud_vendor_name_kv)) + goto fail; + } + if (ud->flags & UNIT_DIRECTORY_MODEL_ID) { + if (device_create_file(dev, &dev_attr_ud_model_id)) + goto fail; + if (ud->model_name_kv && + device_create_file(dev, &dev_attr_ud_model_name_kv)) + goto fail; + } + return; +fail: + HPSB_ERR("Failed to add sysfs attribute"); +} + + +static int nodemgr_bus_match(struct device * dev, struct device_driver * drv) +{ + struct hpsb_protocol_driver *driver; + struct unit_directory *ud; + struct ieee1394_device_id *id; + + /* We only match unit directories */ + if (dev->platform_data != &nodemgr_ud_platform_data) + return 0; + + ud = container_of(dev, struct unit_directory, device); + if (ud->ne->in_limbo || ud->ignore_driver) + return 0; + + /* We only match drivers of type hpsb_protocol_driver */ + if (drv == &nodemgr_mid_layer_driver) + return 0; + + driver = container_of(drv, struct hpsb_protocol_driver, driver); + id = driver->id_table; + if (!id) + return 0; + + for (; id->match_flags != 0; id++) { + if ((id->match_flags & IEEE1394_MATCH_VENDOR_ID) && + id->vendor_id != ud->vendor_id) + continue; + + if ((id->match_flags & IEEE1394_MATCH_MODEL_ID) && + id->model_id != ud->model_id) + continue; + + if ((id->match_flags & IEEE1394_MATCH_SPECIFIER_ID) && + id->specifier_id != ud->specifier_id) + continue; + + if ((id->match_flags & IEEE1394_MATCH_VERSION) && + id->version != ud->version) + continue; + + return 1; + } + + return 0; +} + + +static DEFINE_MUTEX(nodemgr_serialize_remove_uds); + +static int match_ne(struct device *dev, void *data) +{ + struct unit_directory *ud; + struct node_entry *ne = data; + + ud = container_of(dev, struct unit_directory, unit_dev); + return ud->ne == ne; +} + +static void nodemgr_remove_uds(struct node_entry *ne) +{ + struct device *dev; + struct unit_directory *ud; + + /* Use class_find device to iterate the devices. Since this code + * may be called from other contexts besides the knodemgrds, + * protect it by nodemgr_serialize_remove_uds. + */ + mutex_lock(&nodemgr_serialize_remove_uds); + for (;;) { + dev = class_find_device(&nodemgr_ud_class, NULL, ne, match_ne); + if (!dev) + break; + ud = container_of(dev, struct unit_directory, unit_dev); + put_device(dev); + device_unregister(&ud->unit_dev); + device_unregister(&ud->device); + } + mutex_unlock(&nodemgr_serialize_remove_uds); +} + + +static void nodemgr_remove_ne(struct node_entry *ne) +{ + struct device *dev; + + dev = get_device(&ne->device); + if (!dev) + return; + + HPSB_DEBUG("Node removed: ID:BUS[" NODE_BUS_FMT "] GUID[%016Lx]", + NODE_BUS_ARGS(ne->host, ne->nodeid), (unsigned long long)ne->guid); + nodemgr_remove_uds(ne); + + device_unregister(&ne->node_dev); + device_unregister(dev); + + put_device(dev); +} + +static int remove_host_dev(struct device *dev, void *data) +{ + if (dev->bus == &ieee1394_bus_type) + nodemgr_remove_ne(container_of(dev, struct node_entry, + device)); + return 0; +} + +static void nodemgr_remove_host_dev(struct device *dev) +{ + device_for_each_child(dev, NULL, remove_host_dev); + sysfs_remove_link(&dev->kobj, "irm_id"); + sysfs_remove_link(&dev->kobj, "busmgr_id"); + sysfs_remove_link(&dev->kobj, "host_id"); +} + + +static void nodemgr_update_bus_options(struct node_entry *ne) +{ +#ifdef CONFIG_IEEE1394_VERBOSEDEBUG + static const u16 mr[] = { 4, 64, 1024, 0}; +#endif + quadlet_t busoptions = be32_to_cpu(ne->csr->bus_info_data[2]); + + ne->busopt.irmc = (busoptions >> 31) & 1; + ne->busopt.cmc = (busoptions >> 30) & 1; + ne->busopt.isc = (busoptions >> 29) & 1; + ne->busopt.bmc = (busoptions >> 28) & 1; + ne->busopt.pmc = (busoptions >> 27) & 1; + ne->busopt.cyc_clk_acc = (busoptions >> 16) & 0xff; + ne->busopt.max_rec = 1 << (((busoptions >> 12) & 0xf) + 1); + ne->busopt.max_rom = (busoptions >> 8) & 0x3; + ne->busopt.generation = (busoptions >> 4) & 0xf; + ne->busopt.lnkspd = busoptions & 0x7; + + HPSB_VERBOSE("NodeMgr: raw=0x%08x irmc=%d cmc=%d isc=%d bmc=%d pmc=%d " + "cyc_clk_acc=%d max_rec=%d max_rom=%d gen=%d lspd=%d", + busoptions, ne->busopt.irmc, ne->busopt.cmc, + ne->busopt.isc, ne->busopt.bmc, ne->busopt.pmc, + ne->busopt.cyc_clk_acc, ne->busopt.max_rec, + mr[ne->busopt.max_rom], + ne->busopt.generation, ne->busopt.lnkspd); +} + + +static struct node_entry *nodemgr_create_node(octlet_t guid, + struct csr1212_csr *csr, struct hpsb_host *host, + nodeid_t nodeid, unsigned int generation) +{ + struct node_entry *ne; + + ne = kzalloc(sizeof(*ne), GFP_KERNEL); + if (!ne) + goto fail_alloc; + + ne->host = host; + ne->nodeid = nodeid; + ne->generation = generation; + ne->needs_probe = true; + + ne->guid = guid; + ne->guid_vendor_id = (guid >> 40) & 0xffffff; + ne->csr = csr; + + memcpy(&ne->device, &nodemgr_dev_template_ne, + sizeof(ne->device)); + ne->device.parent = &host->device; + dev_set_name(&ne->device, "%016Lx", (unsigned long long)(ne->guid)); + + ne->node_dev.parent = &ne->device; + ne->node_dev.class = &nodemgr_ne_class; + dev_set_name(&ne->node_dev, "%016Lx", (unsigned long long)(ne->guid)); + + if (device_register(&ne->device)) + goto fail_devreg; + if (device_register(&ne->node_dev)) + goto fail_classdevreg; + get_device(&ne->device); + + nodemgr_create_ne_dev_files(ne); + + nodemgr_update_bus_options(ne); + + HPSB_DEBUG("%s added: ID:BUS[" NODE_BUS_FMT "] GUID[%016Lx]", + (host->node_id == nodeid) ? "Host" : "Node", + NODE_BUS_ARGS(host, nodeid), (unsigned long long)guid); + + return ne; + +fail_classdevreg: + device_unregister(&ne->device); +fail_devreg: + kfree(ne); +fail_alloc: + HPSB_ERR("Failed to create node ID:BUS[" NODE_BUS_FMT "] GUID[%016Lx]", + NODE_BUS_ARGS(host, nodeid), (unsigned long long)guid); + + return NULL; +} + +static int match_ne_guid(struct device *dev, void *data) +{ + struct node_entry *ne; + u64 *guid = data; + + ne = container_of(dev, struct node_entry, node_dev); + return ne->guid == *guid; +} + +static struct node_entry *find_entry_by_guid(u64 guid) +{ + struct device *dev; + struct node_entry *ne; + + dev = class_find_device(&nodemgr_ne_class, NULL, &guid, match_ne_guid); + if (!dev) + return NULL; + ne = container_of(dev, struct node_entry, node_dev); + put_device(dev); + + return ne; +} + +struct match_nodeid_parameter { + struct hpsb_host *host; + nodeid_t nodeid; +}; + +static int match_ne_nodeid(struct device *dev, void *data) +{ + int found = 0; + struct node_entry *ne; + struct match_nodeid_parameter *p = data; + + if (!dev) + goto ret; + ne = container_of(dev, struct node_entry, node_dev); + if (ne->host == p->host && ne->nodeid == p->nodeid) + found = 1; +ret: + return found; +} + +static struct node_entry *find_entry_by_nodeid(struct hpsb_host *host, + nodeid_t nodeid) +{ + struct device *dev; + struct node_entry *ne; + struct match_nodeid_parameter p; + + p.host = host; + p.nodeid = nodeid; + + dev = class_find_device(&nodemgr_ne_class, NULL, &p, match_ne_nodeid); + if (!dev) + return NULL; + ne = container_of(dev, struct node_entry, node_dev); + put_device(dev); + + return ne; +} + + +static void nodemgr_register_device(struct node_entry *ne, + struct unit_directory *ud, struct device *parent) +{ + memcpy(&ud->device, &nodemgr_dev_template_ud, + sizeof(ud->device)); + + ud->device.parent = parent; + + dev_set_name(&ud->device, "%s-%u", dev_name(&ne->device), ud->id); + + ud->unit_dev.parent = &ud->device; + ud->unit_dev.class = &nodemgr_ud_class; + dev_set_name(&ud->unit_dev, "%s-%u", dev_name(&ne->device), ud->id); + + if (device_register(&ud->device)) + goto fail_devreg; + if (device_register(&ud->unit_dev)) + goto fail_classdevreg; + get_device(&ud->device); + + nodemgr_create_ud_dev_files(ud); + + return; + +fail_classdevreg: + device_unregister(&ud->device); +fail_devreg: + HPSB_ERR("Failed to create unit %s", dev_name(&ud->device)); +} + + +/* This implementation currently only scans the config rom and its + * immediate unit directories looking for software_id and + * software_version entries, in order to get driver autoloading working. */ +static struct unit_directory *nodemgr_process_unit_directory + (struct node_entry *ne, struct csr1212_keyval *ud_kv, + unsigned int *id, struct unit_directory *parent) +{ + struct unit_directory *ud; + struct unit_directory *ud_child = NULL; + struct csr1212_dentry *dentry; + struct csr1212_keyval *kv; + u8 last_key_id = 0; + + ud = kzalloc(sizeof(*ud), GFP_KERNEL); + if (!ud) + goto unit_directory_error; + + ud->ne = ne; + ud->ignore_driver = ignore_drivers; + ud->address = ud_kv->offset + CSR1212_REGISTER_SPACE_BASE; + ud->directory_id = ud->address & 0xffffff; + ud->ud_kv = ud_kv; + ud->id = (*id)++; + + csr1212_for_each_dir_entry(ne->csr, kv, ud_kv, dentry) { + switch (kv->key.id) { + case CSR1212_KV_ID_VENDOR: + if (kv->key.type == CSR1212_KV_TYPE_IMMEDIATE) { + ud->vendor_id = kv->value.immediate; + ud->flags |= UNIT_DIRECTORY_VENDOR_ID; + } + break; + + case CSR1212_KV_ID_MODEL: + ud->model_id = kv->value.immediate; + ud->flags |= UNIT_DIRECTORY_MODEL_ID; + break; + + case CSR1212_KV_ID_SPECIFIER_ID: + ud->specifier_id = kv->value.immediate; + ud->flags |= UNIT_DIRECTORY_SPECIFIER_ID; + break; + + case CSR1212_KV_ID_VERSION: + ud->version = kv->value.immediate; + ud->flags |= UNIT_DIRECTORY_VERSION; + break; + + case CSR1212_KV_ID_DESCRIPTOR: + if (kv->key.type == CSR1212_KV_TYPE_LEAF && + CSR1212_DESCRIPTOR_LEAF_TYPE(kv) == 0 && + CSR1212_DESCRIPTOR_LEAF_SPECIFIER_ID(kv) == 0 && + CSR1212_TEXTUAL_DESCRIPTOR_LEAF_WIDTH(kv) == 0 && + CSR1212_TEXTUAL_DESCRIPTOR_LEAF_CHAR_SET(kv) == 0 && + CSR1212_TEXTUAL_DESCRIPTOR_LEAF_LANGUAGE(kv) == 0) { + switch (last_key_id) { + case CSR1212_KV_ID_VENDOR: + csr1212_keep_keyval(kv); + ud->vendor_name_kv = kv; + break; + + case CSR1212_KV_ID_MODEL: + csr1212_keep_keyval(kv); + ud->model_name_kv = kv; + break; + + } + } /* else if (kv->key.type == CSR1212_KV_TYPE_DIRECTORY) ... */ + break; + + case CSR1212_KV_ID_DEPENDENT_INFO: + /* Logical Unit Number */ + if (kv->key.type == CSR1212_KV_TYPE_IMMEDIATE) { + if (ud->flags & UNIT_DIRECTORY_HAS_LUN) { + ud_child = kmemdup(ud, sizeof(*ud_child), GFP_KERNEL); + if (!ud_child) + goto unit_directory_error; + nodemgr_register_device(ne, ud_child, &ne->device); + ud_child = NULL; + + ud->id = (*id)++; + } + ud->lun = kv->value.immediate; + ud->flags |= UNIT_DIRECTORY_HAS_LUN; + + /* Logical Unit Directory */ + } else if (kv->key.type == CSR1212_KV_TYPE_DIRECTORY) { + /* This should really be done in SBP2 as this is + * doing SBP2 specific parsing. + */ + + /* first register the parent unit */ + ud->flags |= UNIT_DIRECTORY_HAS_LUN_DIRECTORY; + if (ud->device.bus != &ieee1394_bus_type) + nodemgr_register_device(ne, ud, &ne->device); + + /* process the child unit */ + ud_child = nodemgr_process_unit_directory(ne, kv, id, ud); + + if (ud_child == NULL) + break; + + /* inherit unspecified values, the driver core picks it up */ + if ((ud->flags & UNIT_DIRECTORY_MODEL_ID) && + !(ud_child->flags & UNIT_DIRECTORY_MODEL_ID)) + { + ud_child->flags |= UNIT_DIRECTORY_MODEL_ID; + ud_child->model_id = ud->model_id; + } + if ((ud->flags & UNIT_DIRECTORY_SPECIFIER_ID) && + !(ud_child->flags & UNIT_DIRECTORY_SPECIFIER_ID)) + { + ud_child->flags |= UNIT_DIRECTORY_SPECIFIER_ID; + ud_child->specifier_id = ud->specifier_id; + } + if ((ud->flags & UNIT_DIRECTORY_VERSION) && + !(ud_child->flags & UNIT_DIRECTORY_VERSION)) + { + ud_child->flags |= UNIT_DIRECTORY_VERSION; + ud_child->version = ud->version; + } + + /* register the child unit */ + ud_child->flags |= UNIT_DIRECTORY_LUN_DIRECTORY; + nodemgr_register_device(ne, ud_child, &ud->device); + } + + break; + + case CSR1212_KV_ID_DIRECTORY_ID: + ud->directory_id = kv->value.immediate; + break; + + default: + break; + } + last_key_id = kv->key.id; + } + + /* do not process child units here and only if not already registered */ + if (!parent && ud->device.bus != &ieee1394_bus_type) + nodemgr_register_device(ne, ud, &ne->device); + + return ud; + +unit_directory_error: + kfree(ud); + return NULL; +} + + +static void nodemgr_process_root_directory(struct node_entry *ne) +{ + unsigned int ud_id = 0; + struct csr1212_dentry *dentry; + struct csr1212_keyval *kv, *vendor_name_kv = NULL; + u8 last_key_id = 0; + + ne->needs_probe = false; + + csr1212_for_each_dir_entry(ne->csr, kv, ne->csr->root_kv, dentry) { + switch (kv->key.id) { + case CSR1212_KV_ID_VENDOR: + ne->vendor_id = kv->value.immediate; + break; + + case CSR1212_KV_ID_NODE_CAPABILITIES: + ne->capabilities = kv->value.immediate; + break; + + case CSR1212_KV_ID_UNIT: + nodemgr_process_unit_directory(ne, kv, &ud_id, NULL); + break; + + case CSR1212_KV_ID_DESCRIPTOR: + if (last_key_id == CSR1212_KV_ID_VENDOR) { + if (kv->key.type == CSR1212_KV_TYPE_LEAF && + CSR1212_DESCRIPTOR_LEAF_TYPE(kv) == 0 && + CSR1212_DESCRIPTOR_LEAF_SPECIFIER_ID(kv) == 0 && + CSR1212_TEXTUAL_DESCRIPTOR_LEAF_WIDTH(kv) == 0 && + CSR1212_TEXTUAL_DESCRIPTOR_LEAF_CHAR_SET(kv) == 0 && + CSR1212_TEXTUAL_DESCRIPTOR_LEAF_LANGUAGE(kv) == 0) { + csr1212_keep_keyval(kv); + vendor_name_kv = kv; + } + } + break; + } + last_key_id = kv->key.id; + } + + if (ne->vendor_name_kv) { + kv = ne->vendor_name_kv; + ne->vendor_name_kv = vendor_name_kv; + csr1212_release_keyval(kv); + } else if (vendor_name_kv) { + ne->vendor_name_kv = vendor_name_kv; + if (device_create_file(&ne->device, + &dev_attr_ne_vendor_name_kv) != 0) + HPSB_ERR("Failed to add sysfs attribute"); + } +} + +#ifdef CONFIG_HOTPLUG + +static int nodemgr_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct unit_directory *ud; + int retval = 0; + /* ieee1394:venNmoNspNverN */ + char buf[8 + 1 + 3 + 8 + 2 + 8 + 2 + 8 + 3 + 8 + 1]; + + if (!dev) + return -ENODEV; + + ud = container_of(dev, struct unit_directory, unit_dev); + + if (ud->ne->in_limbo || ud->ignore_driver) + return -ENODEV; + +#define PUT_ENVP(fmt,val) \ +do { \ + retval = add_uevent_var(env, fmt, val); \ + if (retval) \ + return retval; \ +} while (0) + + PUT_ENVP("VENDOR_ID=%06x", ud->vendor_id); + PUT_ENVP("MODEL_ID=%06x", ud->model_id); + PUT_ENVP("GUID=%016Lx", (unsigned long long)ud->ne->guid); + PUT_ENVP("SPECIFIER_ID=%06x", ud->specifier_id); + PUT_ENVP("VERSION=%06x", ud->version); + snprintf(buf, sizeof(buf), "ieee1394:ven%08Xmo%08Xsp%08Xver%08X", + ud->vendor_id, + ud->model_id, + ud->specifier_id, + ud->version); + PUT_ENVP("MODALIAS=%s", buf); + +#undef PUT_ENVP + + return 0; +} + +#else + +static int nodemgr_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + return -ENODEV; +} + +#endif /* CONFIG_HOTPLUG */ + + +int __hpsb_register_protocol(struct hpsb_protocol_driver *drv, + struct module *owner) +{ + int error; + + drv->driver.bus = &ieee1394_bus_type; + drv->driver.owner = owner; + drv->driver.name = drv->name; + + /* This will cause a probe for devices */ + error = driver_register(&drv->driver); + if (!error) + nodemgr_create_drv_files(drv); + return error; +} + +void hpsb_unregister_protocol(struct hpsb_protocol_driver *driver) +{ + nodemgr_remove_drv_files(driver); + /* This will subsequently disconnect all devices that our driver + * is attached to. */ + driver_unregister(&driver->driver); +} + + +/* + * This function updates nodes that were present on the bus before the + * reset and still are after the reset. The nodeid and the config rom + * may have changed, and the drivers managing this device must be + * informed that this device just went through a bus reset, to allow + * the to take whatever actions required. + */ +static void nodemgr_update_node(struct node_entry *ne, struct csr1212_csr *csr, + nodeid_t nodeid, unsigned int generation) +{ + if (ne->nodeid != nodeid) { + HPSB_DEBUG("Node changed: " NODE_BUS_FMT " -> " NODE_BUS_FMT, + NODE_BUS_ARGS(ne->host, ne->nodeid), + NODE_BUS_ARGS(ne->host, nodeid)); + ne->nodeid = nodeid; + } + + if (ne->busopt.generation != ((be32_to_cpu(csr->bus_info_data[2]) >> 4) & 0xf)) { + kfree(ne->csr->private); + csr1212_destroy_csr(ne->csr); + ne->csr = csr; + + /* If the node's configrom generation has changed, we + * unregister all the unit directories. */ + nodemgr_remove_uds(ne); + + nodemgr_update_bus_options(ne); + + /* Mark the node as new, so it gets re-probed */ + ne->needs_probe = true; + } else { + /* old cache is valid, so update its generation */ + struct nodemgr_csr_info *ci = ne->csr->private; + ci->generation = generation; + /* free the partially filled now unneeded new cache */ + kfree(csr->private); + csr1212_destroy_csr(csr); + } + + /* Mark the node current */ + ne->generation = generation; + + if (ne->in_limbo) { + device_remove_file(&ne->device, &dev_attr_ne_in_limbo); + ne->in_limbo = false; + + HPSB_DEBUG("Node reactivated: " + "ID:BUS[" NODE_BUS_FMT "] GUID[%016Lx]", + NODE_BUS_ARGS(ne->host, ne->nodeid), + (unsigned long long)ne->guid); + } +} + +static void nodemgr_node_scan_one(struct hpsb_host *host, + nodeid_t nodeid, int generation) +{ + struct node_entry *ne; + octlet_t guid; + struct csr1212_csr *csr; + struct nodemgr_csr_info *ci; + u8 *speed; + + ci = kmalloc(sizeof(*ci), GFP_KERNEL); + if (!ci) + return; + + ci->host = host; + ci->nodeid = nodeid; + ci->generation = generation; + + /* Prepare for speed probe which occurs when reading the ROM */ + speed = &(host->speed[NODEID_TO_NODE(nodeid)]); + if (*speed > host->csr.lnk_spd) + *speed = host->csr.lnk_spd; + ci->speed_unverified = *speed > IEEE1394_SPEED_100; + + /* We need to detect when the ConfigROM's generation has changed, + * so we only update the node's info when it needs to be. */ + + csr = csr1212_create_csr(&nodemgr_csr_ops, 5 * sizeof(quadlet_t), ci); + if (!csr || csr1212_parse_csr(csr) != CSR1212_SUCCESS) { + HPSB_ERR("Error parsing configrom for node " NODE_BUS_FMT, + NODE_BUS_ARGS(host, nodeid)); + if (csr) + csr1212_destroy_csr(csr); + kfree(ci); + return; + } + + if (csr->bus_info_data[1] != IEEE1394_BUSID_MAGIC) { + /* This isn't a 1394 device, but we let it slide. There + * was a report of a device with broken firmware which + * reported '2394' instead of '1394', which is obviously a + * mistake. One would hope that a non-1394 device never + * gets connected to Firewire bus. If someone does, we + * shouldn't be held responsible, so we'll allow it with a + * warning. */ + HPSB_WARN("Node " NODE_BUS_FMT " has invalid busID magic [0x%08x]", + NODE_BUS_ARGS(host, nodeid), csr->bus_info_data[1]); + } + + guid = ((u64)be32_to_cpu(csr->bus_info_data[3]) << 32) | be32_to_cpu(csr->bus_info_data[4]); + ne = find_entry_by_guid(guid); + + if (ne && ne->host != host && ne->in_limbo) { + /* Must have moved this device from one host to another */ + nodemgr_remove_ne(ne); + ne = NULL; + } + + if (!ne) + nodemgr_create_node(guid, csr, host, nodeid, generation); + else + nodemgr_update_node(ne, csr, nodeid, generation); +} + + +static void nodemgr_node_scan(struct hpsb_host *host, int generation) +{ + int count; + struct selfid *sid = (struct selfid *)host->topology_map; + nodeid_t nodeid = LOCAL_BUS; + + /* Scan each node on the bus */ + for (count = host->selfid_count; count; count--, sid++) { + if (sid->extended) + continue; + + if (!sid->link_active) { + nodeid++; + continue; + } + nodemgr_node_scan_one(host, nodeid++, generation); + } +} + +static void nodemgr_pause_ne(struct node_entry *ne) +{ + HPSB_DEBUG("Node paused: ID:BUS[" NODE_BUS_FMT "] GUID[%016Lx]", + NODE_BUS_ARGS(ne->host, ne->nodeid), + (unsigned long long)ne->guid); + + ne->in_limbo = true; + WARN_ON(device_create_file(&ne->device, &dev_attr_ne_in_limbo)); +} + +static int update_pdrv(struct device *dev, void *data) +{ + struct unit_directory *ud; + struct device_driver *drv; + struct hpsb_protocol_driver *pdrv; + struct node_entry *ne = data; + int error; + + ud = container_of(dev, struct unit_directory, unit_dev); + if (ud->ne == ne) { + drv = get_driver(ud->device.driver); + if (drv) { + error = 0; + pdrv = container_of(drv, struct hpsb_protocol_driver, + driver); + if (pdrv->update) { + down(&ud->device.sem); + error = pdrv->update(ud); + up(&ud->device.sem); + } + if (error) + device_release_driver(&ud->device); + put_driver(drv); + } + } + + return 0; +} + +static void nodemgr_update_pdrv(struct node_entry *ne) +{ + class_for_each_device(&nodemgr_ud_class, NULL, ne, update_pdrv); +} + +/* Write the BROADCAST_CHANNEL as per IEEE1394a 8.3.2.3.11 and 8.4.2.3. This + * seems like an optional service but in the end it is practically mandatory + * as a consequence of these clauses. + * + * Note that we cannot do a broadcast write to all nodes at once because some + * pre-1394a devices would hang. */ +static void nodemgr_irm_write_bc(struct node_entry *ne, int generation) +{ + const u64 bc_addr = (CSR_REGISTER_BASE | CSR_BROADCAST_CHANNEL); + quadlet_t bc_remote, bc_local; + int error; + + if (!ne->host->is_irm || ne->generation != generation || + ne->nodeid == ne->host->node_id) + return; + + bc_local = cpu_to_be32(ne->host->csr.broadcast_channel); + + /* Check if the register is implemented and 1394a compliant. */ + error = hpsb_read(ne->host, ne->nodeid, generation, bc_addr, &bc_remote, + sizeof(bc_remote)); + if (!error && bc_remote & cpu_to_be32(0x80000000) && + bc_remote != bc_local) + hpsb_node_write(ne, bc_addr, &bc_local, sizeof(bc_local)); +} + + +static void nodemgr_probe_ne(struct hpsb_host *host, struct node_entry *ne, + int generation) +{ + struct device *dev; + + if (ne->host != host || ne->in_limbo) + return; + + dev = get_device(&ne->device); + if (!dev) + return; + + nodemgr_irm_write_bc(ne, generation); + + /* If "needs_probe", then this is either a new or changed node we + * rescan totally. If the generation matches for an existing node + * (one that existed prior to the bus reset) we send update calls + * down to the drivers. Otherwise, this is a dead node and we + * suspend it. */ + if (ne->needs_probe) + nodemgr_process_root_directory(ne); + else if (ne->generation == generation) + nodemgr_update_pdrv(ne); + else + nodemgr_pause_ne(ne); + + put_device(dev); +} + +struct node_probe_parameter { + struct hpsb_host *host; + int generation; + bool probe_now; +}; + +static int node_probe(struct device *dev, void *data) +{ + struct node_probe_parameter *p = data; + struct node_entry *ne; + + if (p->generation != get_hpsb_generation(p->host)) + return -EAGAIN; + + ne = container_of(dev, struct node_entry, node_dev); + if (ne->needs_probe == p->probe_now) + nodemgr_probe_ne(p->host, ne, p->generation); + return 0; +} + +static int nodemgr_node_probe(struct hpsb_host *host, int generation) +{ + struct node_probe_parameter p; + + p.host = host; + p.generation = generation; + /* + * Do some processing of the nodes we've probed. This pulls them + * into the sysfs layer if needed, and can result in processing of + * unit-directories, or just updating the node and it's + * unit-directories. + * + * Run updates before probes. Usually, updates are time-critical + * while probes are time-consuming. + * + * Meanwhile, another bus reset may have happened. In this case we + * skip everything here and let the next bus scan handle it. + * Otherwise we may prematurely remove nodes which are still there. + */ + p.probe_now = false; + if (class_for_each_device(&nodemgr_ne_class, NULL, &p, node_probe) != 0) + return 0; + + p.probe_now = true; + if (class_for_each_device(&nodemgr_ne_class, NULL, &p, node_probe) != 0) + return 0; + /* + * Now let's tell the bus to rescan our devices. This may seem + * like overhead, but the driver-model core will only scan a + * device for a driver when either the device is added, or when a + * new driver is added. A bus reset is a good reason to rescan + * devices that were there before. For example, an sbp2 device + * may become available for login, if the host that held it was + * just removed. + */ + if (bus_rescan_devices(&ieee1394_bus_type) != 0) + HPSB_DEBUG("bus_rescan_devices had an error"); + + return 1; +} + +static int remove_nodes_in_limbo(struct device *dev, void *data) +{ + struct node_entry *ne; + + if (dev->bus != &ieee1394_bus_type) + return 0; + + ne = container_of(dev, struct node_entry, device); + if (ne->in_limbo) + nodemgr_remove_ne(ne); + + return 0; +} + +static void nodemgr_remove_nodes_in_limbo(struct hpsb_host *host) +{ + device_for_each_child(&host->device, NULL, remove_nodes_in_limbo); +} + +static int nodemgr_send_resume_packet(struct hpsb_host *host) +{ + struct hpsb_packet *packet; + int error = -ENOMEM; + + packet = hpsb_make_phypacket(host, + EXTPHYPACKET_TYPE_RESUME | + NODEID_TO_NODE(host->node_id) << PHYPACKET_PORT_SHIFT); + if (packet) { + packet->no_waiter = 1; + packet->generation = get_hpsb_generation(host); + error = hpsb_send_packet(packet); + } + if (error) + HPSB_WARN("fw-host%d: Failed to broadcast resume packet", + host->id); + return error; +} + +/* Perform a few high-level IRM responsibilities. */ +static int nodemgr_do_irm_duties(struct hpsb_host *host, int cycles) +{ + quadlet_t bc; + + /* if irm_id == -1 then there is no IRM on this bus */ + if (!host->is_irm || host->irm_id == (nodeid_t)-1) + return 1; + + /* We are a 1394a-2000 compliant IRM. Set the validity bit. */ + host->csr.broadcast_channel |= 0x40000000; + + /* If there is no bus manager then we should set the root node's + * force_root bit to promote bus stability per the 1394 + * spec. (8.4.2.6) */ + if (host->busmgr_id == 0xffff && host->node_count > 1) + { + u16 root_node = host->node_count - 1; + + /* get cycle master capability flag from root node */ + if (host->is_cycmst || + (!hpsb_read(host, LOCAL_BUS | root_node, get_hpsb_generation(host), + (CSR_REGISTER_BASE + CSR_CONFIG_ROM + 2 * sizeof(quadlet_t)), + &bc, sizeof(quadlet_t)) && + be32_to_cpu(bc) & 1 << CSR_CMC_SHIFT)) + hpsb_send_phy_config(host, root_node, -1); + else { + HPSB_DEBUG("The root node is not cycle master capable; " + "selecting a new root node and resetting..."); + + if (cycles >= 5) { + /* Oh screw it! Just leave the bus as it is */ + HPSB_DEBUG("Stopping reset loop for IRM sanity"); + return 1; + } + + hpsb_send_phy_config(host, NODEID_TO_NODE(host->node_id), -1); + hpsb_reset_bus(host, LONG_RESET_FORCE_ROOT); + + return 0; + } + } + + /* Some devices suspend their ports while being connected to an inactive + * host adapter, i.e. if connected before the low-level driver is + * loaded. They become visible either when physically unplugged and + * replugged, or when receiving a resume packet. Send one once. */ + if (!host->resume_packet_sent && !nodemgr_send_resume_packet(host)) + host->resume_packet_sent = 1; + + return 1; +} + +/* We need to ensure that if we are not the IRM, that the IRM node is capable of + * everything we can do, otherwise issue a bus reset and try to become the IRM + * ourselves. */ +static int nodemgr_check_irm_capability(struct hpsb_host *host, int cycles) +{ + quadlet_t bc; + int status; + + if (hpsb_disable_irm || host->is_irm) + return 1; + + status = hpsb_read(host, LOCAL_BUS | (host->irm_id), + get_hpsb_generation(host), + (CSR_REGISTER_BASE | CSR_BROADCAST_CHANNEL), + &bc, sizeof(quadlet_t)); + + if (status < 0 || !(be32_to_cpu(bc) & 0x80000000)) { + /* The current irm node does not have a valid BROADCAST_CHANNEL + * register and we do, so reset the bus with force_root set */ + HPSB_DEBUG("Current remote IRM is not 1394a-2000 compliant, resetting..."); + + if (cycles >= 5) { + /* Oh screw it! Just leave the bus as it is */ + HPSB_DEBUG("Stopping reset loop for IRM sanity"); + return 1; + } + + hpsb_send_phy_config(host, NODEID_TO_NODE(host->node_id), -1); + hpsb_reset_bus(host, LONG_RESET_FORCE_ROOT); + + return 0; + } + + return 1; +} + +static int nodemgr_host_thread(void *data) +{ + struct hpsb_host *host = data; + unsigned int g, generation = 0; + int i, reset_cycles = 0; + + set_freezable(); + /* Setup our device-model entries */ + nodemgr_create_host_dev_files(host); + + for (;;) { + /* Sleep until next bus reset */ + set_current_state(TASK_INTERRUPTIBLE); + if (get_hpsb_generation(host) == generation && + !kthread_should_stop()) + schedule(); + __set_current_state(TASK_RUNNING); + + /* Thread may have been woken up to freeze or to exit */ + if (try_to_freeze()) + continue; + if (kthread_should_stop()) + goto exit; + + /* Pause for 1/4 second in 1/16 second intervals, + * to make sure things settle down. */ + g = get_hpsb_generation(host); + for (i = 0; i < 4 ; i++) { + msleep_interruptible(63); + try_to_freeze(); + if (kthread_should_stop()) + goto exit; + + /* Now get the generation in which the node ID's we collect + * are valid. During the bus scan we will use this generation + * for the read transactions, so that if another reset occurs + * during the scan the transactions will fail instead of + * returning bogus data. */ + generation = get_hpsb_generation(host); + + /* If we get a reset before we are done waiting, then + * start the waiting over again */ + if (generation != g) + g = generation, i = 0; + } + + if (!nodemgr_check_irm_capability(host, reset_cycles) || + !nodemgr_do_irm_duties(host, reset_cycles)) { + reset_cycles++; + continue; + } + reset_cycles = 0; + + /* Scan our nodes to get the bus options and create node + * entries. This does not do the sysfs stuff, since that + * would trigger uevents and such, which is a bad idea at + * this point. */ + nodemgr_node_scan(host, generation); + + /* This actually does the full probe, with sysfs + * registration. */ + if (!nodemgr_node_probe(host, generation)) + continue; + + /* Update some of our sysfs symlinks */ + nodemgr_update_host_dev_links(host); + + /* Sleep 3 seconds */ + for (i = 3000/200; i; i--) { + msleep_interruptible(200); + try_to_freeze(); + if (kthread_should_stop()) + goto exit; + + if (generation != get_hpsb_generation(host)) + break; + } + /* Remove nodes which are gone, unless a bus reset happened */ + if (!i) + nodemgr_remove_nodes_in_limbo(host); + } +exit: + HPSB_VERBOSE("NodeMgr: Exiting thread"); + return 0; +} + +struct per_host_parameter { + void *data; + int (*cb)(struct hpsb_host *, void *); +}; + +static int per_host(struct device *dev, void *data) +{ + struct hpsb_host *host; + struct per_host_parameter *p = data; + + host = container_of(dev, struct hpsb_host, host_dev); + return p->cb(host, p->data); +} + +/** + * nodemgr_for_each_host - call a function for each IEEE 1394 host + * @data: an address to supply to the callback + * @cb: function to call for each host + * + * Iterate the hosts, calling a given function with supplied data for each host. + * If the callback fails on a host, i.e. if it returns a non-zero value, the + * iteration is stopped. + * + * Return value: 0 on success, non-zero on failure (same as returned by last run + * of the callback). + */ +int nodemgr_for_each_host(void *data, int (*cb)(struct hpsb_host *, void *)) +{ + struct per_host_parameter p; + + p.cb = cb; + p.data = data; + return class_for_each_device(&hpsb_host_class, NULL, &p, per_host); +} + +/* The following two convenience functions use a struct node_entry + * for addressing a node on the bus. They are intended for use by any + * process context, not just the nodemgr thread, so we need to be a + * little careful when reading out the node ID and generation. The + * thing that can go wrong is that we get the node ID, then a bus + * reset occurs, and then we read the generation. The node ID is + * possibly invalid, but the generation is current, and we end up + * sending a packet to a the wrong node. + * + * The solution is to make sure we read the generation first, so that + * if a reset occurs in the process, we end up with a stale generation + * and the transactions will fail instead of silently using wrong node + * ID's. + */ + +/** + * hpsb_node_fill_packet - fill some destination information into a packet + * @ne: destination node + * @packet: packet to fill in + * + * This will fill in the given, pre-initialised hpsb_packet with the current + * information from the node entry (host, node ID, bus generation number). + */ +void hpsb_node_fill_packet(struct node_entry *ne, struct hpsb_packet *packet) +{ + packet->host = ne->host; + packet->generation = ne->generation; + barrier(); + packet->node_id = ne->nodeid; +} + +int hpsb_node_write(struct node_entry *ne, u64 addr, + quadlet_t *buffer, size_t length) +{ + unsigned int generation = ne->generation; + + barrier(); + return hpsb_write(ne->host, ne->nodeid, generation, + addr, buffer, length); +} + +static void nodemgr_add_host(struct hpsb_host *host) +{ + struct host_info *hi; + + hi = hpsb_create_hostinfo(&nodemgr_highlevel, host, sizeof(*hi)); + if (!hi) { + HPSB_ERR("NodeMgr: out of memory in add host"); + return; + } + hi->host = host; + hi->thread = kthread_run(nodemgr_host_thread, host, "knodemgrd_%d", + host->id); + if (IS_ERR(hi->thread)) { + HPSB_ERR("NodeMgr: cannot start thread for host %d", host->id); + hpsb_destroy_hostinfo(&nodemgr_highlevel, host); + } +} + +static void nodemgr_host_reset(struct hpsb_host *host) +{ + struct host_info *hi = hpsb_get_hostinfo(&nodemgr_highlevel, host); + + if (hi) { + HPSB_VERBOSE("NodeMgr: Processing reset for host %d", host->id); + wake_up_process(hi->thread); + } +} + +static void nodemgr_remove_host(struct hpsb_host *host) +{ + struct host_info *hi = hpsb_get_hostinfo(&nodemgr_highlevel, host); + + if (hi) { + kthread_stop(hi->thread); + nodemgr_remove_host_dev(&host->device); + } +} + +static struct hpsb_highlevel nodemgr_highlevel = { + .name = "Node manager", + .add_host = nodemgr_add_host, + .host_reset = nodemgr_host_reset, + .remove_host = nodemgr_remove_host, +}; + +int init_ieee1394_nodemgr(void) +{ + int error; + + error = class_register(&nodemgr_ne_class); + if (error) + goto fail_ne; + error = class_register(&nodemgr_ud_class); + if (error) + goto fail_ud; + error = driver_register(&nodemgr_mid_layer_driver); + if (error) + goto fail_ml; + /* This driver is not used if nodemgr is off (disable_nodemgr=1). */ + nodemgr_dev_template_host.driver = &nodemgr_mid_layer_driver; + + hpsb_register_highlevel(&nodemgr_highlevel); + return 0; + +fail_ml: + class_unregister(&nodemgr_ud_class); +fail_ud: + class_unregister(&nodemgr_ne_class); +fail_ne: + return error; +} + +void cleanup_ieee1394_nodemgr(void) +{ + hpsb_unregister_highlevel(&nodemgr_highlevel); + driver_unregister(&nodemgr_mid_layer_driver); + class_unregister(&nodemgr_ud_class); + class_unregister(&nodemgr_ne_class); +} diff --git a/drivers/ieee1394/nodemgr.h b/drivers/ieee1394/nodemgr.h new file mode 100644 index 0000000..4f287a3 --- /dev/null +++ b/drivers/ieee1394/nodemgr.h @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2000 Andreas E. Bombe + * 2001 Ben Collins <bcollins@debian.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _IEEE1394_NODEMGR_H +#define _IEEE1394_NODEMGR_H + +#include <linux/device.h> +#include <asm/types.h> + +#include "ieee1394_core.h" +#include "ieee1394_types.h" + +struct csr1212_csr; +struct csr1212_keyval; +struct hpsb_host; +struct ieee1394_device_id; + +/* '1' '3' '9' '4' in ASCII */ +#define IEEE1394_BUSID_MAGIC __constant_cpu_to_be32(0x31333934) + +/* This is the start of a Node entry structure. It should be a stable API + * for which to gather info from the Node Manager about devices attached + * to the bus. */ +struct bus_options { + u8 irmc; /* Iso Resource Manager Capable */ + u8 cmc; /* Cycle Master Capable */ + u8 isc; /* Iso Capable */ + u8 bmc; /* Bus Master Capable */ + u8 pmc; /* Power Manager Capable (PNP spec) */ + u8 cyc_clk_acc; /* Cycle clock accuracy */ + u8 max_rom; /* Maximum block read supported in the CSR */ + u8 generation; /* Incremented when configrom changes */ + u8 lnkspd; /* Link speed */ + u16 max_rec; /* Maximum packet size node can receive */ +}; + +#define UNIT_DIRECTORY_VENDOR_ID 0x01 +#define UNIT_DIRECTORY_MODEL_ID 0x02 +#define UNIT_DIRECTORY_SPECIFIER_ID 0x04 +#define UNIT_DIRECTORY_VERSION 0x08 +#define UNIT_DIRECTORY_HAS_LUN_DIRECTORY 0x10 +#define UNIT_DIRECTORY_LUN_DIRECTORY 0x20 +#define UNIT_DIRECTORY_HAS_LUN 0x40 + +/* + * A unit directory corresponds to a protocol supported by the + * node. If a node supports eg. IP/1394 and AV/C, its config rom has a + * unit directory for each of these protocols. + */ +struct unit_directory { + struct node_entry *ne; /* The node which this directory belongs to */ + octlet_t address; /* Address of the unit directory on the node */ + u8 flags; /* Indicates which entries were read */ + + quadlet_t vendor_id; + struct csr1212_keyval *vendor_name_kv; + + quadlet_t model_id; + struct csr1212_keyval *model_name_kv; + quadlet_t specifier_id; + quadlet_t version; + quadlet_t directory_id; + + unsigned int id; + + int ignore_driver; + + int length; /* Number of quadlets */ + + struct device device; + struct device unit_dev; + + struct csr1212_keyval *ud_kv; + u32 lun; /* logical unit number immediate value */ +}; + +struct node_entry { + u64 guid; /* GUID of this node */ + u32 guid_vendor_id; /* Top 24bits of guid */ + + struct hpsb_host *host; /* Host this node is attached to */ + nodeid_t nodeid; /* NodeID */ + struct bus_options busopt; /* Bus Options */ + bool needs_probe; + unsigned int generation; /* Synced with hpsb generation */ + + /* The following is read from the config rom */ + u32 vendor_id; + struct csr1212_keyval *vendor_name_kv; + + u32 capabilities; + + struct device device; + struct device node_dev; + + /* Means this node is not attached anymore */ + bool in_limbo; + + struct csr1212_csr *csr; +}; + +struct hpsb_protocol_driver { + /* The name of the driver, e.g. SBP2 or IP1394 */ + const char *name; + + /* + * The device id table describing the protocols and/or devices + * supported by this driver. This is used by the nodemgr to + * decide if a driver could support a given node, but the + * probe function below can implement further protocol + * dependent or vendor dependent checking. + */ + struct ieee1394_device_id *id_table; + + /* + * The update function is called when the node has just + * survived a bus reset, i.e. it is still present on the bus. + * However, it may be necessary to reestablish the connection + * or login into the node again, depending on the protocol. If the + * probe fails (returns non-zero), we unbind the driver from this + * device. + */ + int (*update)(struct unit_directory *ud); + + /* Our LDM structure */ + struct device_driver driver; +}; + +int __hpsb_register_protocol(struct hpsb_protocol_driver *, struct module *); +static inline int hpsb_register_protocol(struct hpsb_protocol_driver *driver) +{ + return __hpsb_register_protocol(driver, THIS_MODULE); +} + +void hpsb_unregister_protocol(struct hpsb_protocol_driver *driver); + +static inline int hpsb_node_entry_valid(struct node_entry *ne) +{ + return ne->generation == get_hpsb_generation(ne->host); +} +void hpsb_node_fill_packet(struct node_entry *ne, struct hpsb_packet *packet); +int hpsb_node_write(struct node_entry *ne, u64 addr, + quadlet_t *buffer, size_t length); +int nodemgr_for_each_host(void *data, int (*cb)(struct hpsb_host *, void *)); + +int init_ieee1394_nodemgr(void); +void cleanup_ieee1394_nodemgr(void); + +/* The template for a host device */ +extern struct device nodemgr_dev_template_host; + +/* Bus attributes we export */ +extern struct bus_attribute *const fw_bus_attrs[]; + +#endif /* _IEEE1394_NODEMGR_H */ diff --git a/drivers/ieee1394/ohci1394.c b/drivers/ieee1394/ohci1394.c new file mode 100644 index 0000000..e509e13 --- /dev/null +++ b/drivers/ieee1394/ohci1394.c @@ -0,0 +1,3582 @@ +/* + * ohci1394.c - driver for OHCI 1394 boards + * Copyright (C)1999,2000 Sebastien Rougeaux <sebastien.rougeaux@anu.edu.au> + * Gord Peters <GordPeters@smarttech.com> + * 2001 Ben Collins <bcollins@debian.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * Things known to be working: + * . Async Request Transmit + * . Async Response Receive + * . Async Request Receive + * . Async Response Transmit + * . Iso Receive + * . DMA mmap for iso receive + * . Config ROM generation + * + * Things implemented, but still in test phase: + * . Iso Transmit + * . Async Stream Packets Transmit (Receive done via Iso interface) + * + * Things not implemented: + * . DMA error recovery + * + * Known bugs: + * . devctl BUS_RESET arg confusion (reset type or root holdoff?) + * added LONG_RESET_ROOT and SHORT_RESET_ROOT for root holdoff --kk + */ + +/* + * Acknowledgments: + * + * Adam J Richter <adam@yggdrasil.com> + * . Use of pci_class to find device + * + * Emilie Chung <emilie.chung@axis.com> + * . Tip on Async Request Filter + * + * Pascal Drolet <pascal.drolet@informission.ca> + * . Various tips for optimization and functionnalities + * + * Robert Ficklin <rficklin@westengineering.com> + * . Loop in irq_handler + * + * James Goodwin <jamesg@Filanet.com> + * . Various tips on initialization, self-id reception, etc. + * + * Albrecht Dress <ad@mpifr-bonn.mpg.de> + * . Apple PowerBook detection + * + * Daniel Kobras <daniel.kobras@student.uni-tuebingen.de> + * . Reset the board properly before leaving + misc cleanups + * + * Leon van Stuivenberg <leonvs@iae.nl> + * . Bug fixes + * + * Ben Collins <bcollins@debian.org> + * . Working big-endian support + * . Updated to 2.4.x module scheme (PCI aswell) + * . Config ROM generation + * + * Manfred Weihs <weihs@ict.tuwien.ac.at> + * . Reworked code for initiating bus resets + * (long, short, with or without hold-off) + * + * Nandu Santhi <contactnandu@users.sourceforge.net> + * . Added support for nVidia nForce2 onboard Firewire chipset + * + */ + +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/pci.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <asm/byteorder.h> +#include <asm/atomic.h> +#include <asm/uaccess.h> +#include <linux/delay.h> +#include <linux/spinlock.h> + +#include <asm/pgtable.h> +#include <asm/page.h> +#include <asm/irq.h> +#include <linux/types.h> +#include <linux/vmalloc.h> +#include <linux/init.h> + +#ifdef CONFIG_PPC_PMAC +#include <asm/machdep.h> +#include <asm/pmac_feature.h> +#include <asm/prom.h> +#include <asm/pci-bridge.h> +#endif + +#include "csr1212.h" +#include "ieee1394.h" +#include "ieee1394_types.h" +#include "hosts.h" +#include "dma.h" +#include "iso.h" +#include "ieee1394_core.h" +#include "highlevel.h" +#include "ohci1394.h" + +#ifdef CONFIG_IEEE1394_VERBOSEDEBUG +#define OHCI1394_DEBUG +#endif + +#ifdef DBGMSG +#undef DBGMSG +#endif + +#ifdef OHCI1394_DEBUG +#define DBGMSG(fmt, args...) \ +printk(KERN_INFO "%s: fw-host%d: " fmt "\n" , OHCI1394_DRIVER_NAME, ohci->host->id , ## args) +#else +#define DBGMSG(fmt, args...) do {} while (0) +#endif + +/* print general (card independent) information */ +#define PRINT_G(level, fmt, args...) \ +printk(level "%s: " fmt "\n" , OHCI1394_DRIVER_NAME , ## args) + +/* print card specific information */ +#define PRINT(level, fmt, args...) \ +printk(level "%s: fw-host%d: " fmt "\n" , OHCI1394_DRIVER_NAME, ohci->host->id , ## args) + +/* Module Parameters */ +static int phys_dma = 1; +module_param(phys_dma, int, 0444); +MODULE_PARM_DESC(phys_dma, "Enable physical DMA (default = 1)."); + +static void dma_trm_tasklet(unsigned long data); +static void dma_trm_reset(struct dma_trm_ctx *d); + +static int alloc_dma_rcv_ctx(struct ti_ohci *ohci, struct dma_rcv_ctx *d, + enum context_type type, int ctx, int num_desc, + int buf_size, int split_buf_size, int context_base); +static void free_dma_rcv_ctx(struct dma_rcv_ctx *d); + +static int alloc_dma_trm_ctx(struct ti_ohci *ohci, struct dma_trm_ctx *d, + enum context_type type, int ctx, int num_desc, + int context_base); + +static void ohci1394_pci_remove(struct pci_dev *pdev); + +#ifndef __LITTLE_ENDIAN +static const size_t hdr_sizes[] = { + 3, /* TCODE_WRITEQ */ + 4, /* TCODE_WRITEB */ + 3, /* TCODE_WRITE_RESPONSE */ + 0, /* reserved */ + 3, /* TCODE_READQ */ + 4, /* TCODE_READB */ + 3, /* TCODE_READQ_RESPONSE */ + 4, /* TCODE_READB_RESPONSE */ + 1, /* TCODE_CYCLE_START */ + 4, /* TCODE_LOCK_REQUEST */ + 2, /* TCODE_ISO_DATA */ + 4, /* TCODE_LOCK_RESPONSE */ + /* rest is reserved or link-internal */ +}; + +static inline void header_le32_to_cpu(quadlet_t *data, unsigned char tcode) +{ + size_t size; + + if (unlikely(tcode >= ARRAY_SIZE(hdr_sizes))) + return; + + size = hdr_sizes[tcode]; + while (size--) + data[size] = le32_to_cpu(data[size]); +} +#else +#define header_le32_to_cpu(w,x) do {} while (0) +#endif /* !LITTLE_ENDIAN */ + +/*********************************** + * IEEE-1394 functionality section * + ***********************************/ + +static u8 get_phy_reg(struct ti_ohci *ohci, u8 addr) +{ + int i; + unsigned long flags; + quadlet_t r; + + spin_lock_irqsave (&ohci->phy_reg_lock, flags); + + reg_write(ohci, OHCI1394_PhyControl, (addr << 8) | 0x00008000); + + for (i = 0; i < OHCI_LOOP_COUNT; i++) { + if (reg_read(ohci, OHCI1394_PhyControl) & 0x80000000) + break; + + mdelay(1); + } + + r = reg_read(ohci, OHCI1394_PhyControl); + + if (i >= OHCI_LOOP_COUNT) + PRINT (KERN_ERR, "Get PHY Reg timeout [0x%08x/0x%08x/%d]", + r, r & 0x80000000, i); + + spin_unlock_irqrestore (&ohci->phy_reg_lock, flags); + + return (r & 0x00ff0000) >> 16; +} + +static void set_phy_reg(struct ti_ohci *ohci, u8 addr, u8 data) +{ + int i; + unsigned long flags; + u32 r = 0; + + spin_lock_irqsave (&ohci->phy_reg_lock, flags); + + reg_write(ohci, OHCI1394_PhyControl, (addr << 8) | data | 0x00004000); + + for (i = 0; i < OHCI_LOOP_COUNT; i++) { + r = reg_read(ohci, OHCI1394_PhyControl); + if (!(r & 0x00004000)) + break; + + mdelay(1); + } + + if (i == OHCI_LOOP_COUNT) + PRINT (KERN_ERR, "Set PHY Reg timeout [0x%08x/0x%08x/%d]", + r, r & 0x00004000, i); + + spin_unlock_irqrestore (&ohci->phy_reg_lock, flags); + + return; +} + +/* Or's our value into the current value */ +static void set_phy_reg_mask(struct ti_ohci *ohci, u8 addr, u8 data) +{ + u8 old; + + old = get_phy_reg (ohci, addr); + old |= data; + set_phy_reg (ohci, addr, old); + + return; +} + +static void handle_selfid(struct ti_ohci *ohci, struct hpsb_host *host, + int phyid, int isroot) +{ + quadlet_t *q = ohci->selfid_buf_cpu; + quadlet_t self_id_count=reg_read(ohci, OHCI1394_SelfIDCount); + size_t size; + quadlet_t q0, q1; + + /* Check status of self-id reception */ + + if (ohci->selfid_swap) + q0 = le32_to_cpu(q[0]); + else + q0 = q[0]; + + if ((self_id_count & 0x80000000) || + ((self_id_count & 0x00FF0000) != (q0 & 0x00FF0000))) { + PRINT(KERN_ERR, + "Error in reception of SelfID packets [0x%08x/0x%08x] (count: %d)", + self_id_count, q0, ohci->self_id_errors); + + /* Tip by James Goodwin <jamesg@Filanet.com>: + * We had an error, generate another bus reset in response. */ + if (ohci->self_id_errors<OHCI1394_MAX_SELF_ID_ERRORS) { + set_phy_reg_mask (ohci, 1, 0x40); + ohci->self_id_errors++; + } else { + PRINT(KERN_ERR, + "Too many errors on SelfID error reception, giving up!"); + } + return; + } + + /* SelfID Ok, reset error counter. */ + ohci->self_id_errors = 0; + + size = ((self_id_count & 0x00001FFC) >> 2) - 1; + q++; + + while (size > 0) { + if (ohci->selfid_swap) { + q0 = le32_to_cpu(q[0]); + q1 = le32_to_cpu(q[1]); + } else { + q0 = q[0]; + q1 = q[1]; + } + + if (q0 == ~q1) { + DBGMSG ("SelfID packet 0x%x received", q0); + hpsb_selfid_received(host, cpu_to_be32(q0)); + if (((q0 & 0x3f000000) >> 24) == phyid) + DBGMSG ("SelfID for this node is 0x%08x", q0); + } else { + PRINT(KERN_ERR, + "SelfID is inconsistent [0x%08x/0x%08x]", q0, q1); + } + q += 2; + size -= 2; + } + + DBGMSG("SelfID complete"); + + return; +} + +static void ohci_soft_reset(struct ti_ohci *ohci) { + int i; + + reg_write(ohci, OHCI1394_HCControlSet, OHCI1394_HCControl_softReset); + + for (i = 0; i < OHCI_LOOP_COUNT; i++) { + if (!(reg_read(ohci, OHCI1394_HCControlSet) & OHCI1394_HCControl_softReset)) + break; + mdelay(1); + } + DBGMSG ("Soft reset finished"); +} + + +/* Generate the dma receive prgs and start the context */ +static void initialize_dma_rcv_ctx(struct dma_rcv_ctx *d, int generate_irq) +{ + struct ti_ohci *ohci = (struct ti_ohci*)(d->ohci); + int i; + + ohci1394_stop_context(ohci, d->ctrlClear, NULL); + + for (i=0; i<d->num_desc; i++) { + u32 c; + + c = DMA_CTL_INPUT_MORE | DMA_CTL_UPDATE | DMA_CTL_BRANCH; + if (generate_irq) + c |= DMA_CTL_IRQ; + + d->prg_cpu[i]->control = cpu_to_le32(c | d->buf_size); + + /* End of descriptor list? */ + if (i + 1 < d->num_desc) { + d->prg_cpu[i]->branchAddress = + cpu_to_le32((d->prg_bus[i+1] & 0xfffffff0) | 0x1); + } else { + d->prg_cpu[i]->branchAddress = + cpu_to_le32((d->prg_bus[0] & 0xfffffff0)); + } + + d->prg_cpu[i]->address = cpu_to_le32(d->buf_bus[i]); + d->prg_cpu[i]->status = cpu_to_le32(d->buf_size); + } + + d->buf_ind = 0; + d->buf_offset = 0; + + if (d->type == DMA_CTX_ISO) { + /* Clear contextControl */ + reg_write(ohci, d->ctrlClear, 0xffffffff); + + /* Set bufferFill, isochHeader, multichannel for IR context */ + reg_write(ohci, d->ctrlSet, 0xd0000000); + + /* Set the context match register to match on all tags */ + reg_write(ohci, d->ctxtMatch, 0xf0000000); + + /* Clear the multi channel mask high and low registers */ + reg_write(ohci, OHCI1394_IRMultiChanMaskHiClear, 0xffffffff); + reg_write(ohci, OHCI1394_IRMultiChanMaskLoClear, 0xffffffff); + + /* Set up isoRecvIntMask to generate interrupts */ + reg_write(ohci, OHCI1394_IsoRecvIntMaskSet, 1 << d->ctx); + } + + /* Tell the controller where the first AR program is */ + reg_write(ohci, d->cmdPtr, d->prg_bus[0] | 0x1); + + /* Run context */ + reg_write(ohci, d->ctrlSet, 0x00008000); + + DBGMSG("Receive DMA ctx=%d initialized", d->ctx); +} + +/* Initialize the dma transmit context */ +static void initialize_dma_trm_ctx(struct dma_trm_ctx *d) +{ + struct ti_ohci *ohci = (struct ti_ohci*)(d->ohci); + + /* Stop the context */ + ohci1394_stop_context(ohci, d->ctrlClear, NULL); + + d->prg_ind = 0; + d->sent_ind = 0; + d->free_prgs = d->num_desc; + d->branchAddrPtr = NULL; + INIT_LIST_HEAD(&d->fifo_list); + INIT_LIST_HEAD(&d->pending_list); + + if (d->type == DMA_CTX_ISO) { + /* enable interrupts */ + reg_write(ohci, OHCI1394_IsoXmitIntMaskSet, 1 << d->ctx); + } + + DBGMSG("Transmit DMA ctx=%d initialized", d->ctx); +} + +/* Count the number of available iso contexts */ +static int get_nb_iso_ctx(struct ti_ohci *ohci, int reg) +{ + int i,ctx=0; + u32 tmp; + + reg_write(ohci, reg, 0xffffffff); + tmp = reg_read(ohci, reg); + + DBGMSG("Iso contexts reg: %08x implemented: %08x", reg, tmp); + + /* Count the number of contexts */ + for (i=0; i<32; i++) { + if (tmp & 1) ctx++; + tmp >>= 1; + } + return ctx; +} + +/* Global initialization */ +static void ohci_initialize(struct ti_ohci *ohci) +{ + quadlet_t buf; + int num_ports, i; + + spin_lock_init(&ohci->phy_reg_lock); + + /* Put some defaults to these undefined bus options */ + buf = reg_read(ohci, OHCI1394_BusOptions); + buf |= 0x60000000; /* Enable CMC and ISC */ + if (hpsb_disable_irm) + buf &= ~0x80000000; + else + buf |= 0x80000000; /* Enable IRMC */ + buf &= ~0x00ff0000; /* XXX: Set cyc_clk_acc to zero for now */ + buf &= ~0x18000000; /* Disable PMC and BMC */ + reg_write(ohci, OHCI1394_BusOptions, buf); + + /* Set the bus number */ + reg_write(ohci, OHCI1394_NodeID, 0x0000ffc0); + + /* Enable posted writes */ + reg_write(ohci, OHCI1394_HCControlSet, OHCI1394_HCControl_postedWriteEnable); + + /* Clear link control register */ + reg_write(ohci, OHCI1394_LinkControlClear, 0xffffffff); + + /* Enable cycle timer and cycle master and set the IRM + * contender bit in our self ID packets if appropriate. */ + reg_write(ohci, OHCI1394_LinkControlSet, + OHCI1394_LinkControl_CycleTimerEnable | + OHCI1394_LinkControl_CycleMaster); + i = get_phy_reg(ohci, 4) | PHY_04_LCTRL; + if (hpsb_disable_irm) + i &= ~PHY_04_CONTENDER; + else + i |= PHY_04_CONTENDER; + set_phy_reg(ohci, 4, i); + + /* Set up self-id dma buffer */ + reg_write(ohci, OHCI1394_SelfIDBuffer, ohci->selfid_buf_bus); + + /* enable self-id */ + reg_write(ohci, OHCI1394_LinkControlSet, OHCI1394_LinkControl_RcvSelfID); + + /* Set the Config ROM mapping register */ + reg_write(ohci, OHCI1394_ConfigROMmap, ohci->csr_config_rom_bus); + + /* Now get our max packet size */ + ohci->max_packet_size = + 1<<(((reg_read(ohci, OHCI1394_BusOptions)>>12)&0xf)+1); + + /* Clear the interrupt mask */ + reg_write(ohci, OHCI1394_IsoRecvIntMaskClear, 0xffffffff); + reg_write(ohci, OHCI1394_IsoRecvIntEventClear, 0xffffffff); + + /* Clear the interrupt mask */ + reg_write(ohci, OHCI1394_IsoXmitIntMaskClear, 0xffffffff); + reg_write(ohci, OHCI1394_IsoXmitIntEventClear, 0xffffffff); + + /* Initialize AR dma */ + initialize_dma_rcv_ctx(&ohci->ar_req_context, 0); + initialize_dma_rcv_ctx(&ohci->ar_resp_context, 0); + + /* Initialize AT dma */ + initialize_dma_trm_ctx(&ohci->at_req_context); + initialize_dma_trm_ctx(&ohci->at_resp_context); + + /* Accept AR requests from all nodes */ + reg_write(ohci, OHCI1394_AsReqFilterHiSet, 0x80000000); + + /* Set the address range of the physical response unit. + * Most controllers do not implement it as a writable register though. + * They will keep a hardwired offset of 0x00010000 and show 0x0 as + * register content. + * To actually enable physical responses is the job of our interrupt + * handler which programs the physical request filter. */ + reg_write(ohci, OHCI1394_PhyUpperBound, + OHCI1394_PHYS_UPPER_BOUND_PROGRAMMED >> 16); + + DBGMSG("physUpperBoundOffset=%08x", + reg_read(ohci, OHCI1394_PhyUpperBound)); + + /* Specify AT retries */ + reg_write(ohci, OHCI1394_ATRetries, + OHCI1394_MAX_AT_REQ_RETRIES | + (OHCI1394_MAX_AT_RESP_RETRIES<<4) | + (OHCI1394_MAX_PHYS_RESP_RETRIES<<8)); + + /* We don't want hardware swapping */ + reg_write(ohci, OHCI1394_HCControlClear, OHCI1394_HCControl_noByteSwap); + + /* Enable interrupts */ + reg_write(ohci, OHCI1394_IntMaskSet, + OHCI1394_unrecoverableError | + OHCI1394_masterIntEnable | + OHCI1394_busReset | + OHCI1394_selfIDComplete | + OHCI1394_RSPkt | + OHCI1394_RQPkt | + OHCI1394_respTxComplete | + OHCI1394_reqTxComplete | + OHCI1394_isochRx | + OHCI1394_isochTx | + OHCI1394_postedWriteErr | + OHCI1394_cycleTooLong | + OHCI1394_cycleInconsistent); + + /* Enable link */ + reg_write(ohci, OHCI1394_HCControlSet, OHCI1394_HCControl_linkEnable); + + buf = reg_read(ohci, OHCI1394_Version); + PRINT(KERN_INFO, "OHCI-1394 %d.%d (PCI): IRQ=[%d] " + "MMIO=[%llx-%llx] Max Packet=[%d] IR/IT contexts=[%d/%d]", + ((((buf) >> 16) & 0xf) + (((buf) >> 20) & 0xf) * 10), + ((((buf) >> 4) & 0xf) + ((buf) & 0xf) * 10), ohci->dev->irq, + (unsigned long long)pci_resource_start(ohci->dev, 0), + (unsigned long long)pci_resource_start(ohci->dev, 0) + OHCI1394_REGISTER_SIZE - 1, + ohci->max_packet_size, + ohci->nb_iso_rcv_ctx, ohci->nb_iso_xmit_ctx); + + /* Check all of our ports to make sure that if anything is + * connected, we enable that port. */ + num_ports = get_phy_reg(ohci, 2) & 0xf; + for (i = 0; i < num_ports; i++) { + unsigned int status; + + set_phy_reg(ohci, 7, i); + status = get_phy_reg(ohci, 8); + + if (status & 0x20) + set_phy_reg(ohci, 8, status & ~1); + } + + /* Serial EEPROM Sanity check. */ + if ((ohci->max_packet_size < 512) || + (ohci->max_packet_size > 4096)) { + /* Serial EEPROM contents are suspect, set a sane max packet + * size and print the raw contents for bug reports if verbose + * debug is enabled. */ +#ifdef CONFIG_IEEE1394_VERBOSEDEBUG + int i; +#endif + + PRINT(KERN_DEBUG, "Serial EEPROM has suspicious values, " + "attempting to set max_packet_size to 512 bytes"); + reg_write(ohci, OHCI1394_BusOptions, + (reg_read(ohci, OHCI1394_BusOptions) & 0xf007) | 0x8002); + ohci->max_packet_size = 512; +#ifdef CONFIG_IEEE1394_VERBOSEDEBUG + PRINT(KERN_DEBUG, " EEPROM Present: %d", + (reg_read(ohci, OHCI1394_Version) >> 24) & 0x1); + reg_write(ohci, OHCI1394_GUID_ROM, 0x80000000); + + for (i = 0; + ((i < 1000) && + (reg_read(ohci, OHCI1394_GUID_ROM) & 0x80000000)); i++) + udelay(10); + + for (i = 0; i < 0x20; i++) { + reg_write(ohci, OHCI1394_GUID_ROM, 0x02000000); + PRINT(KERN_DEBUG, " EEPROM %02x: %02x", i, + (reg_read(ohci, OHCI1394_GUID_ROM) >> 16) & 0xff); + } +#endif + } +} + +/* + * Insert a packet in the DMA fifo and generate the DMA prg + * FIXME: rewrite the program in order to accept packets crossing + * page boundaries. + * check also that a single dma descriptor doesn't cross a + * page boundary. + */ +static void insert_packet(struct ti_ohci *ohci, + struct dma_trm_ctx *d, struct hpsb_packet *packet) +{ + u32 cycleTimer; + int idx = d->prg_ind; + + DBGMSG("Inserting packet for node " NODE_BUS_FMT + ", tlabel=%d, tcode=0x%x, speed=%d", + NODE_BUS_ARGS(ohci->host, packet->node_id), packet->tlabel, + packet->tcode, packet->speed_code); + + d->prg_cpu[idx]->begin.address = 0; + d->prg_cpu[idx]->begin.branchAddress = 0; + + if (d->type == DMA_CTX_ASYNC_RESP) { + /* + * For response packets, we need to put a timeout value in + * the 16 lower bits of the status... let's try 1 sec timeout + */ + cycleTimer = reg_read(ohci, OHCI1394_IsochronousCycleTimer); + d->prg_cpu[idx]->begin.status = cpu_to_le32( + (((((cycleTimer>>25)&0x7)+1)&0x7)<<13) | + ((cycleTimer&0x01fff000)>>12)); + + DBGMSG("cycleTimer: %08x timeStamp: %08x", + cycleTimer, d->prg_cpu[idx]->begin.status); + } else + d->prg_cpu[idx]->begin.status = 0; + + if ( (packet->type == hpsb_async) || (packet->type == hpsb_raw) ) { + + if (packet->type == hpsb_raw) { + d->prg_cpu[idx]->data[0] = cpu_to_le32(OHCI1394_TCODE_PHY<<4); + d->prg_cpu[idx]->data[1] = cpu_to_le32(packet->header[0]); + d->prg_cpu[idx]->data[2] = cpu_to_le32(packet->header[1]); + } else { + d->prg_cpu[idx]->data[0] = packet->speed_code<<16 | + (packet->header[0] & 0xFFFF); + + if (packet->tcode == TCODE_ISO_DATA) { + /* Sending an async stream packet */ + d->prg_cpu[idx]->data[1] = packet->header[0] & 0xFFFF0000; + } else { + /* Sending a normal async request or response */ + d->prg_cpu[idx]->data[1] = + (packet->header[1] & 0xFFFF) | + (packet->header[0] & 0xFFFF0000); + d->prg_cpu[idx]->data[2] = packet->header[2]; + d->prg_cpu[idx]->data[3] = packet->header[3]; + } + header_le32_to_cpu(d->prg_cpu[idx]->data, packet->tcode); + } + + if (packet->data_size) { /* block transmit */ + if (packet->tcode == TCODE_STREAM_DATA){ + d->prg_cpu[idx]->begin.control = + cpu_to_le32(DMA_CTL_OUTPUT_MORE | + DMA_CTL_IMMEDIATE | 0x8); + } else { + d->prg_cpu[idx]->begin.control = + cpu_to_le32(DMA_CTL_OUTPUT_MORE | + DMA_CTL_IMMEDIATE | 0x10); + } + d->prg_cpu[idx]->end.control = + cpu_to_le32(DMA_CTL_OUTPUT_LAST | + DMA_CTL_IRQ | + DMA_CTL_BRANCH | + packet->data_size); + /* + * Check that the packet data buffer + * does not cross a page boundary. + * + * XXX Fix this some day. eth1394 seems to trigger + * it, but ignoring it doesn't seem to cause a + * problem. + */ +#if 0 + if (cross_bound((unsigned long)packet->data, + packet->data_size)>0) { + /* FIXME: do something about it */ + PRINT(KERN_ERR, + "%s: packet data addr: %p size %Zd bytes " + "cross page boundary", __func__, + packet->data, packet->data_size); + } +#endif + d->prg_cpu[idx]->end.address = cpu_to_le32( + pci_map_single(ohci->dev, packet->data, + packet->data_size, + PCI_DMA_TODEVICE)); + + d->prg_cpu[idx]->end.branchAddress = 0; + d->prg_cpu[idx]->end.status = 0; + if (d->branchAddrPtr) + *(d->branchAddrPtr) = + cpu_to_le32(d->prg_bus[idx] | 0x3); + d->branchAddrPtr = + &(d->prg_cpu[idx]->end.branchAddress); + } else { /* quadlet transmit */ + if (packet->type == hpsb_raw) + d->prg_cpu[idx]->begin.control = + cpu_to_le32(DMA_CTL_OUTPUT_LAST | + DMA_CTL_IMMEDIATE | + DMA_CTL_IRQ | + DMA_CTL_BRANCH | + (packet->header_size + 4)); + else + d->prg_cpu[idx]->begin.control = + cpu_to_le32(DMA_CTL_OUTPUT_LAST | + DMA_CTL_IMMEDIATE | + DMA_CTL_IRQ | + DMA_CTL_BRANCH | + packet->header_size); + + if (d->branchAddrPtr) + *(d->branchAddrPtr) = + cpu_to_le32(d->prg_bus[idx] | 0x2); + d->branchAddrPtr = + &(d->prg_cpu[idx]->begin.branchAddress); + } + + } else { /* iso packet */ + d->prg_cpu[idx]->data[0] = packet->speed_code<<16 | + (packet->header[0] & 0xFFFF); + d->prg_cpu[idx]->data[1] = packet->header[0] & 0xFFFF0000; + header_le32_to_cpu(d->prg_cpu[idx]->data, packet->tcode); + + d->prg_cpu[idx]->begin.control = + cpu_to_le32(DMA_CTL_OUTPUT_MORE | + DMA_CTL_IMMEDIATE | 0x8); + d->prg_cpu[idx]->end.control = + cpu_to_le32(DMA_CTL_OUTPUT_LAST | + DMA_CTL_UPDATE | + DMA_CTL_IRQ | + DMA_CTL_BRANCH | + packet->data_size); + d->prg_cpu[idx]->end.address = cpu_to_le32( + pci_map_single(ohci->dev, packet->data, + packet->data_size, PCI_DMA_TODEVICE)); + + d->prg_cpu[idx]->end.branchAddress = 0; + d->prg_cpu[idx]->end.status = 0; + DBGMSG("Iso xmit context info: header[%08x %08x]\n" + " begin=%08x %08x %08x %08x\n" + " %08x %08x %08x %08x\n" + " end =%08x %08x %08x %08x", + d->prg_cpu[idx]->data[0], d->prg_cpu[idx]->data[1], + d->prg_cpu[idx]->begin.control, + d->prg_cpu[idx]->begin.address, + d->prg_cpu[idx]->begin.branchAddress, + d->prg_cpu[idx]->begin.status, + d->prg_cpu[idx]->data[0], + d->prg_cpu[idx]->data[1], + d->prg_cpu[idx]->data[2], + d->prg_cpu[idx]->data[3], + d->prg_cpu[idx]->end.control, + d->prg_cpu[idx]->end.address, + d->prg_cpu[idx]->end.branchAddress, + d->prg_cpu[idx]->end.status); + if (d->branchAddrPtr) + *(d->branchAddrPtr) = cpu_to_le32(d->prg_bus[idx] | 0x3); + d->branchAddrPtr = &(d->prg_cpu[idx]->end.branchAddress); + } + d->free_prgs--; + + /* queue the packet in the appropriate context queue */ + list_add_tail(&packet->driver_list, &d->fifo_list); + d->prg_ind = (d->prg_ind + 1) % d->num_desc; +} + +/* + * This function fills the FIFO with the (eventual) pending packets + * and runs or wakes up the DMA prg if necessary. + * + * The function MUST be called with the d->lock held. + */ +static void dma_trm_flush(struct ti_ohci *ohci, struct dma_trm_ctx *d) +{ + struct hpsb_packet *packet, *ptmp; + int idx = d->prg_ind; + int z = 0; + + /* insert the packets into the dma fifo */ + list_for_each_entry_safe(packet, ptmp, &d->pending_list, driver_list) { + if (!d->free_prgs) + break; + + /* For the first packet only */ + if (!z) + z = (packet->data_size) ? 3 : 2; + + /* Insert the packet */ + list_del_init(&packet->driver_list); + insert_packet(ohci, d, packet); + } + + /* Nothing must have been done, either no free_prgs or no packets */ + if (z == 0) + return; + + /* Is the context running ? (should be unless it is + the first packet to be sent in this context) */ + if (!(reg_read(ohci, d->ctrlSet) & 0x8000)) { + u32 nodeId = reg_read(ohci, OHCI1394_NodeID); + + DBGMSG("Starting transmit DMA ctx=%d",d->ctx); + reg_write(ohci, d->cmdPtr, d->prg_bus[idx] | z); + + /* Check that the node id is valid, and not 63 */ + if (!(nodeId & 0x80000000) || (nodeId & 0x3f) == 63) + PRINT(KERN_ERR, "Running dma failed because Node ID is not valid"); + else + reg_write(ohci, d->ctrlSet, 0x8000); + } else { + /* Wake up the dma context if necessary */ + if (!(reg_read(ohci, d->ctrlSet) & 0x400)) + DBGMSG("Waking transmit DMA ctx=%d",d->ctx); + + /* do this always, to avoid race condition */ + reg_write(ohci, d->ctrlSet, 0x1000); + } + + return; +} + +/* Transmission of an async or iso packet */ +static int ohci_transmit(struct hpsb_host *host, struct hpsb_packet *packet) +{ + struct ti_ohci *ohci = host->hostdata; + struct dma_trm_ctx *d; + unsigned long flags; + + if (packet->data_size > ohci->max_packet_size) { + PRINT(KERN_ERR, + "Transmit packet size %Zd is too big", + packet->data_size); + return -EOVERFLOW; + } + + if (packet->type == hpsb_raw) + d = &ohci->at_req_context; + else if ((packet->tcode & 0x02) && (packet->tcode != TCODE_ISO_DATA)) + d = &ohci->at_resp_context; + else + d = &ohci->at_req_context; + + spin_lock_irqsave(&d->lock,flags); + + list_add_tail(&packet->driver_list, &d->pending_list); + + dma_trm_flush(ohci, d); + + spin_unlock_irqrestore(&d->lock,flags); + + return 0; +} + +static int ohci_devctl(struct hpsb_host *host, enum devctl_cmd cmd, int arg) +{ + struct ti_ohci *ohci = host->hostdata; + int retval = 0, phy_reg; + + switch (cmd) { + case RESET_BUS: + switch (arg) { + case SHORT_RESET: + phy_reg = get_phy_reg(ohci, 5); + phy_reg |= 0x40; + set_phy_reg(ohci, 5, phy_reg); /* set ISBR */ + break; + case LONG_RESET: + phy_reg = get_phy_reg(ohci, 1); + phy_reg |= 0x40; + set_phy_reg(ohci, 1, phy_reg); /* set IBR */ + break; + case SHORT_RESET_NO_FORCE_ROOT: + phy_reg = get_phy_reg(ohci, 1); + if (phy_reg & 0x80) { + phy_reg &= ~0x80; + set_phy_reg(ohci, 1, phy_reg); /* clear RHB */ + } + + phy_reg = get_phy_reg(ohci, 5); + phy_reg |= 0x40; + set_phy_reg(ohci, 5, phy_reg); /* set ISBR */ + break; + case LONG_RESET_NO_FORCE_ROOT: + phy_reg = get_phy_reg(ohci, 1); + phy_reg &= ~0x80; + phy_reg |= 0x40; + set_phy_reg(ohci, 1, phy_reg); /* clear RHB, set IBR */ + break; + case SHORT_RESET_FORCE_ROOT: + phy_reg = get_phy_reg(ohci, 1); + if (!(phy_reg & 0x80)) { + phy_reg |= 0x80; + set_phy_reg(ohci, 1, phy_reg); /* set RHB */ + } + + phy_reg = get_phy_reg(ohci, 5); + phy_reg |= 0x40; + set_phy_reg(ohci, 5, phy_reg); /* set ISBR */ + break; + case LONG_RESET_FORCE_ROOT: + phy_reg = get_phy_reg(ohci, 1); + phy_reg |= 0xc0; + set_phy_reg(ohci, 1, phy_reg); /* set RHB and IBR */ + break; + default: + retval = -1; + } + break; + + case GET_CYCLE_COUNTER: + retval = reg_read(ohci, OHCI1394_IsochronousCycleTimer); + break; + + case SET_CYCLE_COUNTER: + reg_write(ohci, OHCI1394_IsochronousCycleTimer, arg); + break; + + case SET_BUS_ID: + PRINT(KERN_ERR, "devctl command SET_BUS_ID err"); + break; + + case ACT_CYCLE_MASTER: + if (arg) { + /* check if we are root and other nodes are present */ + u32 nodeId = reg_read(ohci, OHCI1394_NodeID); + if ((nodeId & (1<<30)) && (nodeId & 0x3f)) { + /* + * enable cycleTimer, cycleMaster + */ + DBGMSG("Cycle master enabled"); + reg_write(ohci, OHCI1394_LinkControlSet, + OHCI1394_LinkControl_CycleTimerEnable | + OHCI1394_LinkControl_CycleMaster); + } + } else { + /* disable cycleTimer, cycleMaster, cycleSource */ + reg_write(ohci, OHCI1394_LinkControlClear, + OHCI1394_LinkControl_CycleTimerEnable | + OHCI1394_LinkControl_CycleMaster | + OHCI1394_LinkControl_CycleSource); + } + break; + + case CANCEL_REQUESTS: + DBGMSG("Cancel request received"); + dma_trm_reset(&ohci->at_req_context); + dma_trm_reset(&ohci->at_resp_context); + break; + + default: + PRINT_G(KERN_ERR, "ohci_devctl cmd %d not implemented yet", + cmd); + break; + } + return retval; +} + +/*********************************** + * rawiso ISO reception * + ***********************************/ + +/* + We use either buffer-fill or packet-per-buffer DMA mode. The DMA + buffer is split into "blocks" (regions described by one DMA + descriptor). Each block must be one page or less in size, and + must not cross a page boundary. + + There is one little wrinkle with buffer-fill mode: a packet that + starts in the final block may wrap around into the first block. But + the user API expects all packets to be contiguous. Our solution is + to keep the very last page of the DMA buffer in reserve - if a + packet spans the gap, we copy its tail into this page. +*/ + +struct ohci_iso_recv { + struct ti_ohci *ohci; + + struct ohci1394_iso_tasklet task; + int task_active; + + enum { BUFFER_FILL_MODE = 0, + PACKET_PER_BUFFER_MODE = 1 } dma_mode; + + /* memory and PCI mapping for the DMA descriptors */ + struct dma_prog_region prog; + struct dma_cmd *block; /* = (struct dma_cmd*) prog.virt */ + + /* how many DMA blocks fit in the buffer */ + unsigned int nblocks; + + /* stride of DMA blocks */ + unsigned int buf_stride; + + /* number of blocks to batch between interrupts */ + int block_irq_interval; + + /* block that DMA will finish next */ + int block_dma; + + /* (buffer-fill only) block that the reader will release next */ + int block_reader; + + /* (buffer-fill only) bytes of buffer the reader has released, + less than one block */ + int released_bytes; + + /* (buffer-fill only) buffer offset at which the next packet will appear */ + int dma_offset; + + /* OHCI DMA context control registers */ + u32 ContextControlSet; + u32 ContextControlClear; + u32 CommandPtr; + u32 ContextMatch; +}; + +static void ohci_iso_recv_task(unsigned long data); +static void ohci_iso_recv_stop(struct hpsb_iso *iso); +static void ohci_iso_recv_shutdown(struct hpsb_iso *iso); +static int ohci_iso_recv_start(struct hpsb_iso *iso, int cycle, int tag_mask, int sync); +static void ohci_iso_recv_program(struct hpsb_iso *iso); + +static int ohci_iso_recv_init(struct hpsb_iso *iso) +{ + struct ti_ohci *ohci = iso->host->hostdata; + struct ohci_iso_recv *recv; + int ctx; + int ret = -ENOMEM; + + recv = kmalloc(sizeof(*recv), GFP_KERNEL); + if (!recv) + return -ENOMEM; + + iso->hostdata = recv; + recv->ohci = ohci; + recv->task_active = 0; + dma_prog_region_init(&recv->prog); + recv->block = NULL; + + /* use buffer-fill mode, unless irq_interval is 1 + (note: multichannel requires buffer-fill) */ + + if (((iso->irq_interval == 1 && iso->dma_mode == HPSB_ISO_DMA_OLD_ABI) || + iso->dma_mode == HPSB_ISO_DMA_PACKET_PER_BUFFER) && iso->channel != -1) { + recv->dma_mode = PACKET_PER_BUFFER_MODE; + } else { + recv->dma_mode = BUFFER_FILL_MODE; + } + + /* set nblocks, buf_stride, block_irq_interval */ + + if (recv->dma_mode == BUFFER_FILL_MODE) { + recv->buf_stride = PAGE_SIZE; + + /* one block per page of data in the DMA buffer, minus the final guard page */ + recv->nblocks = iso->buf_size/PAGE_SIZE - 1; + if (recv->nblocks < 3) { + DBGMSG("ohci_iso_recv_init: DMA buffer too small"); + goto err; + } + + /* iso->irq_interval is in packets - translate that to blocks */ + if (iso->irq_interval == 1) + recv->block_irq_interval = 1; + else + recv->block_irq_interval = iso->irq_interval * + ((recv->nblocks+1)/iso->buf_packets); + if (recv->block_irq_interval*4 > recv->nblocks) + recv->block_irq_interval = recv->nblocks/4; + if (recv->block_irq_interval < 1) + recv->block_irq_interval = 1; + + } else { + int max_packet_size; + + recv->nblocks = iso->buf_packets; + recv->block_irq_interval = iso->irq_interval; + if (recv->block_irq_interval * 4 > iso->buf_packets) + recv->block_irq_interval = iso->buf_packets / 4; + if (recv->block_irq_interval < 1) + recv->block_irq_interval = 1; + + /* choose a buffer stride */ + /* must be a power of 2, and <= PAGE_SIZE */ + + max_packet_size = iso->buf_size / iso->buf_packets; + + for (recv->buf_stride = 8; recv->buf_stride < max_packet_size; + recv->buf_stride *= 2); + + if (recv->buf_stride*iso->buf_packets > iso->buf_size || + recv->buf_stride > PAGE_SIZE) { + /* this shouldn't happen, but anyway... */ + DBGMSG("ohci_iso_recv_init: problem choosing a buffer stride"); + goto err; + } + } + + recv->block_reader = 0; + recv->released_bytes = 0; + recv->block_dma = 0; + recv->dma_offset = 0; + + /* size of DMA program = one descriptor per block */ + if (dma_prog_region_alloc(&recv->prog, + sizeof(struct dma_cmd) * recv->nblocks, + recv->ohci->dev)) + goto err; + + recv->block = (struct dma_cmd*) recv->prog.kvirt; + + ohci1394_init_iso_tasklet(&recv->task, + iso->channel == -1 ? OHCI_ISO_MULTICHANNEL_RECEIVE : + OHCI_ISO_RECEIVE, + ohci_iso_recv_task, (unsigned long) iso); + + if (ohci1394_register_iso_tasklet(recv->ohci, &recv->task) < 0) { + ret = -EBUSY; + goto err; + } + + recv->task_active = 1; + + /* recv context registers are spaced 32 bytes apart */ + ctx = recv->task.context; + recv->ContextControlSet = OHCI1394_IsoRcvContextControlSet + 32 * ctx; + recv->ContextControlClear = OHCI1394_IsoRcvContextControlClear + 32 * ctx; + recv->CommandPtr = OHCI1394_IsoRcvCommandPtr + 32 * ctx; + recv->ContextMatch = OHCI1394_IsoRcvContextMatch + 32 * ctx; + + if (iso->channel == -1) { + /* clear multi-channel selection mask */ + reg_write(recv->ohci, OHCI1394_IRMultiChanMaskHiClear, 0xFFFFFFFF); + reg_write(recv->ohci, OHCI1394_IRMultiChanMaskLoClear, 0xFFFFFFFF); + } + + /* write the DMA program */ + ohci_iso_recv_program(iso); + + DBGMSG("ohci_iso_recv_init: %s mode, DMA buffer is %lu pages" + " (%u bytes), using %u blocks, buf_stride %u, block_irq_interval %d", + recv->dma_mode == BUFFER_FILL_MODE ? + "buffer-fill" : "packet-per-buffer", + iso->buf_size/PAGE_SIZE, iso->buf_size, + recv->nblocks, recv->buf_stride, recv->block_irq_interval); + + return 0; + +err: + ohci_iso_recv_shutdown(iso); + return ret; +} + +static void ohci_iso_recv_stop(struct hpsb_iso *iso) +{ + struct ohci_iso_recv *recv = iso->hostdata; + + /* disable interrupts */ + reg_write(recv->ohci, OHCI1394_IsoRecvIntMaskClear, 1 << recv->task.context); + + /* halt DMA */ + ohci1394_stop_context(recv->ohci, recv->ContextControlClear, NULL); +} + +static void ohci_iso_recv_shutdown(struct hpsb_iso *iso) +{ + struct ohci_iso_recv *recv = iso->hostdata; + + if (recv->task_active) { + ohci_iso_recv_stop(iso); + ohci1394_unregister_iso_tasklet(recv->ohci, &recv->task); + recv->task_active = 0; + } + + dma_prog_region_free(&recv->prog); + kfree(recv); + iso->hostdata = NULL; +} + +/* set up a "gapped" ring buffer DMA program */ +static void ohci_iso_recv_program(struct hpsb_iso *iso) +{ + struct ohci_iso_recv *recv = iso->hostdata; + int blk; + + /* address of 'branch' field in previous DMA descriptor */ + u32 *prev_branch = NULL; + + for (blk = 0; blk < recv->nblocks; blk++) { + u32 control; + + /* the DMA descriptor */ + struct dma_cmd *cmd = &recv->block[blk]; + + /* offset of the DMA descriptor relative to the DMA prog buffer */ + unsigned long prog_offset = blk * sizeof(struct dma_cmd); + + /* offset of this packet's data within the DMA buffer */ + unsigned long buf_offset = blk * recv->buf_stride; + + if (recv->dma_mode == BUFFER_FILL_MODE) { + control = 2 << 28; /* INPUT_MORE */ + } else { + control = 3 << 28; /* INPUT_LAST */ + } + + control |= 8 << 24; /* s = 1, update xferStatus and resCount */ + + /* interrupt on last block, and at intervals */ + if (blk == recv->nblocks-1 || (blk % recv->block_irq_interval) == 0) { + control |= 3 << 20; /* want interrupt */ + } + + control |= 3 << 18; /* enable branch to address */ + control |= recv->buf_stride; + + cmd->control = cpu_to_le32(control); + cmd->address = cpu_to_le32(dma_region_offset_to_bus(&iso->data_buf, buf_offset)); + cmd->branchAddress = 0; /* filled in on next loop */ + cmd->status = cpu_to_le32(recv->buf_stride); + + /* link the previous descriptor to this one */ + if (prev_branch) { + *prev_branch = cpu_to_le32(dma_prog_region_offset_to_bus(&recv->prog, prog_offset) | 1); + } + + prev_branch = &cmd->branchAddress; + } + + /* the final descriptor's branch address and Z should be left at 0 */ +} + +/* listen or unlisten to a specific channel (multi-channel mode only) */ +static void ohci_iso_recv_change_channel(struct hpsb_iso *iso, unsigned char channel, int listen) +{ + struct ohci_iso_recv *recv = iso->hostdata; + int reg, i; + + if (channel < 32) { + reg = listen ? OHCI1394_IRMultiChanMaskLoSet : OHCI1394_IRMultiChanMaskLoClear; + i = channel; + } else { + reg = listen ? OHCI1394_IRMultiChanMaskHiSet : OHCI1394_IRMultiChanMaskHiClear; + i = channel - 32; + } + + reg_write(recv->ohci, reg, (1 << i)); + + /* issue a dummy read to force all PCI writes to be posted immediately */ + mb(); + reg_read(recv->ohci, OHCI1394_IsochronousCycleTimer); +} + +static void ohci_iso_recv_set_channel_mask(struct hpsb_iso *iso, u64 mask) +{ + struct ohci_iso_recv *recv = iso->hostdata; + int i; + + for (i = 0; i < 64; i++) { + if (mask & (1ULL << i)) { + if (i < 32) + reg_write(recv->ohci, OHCI1394_IRMultiChanMaskLoSet, (1 << i)); + else + reg_write(recv->ohci, OHCI1394_IRMultiChanMaskHiSet, (1 << (i-32))); + } else { + if (i < 32) + reg_write(recv->ohci, OHCI1394_IRMultiChanMaskLoClear, (1 << i)); + else + reg_write(recv->ohci, OHCI1394_IRMultiChanMaskHiClear, (1 << (i-32))); + } + } + + /* issue a dummy read to force all PCI writes to be posted immediately */ + mb(); + reg_read(recv->ohci, OHCI1394_IsochronousCycleTimer); +} + +static int ohci_iso_recv_start(struct hpsb_iso *iso, int cycle, int tag_mask, int sync) +{ + struct ohci_iso_recv *recv = iso->hostdata; + struct ti_ohci *ohci = recv->ohci; + u32 command, contextMatch; + + reg_write(recv->ohci, recv->ContextControlClear, 0xFFFFFFFF); + wmb(); + + /* always keep ISO headers */ + command = (1 << 30); + + if (recv->dma_mode == BUFFER_FILL_MODE) + command |= (1 << 31); + + reg_write(recv->ohci, recv->ContextControlSet, command); + + /* match on specified tags */ + contextMatch = tag_mask << 28; + + if (iso->channel == -1) { + /* enable multichannel reception */ + reg_write(recv->ohci, recv->ContextControlSet, (1 << 28)); + } else { + /* listen on channel */ + contextMatch |= iso->channel; + } + + if (cycle != -1) { + u32 seconds; + + /* enable cycleMatch */ + reg_write(recv->ohci, recv->ContextControlSet, (1 << 29)); + + /* set starting cycle */ + cycle &= 0x1FFF; + + /* 'cycle' is only mod 8000, but we also need two 'seconds' bits - + just snarf them from the current time */ + seconds = reg_read(recv->ohci, OHCI1394_IsochronousCycleTimer) >> 25; + + /* advance one second to give some extra time for DMA to start */ + seconds += 1; + + cycle |= (seconds & 3) << 13; + + contextMatch |= cycle << 12; + } + + if (sync != -1) { + /* set sync flag on first DMA descriptor */ + struct dma_cmd *cmd = &recv->block[recv->block_dma]; + cmd->control |= cpu_to_le32(DMA_CTL_WAIT); + + /* match sync field */ + contextMatch |= (sync&0xf)<<8; + } + + reg_write(recv->ohci, recv->ContextMatch, contextMatch); + + /* address of first descriptor block */ + command = dma_prog_region_offset_to_bus(&recv->prog, + recv->block_dma * sizeof(struct dma_cmd)); + command |= 1; /* Z=1 */ + + reg_write(recv->ohci, recv->CommandPtr, command); + + /* enable interrupts */ + reg_write(recv->ohci, OHCI1394_IsoRecvIntMaskSet, 1 << recv->task.context); + + wmb(); + + /* run */ + reg_write(recv->ohci, recv->ContextControlSet, 0x8000); + + /* issue a dummy read of the cycle timer register to force + all PCI writes to be posted immediately */ + mb(); + reg_read(recv->ohci, OHCI1394_IsochronousCycleTimer); + + /* check RUN */ + if (!(reg_read(recv->ohci, recv->ContextControlSet) & 0x8000)) { + PRINT(KERN_ERR, + "Error starting IR DMA (ContextControl 0x%08x)\n", + reg_read(recv->ohci, recv->ContextControlSet)); + return -1; + } + + return 0; +} + +static void ohci_iso_recv_release_block(struct ohci_iso_recv *recv, int block) +{ + /* re-use the DMA descriptor for the block */ + /* by linking the previous descriptor to it */ + + int next_i = block; + int prev_i = (next_i == 0) ? (recv->nblocks - 1) : (next_i - 1); + + struct dma_cmd *next = &recv->block[next_i]; + struct dma_cmd *prev = &recv->block[prev_i]; + + /* ignore out-of-range requests */ + if ((block < 0) || (block > recv->nblocks)) + return; + + /* 'next' becomes the new end of the DMA chain, + so disable branch and enable interrupt */ + next->branchAddress = 0; + next->control |= cpu_to_le32(3 << 20); + next->status = cpu_to_le32(recv->buf_stride); + + /* link prev to next */ + prev->branchAddress = cpu_to_le32(dma_prog_region_offset_to_bus(&recv->prog, + sizeof(struct dma_cmd) * next_i) + | 1); /* Z=1 */ + + /* disable interrupt on previous DMA descriptor, except at intervals */ + if ((prev_i % recv->block_irq_interval) == 0) { + prev->control |= cpu_to_le32(3 << 20); /* enable interrupt */ + } else { + prev->control &= cpu_to_le32(~(3<<20)); /* disable interrupt */ + } + wmb(); + + /* wake up DMA in case it fell asleep */ + reg_write(recv->ohci, recv->ContextControlSet, (1 << 12)); +} + +static void ohci_iso_recv_bufferfill_release(struct ohci_iso_recv *recv, + struct hpsb_iso_packet_info *info) +{ + /* release the memory where the packet was */ + recv->released_bytes += info->total_len; + + /* have we released enough memory for one block? */ + while (recv->released_bytes > recv->buf_stride) { + ohci_iso_recv_release_block(recv, recv->block_reader); + recv->block_reader = (recv->block_reader + 1) % recv->nblocks; + recv->released_bytes -= recv->buf_stride; + } +} + +static inline void ohci_iso_recv_release(struct hpsb_iso *iso, struct hpsb_iso_packet_info *info) +{ + struct ohci_iso_recv *recv = iso->hostdata; + if (recv->dma_mode == BUFFER_FILL_MODE) { + ohci_iso_recv_bufferfill_release(recv, info); + } else { + ohci_iso_recv_release_block(recv, info - iso->infos); + } +} + +/* parse all packets from blocks that have been fully received */ +static void ohci_iso_recv_bufferfill_parse(struct hpsb_iso *iso, struct ohci_iso_recv *recv) +{ + int wake = 0; + int runaway = 0; + struct ti_ohci *ohci = recv->ohci; + + while (1) { + /* we expect the next parsable packet to begin at recv->dma_offset */ + /* note: packet layout is as shown in section 10.6.1.1 of the OHCI spec */ + + unsigned int offset; + unsigned short len, cycle, total_len; + unsigned char channel, tag, sy; + + unsigned char *p = iso->data_buf.kvirt; + + unsigned int this_block = recv->dma_offset/recv->buf_stride; + + /* don't loop indefinitely */ + if (runaway++ > 100000) { + atomic_inc(&iso->overflows); + PRINT(KERN_ERR, + "IR DMA error - Runaway during buffer parsing!\n"); + break; + } + + /* stop parsing once we arrive at block_dma (i.e. don't get ahead of DMA) */ + if (this_block == recv->block_dma) + break; + + wake = 1; + + /* parse data length, tag, channel, and sy */ + + /* note: we keep our own local copies of 'len' and 'offset' + so the user can't mess with them by poking in the mmap area */ + + len = p[recv->dma_offset+2] | (p[recv->dma_offset+3] << 8); + + if (len > 4096) { + PRINT(KERN_ERR, + "IR DMA error - bogus 'len' value %u\n", len); + } + + channel = p[recv->dma_offset+1] & 0x3F; + tag = p[recv->dma_offset+1] >> 6; + sy = p[recv->dma_offset+0] & 0xF; + + /* advance to data payload */ + recv->dma_offset += 4; + + /* check for wrap-around */ + if (recv->dma_offset >= recv->buf_stride*recv->nblocks) { + recv->dma_offset -= recv->buf_stride*recv->nblocks; + } + + /* dma_offset now points to the first byte of the data payload */ + offset = recv->dma_offset; + + /* advance to xferStatus/timeStamp */ + recv->dma_offset += len; + + total_len = len + 8; /* 8 bytes header+trailer in OHCI packet */ + /* payload is padded to 4 bytes */ + if (len % 4) { + recv->dma_offset += 4 - (len%4); + total_len += 4 - (len%4); + } + + /* check for wrap-around */ + if (recv->dma_offset >= recv->buf_stride*recv->nblocks) { + /* uh oh, the packet data wraps from the last + to the first DMA block - make the packet + contiguous by copying its "tail" into the + guard page */ + + int guard_off = recv->buf_stride*recv->nblocks; + int tail_len = len - (guard_off - offset); + + if (tail_len > 0 && tail_len < recv->buf_stride) { + memcpy(iso->data_buf.kvirt + guard_off, + iso->data_buf.kvirt, + tail_len); + } + + recv->dma_offset -= recv->buf_stride*recv->nblocks; + } + + /* parse timestamp */ + cycle = p[recv->dma_offset+0] | (p[recv->dma_offset+1]<<8); + cycle &= 0x1FFF; + + /* advance to next packet */ + recv->dma_offset += 4; + + /* check for wrap-around */ + if (recv->dma_offset >= recv->buf_stride*recv->nblocks) { + recv->dma_offset -= recv->buf_stride*recv->nblocks; + } + + hpsb_iso_packet_received(iso, offset, len, total_len, cycle, channel, tag, sy); + } + + if (wake) + hpsb_iso_wake(iso); +} + +static void ohci_iso_recv_bufferfill_task(struct hpsb_iso *iso, struct ohci_iso_recv *recv) +{ + int loop; + struct ti_ohci *ohci = recv->ohci; + + /* loop over all blocks */ + for (loop = 0; loop < recv->nblocks; loop++) { + + /* check block_dma to see if it's done */ + struct dma_cmd *im = &recv->block[recv->block_dma]; + + /* check the DMA descriptor for new writes to xferStatus */ + u16 xferstatus = le32_to_cpu(im->status) >> 16; + + /* rescount is the number of bytes *remaining to be written* in the block */ + u16 rescount = le32_to_cpu(im->status) & 0xFFFF; + + unsigned char event = xferstatus & 0x1F; + + if (!event) { + /* nothing has happened to this block yet */ + break; + } + + if (event != 0x11) { + atomic_inc(&iso->overflows); + PRINT(KERN_ERR, + "IR DMA error - OHCI error code 0x%02x\n", event); + } + + if (rescount != 0) { + /* the card is still writing to this block; + we can't touch it until it's done */ + break; + } + + /* OK, the block is finished... */ + + /* sync our view of the block */ + dma_region_sync_for_cpu(&iso->data_buf, recv->block_dma*recv->buf_stride, recv->buf_stride); + + /* reset the DMA descriptor */ + im->status = recv->buf_stride; + + /* advance block_dma */ + recv->block_dma = (recv->block_dma + 1) % recv->nblocks; + + if ((recv->block_dma+1) % recv->nblocks == recv->block_reader) { + atomic_inc(&iso->overflows); + DBGMSG("ISO reception overflow - " + "ran out of DMA blocks"); + } + } + + /* parse any packets that have arrived */ + ohci_iso_recv_bufferfill_parse(iso, recv); +} + +static void ohci_iso_recv_packetperbuf_task(struct hpsb_iso *iso, struct ohci_iso_recv *recv) +{ + int count; + int wake = 0; + struct ti_ohci *ohci = recv->ohci; + + /* loop over the entire buffer */ + for (count = 0; count < recv->nblocks; count++) { + u32 packet_len = 0; + + /* pointer to the DMA descriptor */ + struct dma_cmd *il = ((struct dma_cmd*) recv->prog.kvirt) + iso->pkt_dma; + + /* check the DMA descriptor for new writes to xferStatus */ + u16 xferstatus = le32_to_cpu(il->status) >> 16; + u16 rescount = le32_to_cpu(il->status) & 0xFFFF; + + unsigned char event = xferstatus & 0x1F; + + if (!event) { + /* this packet hasn't come in yet; we are done for now */ + goto out; + } + + if (event == 0x11) { + /* packet received successfully! */ + + /* rescount is the number of bytes *remaining* in the packet buffer, + after the packet was written */ + packet_len = recv->buf_stride - rescount; + + } else if (event == 0x02) { + PRINT(KERN_ERR, "IR DMA error - packet too long for buffer\n"); + } else if (event) { + PRINT(KERN_ERR, "IR DMA error - OHCI error code 0x%02x\n", event); + } + + /* sync our view of the buffer */ + dma_region_sync_for_cpu(&iso->data_buf, iso->pkt_dma * recv->buf_stride, recv->buf_stride); + + /* record the per-packet info */ + { + /* iso header is 8 bytes ahead of the data payload */ + unsigned char *hdr; + + unsigned int offset; + unsigned short cycle; + unsigned char channel, tag, sy; + + offset = iso->pkt_dma * recv->buf_stride; + hdr = iso->data_buf.kvirt + offset; + + /* skip iso header */ + offset += 8; + packet_len -= 8; + + cycle = (hdr[0] | (hdr[1] << 8)) & 0x1FFF; + channel = hdr[5] & 0x3F; + tag = hdr[5] >> 6; + sy = hdr[4] & 0xF; + + hpsb_iso_packet_received(iso, offset, packet_len, + recv->buf_stride, cycle, channel, tag, sy); + } + + /* reset the DMA descriptor */ + il->status = recv->buf_stride; + + wake = 1; + recv->block_dma = iso->pkt_dma; + } + +out: + if (wake) + hpsb_iso_wake(iso); +} + +static void ohci_iso_recv_task(unsigned long data) +{ + struct hpsb_iso *iso = (struct hpsb_iso*) data; + struct ohci_iso_recv *recv = iso->hostdata; + + if (recv->dma_mode == BUFFER_FILL_MODE) + ohci_iso_recv_bufferfill_task(iso, recv); + else + ohci_iso_recv_packetperbuf_task(iso, recv); +} + +/*********************************** + * rawiso ISO transmission * + ***********************************/ + +struct ohci_iso_xmit { + struct ti_ohci *ohci; + struct dma_prog_region prog; + struct ohci1394_iso_tasklet task; + int task_active; + int last_cycle; + atomic_t skips; + + u32 ContextControlSet; + u32 ContextControlClear; + u32 CommandPtr; +}; + +/* transmission DMA program: + one OUTPUT_MORE_IMMEDIATE for the IT header + one OUTPUT_LAST for the buffer data */ + +struct iso_xmit_cmd { + struct dma_cmd output_more_immediate; + u8 iso_hdr[8]; + u32 unused[2]; + struct dma_cmd output_last; +}; + +static int ohci_iso_xmit_init(struct hpsb_iso *iso); +static int ohci_iso_xmit_start(struct hpsb_iso *iso, int cycle); +static void ohci_iso_xmit_shutdown(struct hpsb_iso *iso); +static void ohci_iso_xmit_task(unsigned long data); + +static int ohci_iso_xmit_init(struct hpsb_iso *iso) +{ + struct ohci_iso_xmit *xmit; + unsigned int prog_size; + int ctx; + int ret = -ENOMEM; + + xmit = kmalloc(sizeof(*xmit), GFP_KERNEL); + if (!xmit) + return -ENOMEM; + + iso->hostdata = xmit; + xmit->ohci = iso->host->hostdata; + xmit->task_active = 0; + xmit->last_cycle = -1; + atomic_set(&iso->skips, 0); + + dma_prog_region_init(&xmit->prog); + + prog_size = sizeof(struct iso_xmit_cmd) * iso->buf_packets; + + if (dma_prog_region_alloc(&xmit->prog, prog_size, xmit->ohci->dev)) + goto err; + + ohci1394_init_iso_tasklet(&xmit->task, OHCI_ISO_TRANSMIT, + ohci_iso_xmit_task, (unsigned long) iso); + + if (ohci1394_register_iso_tasklet(xmit->ohci, &xmit->task) < 0) { + ret = -EBUSY; + goto err; + } + + xmit->task_active = 1; + + /* xmit context registers are spaced 16 bytes apart */ + ctx = xmit->task.context; + xmit->ContextControlSet = OHCI1394_IsoXmitContextControlSet + 16 * ctx; + xmit->ContextControlClear = OHCI1394_IsoXmitContextControlClear + 16 * ctx; + xmit->CommandPtr = OHCI1394_IsoXmitCommandPtr + 16 * ctx; + + return 0; + +err: + ohci_iso_xmit_shutdown(iso); + return ret; +} + +static void ohci_iso_xmit_stop(struct hpsb_iso *iso) +{ + struct ohci_iso_xmit *xmit = iso->hostdata; + struct ti_ohci *ohci = xmit->ohci; + + /* disable interrupts */ + reg_write(xmit->ohci, OHCI1394_IsoXmitIntMaskClear, 1 << xmit->task.context); + + /* halt DMA */ + if (ohci1394_stop_context(xmit->ohci, xmit->ContextControlClear, NULL)) { + /* XXX the DMA context will lock up if you try to send too much data! */ + PRINT(KERN_ERR, + "you probably exceeded the OHCI card's bandwidth limit - " + "reload the module and reduce xmit bandwidth"); + } +} + +static void ohci_iso_xmit_shutdown(struct hpsb_iso *iso) +{ + struct ohci_iso_xmit *xmit = iso->hostdata; + + if (xmit->task_active) { + ohci_iso_xmit_stop(iso); + ohci1394_unregister_iso_tasklet(xmit->ohci, &xmit->task); + xmit->task_active = 0; + } + + dma_prog_region_free(&xmit->prog); + kfree(xmit); + iso->hostdata = NULL; +} + +static void ohci_iso_xmit_task(unsigned long data) +{ + struct hpsb_iso *iso = (struct hpsb_iso*) data; + struct ohci_iso_xmit *xmit = iso->hostdata; + struct ti_ohci *ohci = xmit->ohci; + int wake = 0; + int count; + + /* check the whole buffer if necessary, starting at pkt_dma */ + for (count = 0; count < iso->buf_packets; count++) { + int cycle; + + /* DMA descriptor */ + struct iso_xmit_cmd *cmd = dma_region_i(&xmit->prog, struct iso_xmit_cmd, iso->pkt_dma); + + /* check for new writes to xferStatus */ + u16 xferstatus = le32_to_cpu(cmd->output_last.status) >> 16; + u8 event = xferstatus & 0x1F; + + if (!event) { + /* packet hasn't been sent yet; we are done for now */ + break; + } + + if (event != 0x11) + PRINT(KERN_ERR, + "IT DMA error - OHCI error code 0x%02x\n", event); + + /* at least one packet went out, so wake up the writer */ + wake = 1; + + /* parse cycle */ + cycle = le32_to_cpu(cmd->output_last.status) & 0x1FFF; + + if (xmit->last_cycle > -1) { + int cycle_diff = cycle - xmit->last_cycle; + int skip; + + /* unwrap */ + if (cycle_diff < 0) { + cycle_diff += 8000; + if (cycle_diff < 0) + PRINT(KERN_ERR, "bogus cycle diff %d\n", + cycle_diff); + } + + skip = cycle_diff - 1; + if (skip > 0) { + DBGMSG("skipped %d cycles without packet loss", skip); + atomic_add(skip, &iso->skips); + } + } + xmit->last_cycle = cycle; + + /* tell the subsystem the packet has gone out */ + hpsb_iso_packet_sent(iso, cycle, event != 0x11); + + /* reset the DMA descriptor for next time */ + cmd->output_last.status = 0; + } + + if (wake) + hpsb_iso_wake(iso); +} + +static int ohci_iso_xmit_queue(struct hpsb_iso *iso, struct hpsb_iso_packet_info *info) +{ + struct ohci_iso_xmit *xmit = iso->hostdata; + struct ti_ohci *ohci = xmit->ohci; + + int next_i, prev_i; + struct iso_xmit_cmd *next, *prev; + + unsigned int offset; + unsigned short len; + unsigned char tag, sy; + + /* check that the packet doesn't cross a page boundary + (we could allow this if we added OUTPUT_MORE descriptor support) */ + if (cross_bound(info->offset, info->len)) { + PRINT(KERN_ERR, + "rawiso xmit: packet %u crosses a page boundary", + iso->first_packet); + return -EINVAL; + } + + offset = info->offset; + len = info->len; + tag = info->tag; + sy = info->sy; + + /* sync up the card's view of the buffer */ + dma_region_sync_for_device(&iso->data_buf, offset, len); + + /* append first_packet to the DMA chain */ + /* by linking the previous descriptor to it */ + /* (next will become the new end of the DMA chain) */ + + next_i = iso->first_packet; + prev_i = (next_i == 0) ? (iso->buf_packets - 1) : (next_i - 1); + + next = dma_region_i(&xmit->prog, struct iso_xmit_cmd, next_i); + prev = dma_region_i(&xmit->prog, struct iso_xmit_cmd, prev_i); + + /* set up the OUTPUT_MORE_IMMEDIATE descriptor */ + memset(next, 0, sizeof(struct iso_xmit_cmd)); + next->output_more_immediate.control = cpu_to_le32(0x02000008); + + /* ISO packet header is embedded in the OUTPUT_MORE_IMMEDIATE */ + + /* tcode = 0xA, and sy */ + next->iso_hdr[0] = 0xA0 | (sy & 0xF); + + /* tag and channel number */ + next->iso_hdr[1] = (tag << 6) | (iso->channel & 0x3F); + + /* transmission speed */ + next->iso_hdr[2] = iso->speed & 0x7; + + /* payload size */ + next->iso_hdr[6] = len & 0xFF; + next->iso_hdr[7] = len >> 8; + + /* set up the OUTPUT_LAST */ + next->output_last.control = cpu_to_le32(1 << 28); + next->output_last.control |= cpu_to_le32(1 << 27); /* update timeStamp */ + next->output_last.control |= cpu_to_le32(3 << 20); /* want interrupt */ + next->output_last.control |= cpu_to_le32(3 << 18); /* enable branch */ + next->output_last.control |= cpu_to_le32(len); + + /* payload bus address */ + next->output_last.address = cpu_to_le32(dma_region_offset_to_bus(&iso->data_buf, offset)); + + /* leave branchAddress at zero for now */ + + /* re-write the previous DMA descriptor to chain to this one */ + + /* set prev branch address to point to next (Z=3) */ + prev->output_last.branchAddress = cpu_to_le32( + dma_prog_region_offset_to_bus(&xmit->prog, sizeof(struct iso_xmit_cmd) * next_i) | 3); + + /* + * Link the skip address to this descriptor itself. This causes a + * context to skip a cycle whenever lost cycles or FIFO overruns occur, + * without dropping the data at that point the application should then + * decide whether this is an error condition or not. Some protocols + * can deal with this by dropping some rate-matching padding packets. + */ + next->output_more_immediate.branchAddress = + prev->output_last.branchAddress; + + /* disable interrupt, unless required by the IRQ interval */ + if (prev_i % iso->irq_interval) { + prev->output_last.control &= cpu_to_le32(~(3 << 20)); /* no interrupt */ + } else { + prev->output_last.control |= cpu_to_le32(3 << 20); /* enable interrupt */ + } + + wmb(); + + /* wake DMA in case it is sleeping */ + reg_write(xmit->ohci, xmit->ContextControlSet, 1 << 12); + + /* issue a dummy read of the cycle timer to force all PCI + writes to be posted immediately */ + mb(); + reg_read(xmit->ohci, OHCI1394_IsochronousCycleTimer); + + return 0; +} + +static int ohci_iso_xmit_start(struct hpsb_iso *iso, int cycle) +{ + struct ohci_iso_xmit *xmit = iso->hostdata; + struct ti_ohci *ohci = xmit->ohci; + + /* clear out the control register */ + reg_write(xmit->ohci, xmit->ContextControlClear, 0xFFFFFFFF); + wmb(); + + /* address and length of first descriptor block (Z=3) */ + reg_write(xmit->ohci, xmit->CommandPtr, + dma_prog_region_offset_to_bus(&xmit->prog, iso->pkt_dma * sizeof(struct iso_xmit_cmd)) | 3); + + /* cycle match */ + if (cycle != -1) { + u32 start = cycle & 0x1FFF; + + /* 'cycle' is only mod 8000, but we also need two 'seconds' bits - + just snarf them from the current time */ + u32 seconds = reg_read(xmit->ohci, OHCI1394_IsochronousCycleTimer) >> 25; + + /* advance one second to give some extra time for DMA to start */ + seconds += 1; + + start |= (seconds & 3) << 13; + + reg_write(xmit->ohci, xmit->ContextControlSet, 0x80000000 | (start << 16)); + } + + /* enable interrupts */ + reg_write(xmit->ohci, OHCI1394_IsoXmitIntMaskSet, 1 << xmit->task.context); + + /* run */ + reg_write(xmit->ohci, xmit->ContextControlSet, 0x8000); + mb(); + + /* wait 100 usec to give the card time to go active */ + udelay(100); + + /* check the RUN bit */ + if (!(reg_read(xmit->ohci, xmit->ContextControlSet) & 0x8000)) { + PRINT(KERN_ERR, "Error starting IT DMA (ContextControl 0x%08x)\n", + reg_read(xmit->ohci, xmit->ContextControlSet)); + return -1; + } + + return 0; +} + +static int ohci_isoctl(struct hpsb_iso *iso, enum isoctl_cmd cmd, unsigned long arg) +{ + + switch(cmd) { + case XMIT_INIT: + return ohci_iso_xmit_init(iso); + case XMIT_START: + return ohci_iso_xmit_start(iso, arg); + case XMIT_STOP: + ohci_iso_xmit_stop(iso); + return 0; + case XMIT_QUEUE: + return ohci_iso_xmit_queue(iso, (struct hpsb_iso_packet_info*) arg); + case XMIT_SHUTDOWN: + ohci_iso_xmit_shutdown(iso); + return 0; + + case RECV_INIT: + return ohci_iso_recv_init(iso); + case RECV_START: { + int *args = (int*) arg; + return ohci_iso_recv_start(iso, args[0], args[1], args[2]); + } + case RECV_STOP: + ohci_iso_recv_stop(iso); + return 0; + case RECV_RELEASE: + ohci_iso_recv_release(iso, (struct hpsb_iso_packet_info*) arg); + return 0; + case RECV_FLUSH: + ohci_iso_recv_task((unsigned long) iso); + return 0; + case RECV_SHUTDOWN: + ohci_iso_recv_shutdown(iso); + return 0; + case RECV_LISTEN_CHANNEL: + ohci_iso_recv_change_channel(iso, arg, 1); + return 0; + case RECV_UNLISTEN_CHANNEL: + ohci_iso_recv_change_channel(iso, arg, 0); + return 0; + case RECV_SET_CHANNEL_MASK: + ohci_iso_recv_set_channel_mask(iso, *((u64*) arg)); + return 0; + + default: + PRINT_G(KERN_ERR, "ohci_isoctl cmd %d not implemented yet", + cmd); + break; + } + return -EINVAL; +} + +/*************************************** + * IEEE-1394 functionality section END * + ***************************************/ + + +/******************************************************** + * Global stuff (interrupt handler, init/shutdown code) * + ********************************************************/ + +static void dma_trm_reset(struct dma_trm_ctx *d) +{ + unsigned long flags; + LIST_HEAD(packet_list); + struct ti_ohci *ohci = d->ohci; + struct hpsb_packet *packet, *ptmp; + + ohci1394_stop_context(ohci, d->ctrlClear, NULL); + + /* Lock the context, reset it and release it. Move the packets + * that were pending in the context to packet_list and free + * them after releasing the lock. */ + + spin_lock_irqsave(&d->lock, flags); + + list_splice_init(&d->fifo_list, &packet_list); + list_splice_init(&d->pending_list, &packet_list); + + d->branchAddrPtr = NULL; + d->sent_ind = d->prg_ind; + d->free_prgs = d->num_desc; + + spin_unlock_irqrestore(&d->lock, flags); + + if (list_empty(&packet_list)) + return; + + PRINT(KERN_INFO, "AT dma reset ctx=%d, aborting transmission", d->ctx); + + /* Now process subsystem callbacks for the packets from this + * context. */ + list_for_each_entry_safe(packet, ptmp, &packet_list, driver_list) { + list_del_init(&packet->driver_list); + hpsb_packet_sent(ohci->host, packet, ACKX_ABORTED); + } +} + +static void ohci_schedule_iso_tasklets(struct ti_ohci *ohci, + quadlet_t rx_event, + quadlet_t tx_event) +{ + struct ohci1394_iso_tasklet *t; + unsigned long mask; + unsigned long flags; + + spin_lock_irqsave(&ohci->iso_tasklet_list_lock, flags); + + list_for_each_entry(t, &ohci->iso_tasklet_list, link) { + mask = 1 << t->context; + + if (t->type == OHCI_ISO_TRANSMIT) { + if (tx_event & mask) + tasklet_schedule(&t->tasklet); + } else { + /* OHCI_ISO_RECEIVE or OHCI_ISO_MULTICHANNEL_RECEIVE */ + if (rx_event & mask) + tasklet_schedule(&t->tasklet); + } + } + + spin_unlock_irqrestore(&ohci->iso_tasklet_list_lock, flags); +} + +static irqreturn_t ohci_irq_handler(int irq, void *dev_id) +{ + quadlet_t event, node_id; + struct ti_ohci *ohci = (struct ti_ohci *)dev_id; + struct hpsb_host *host = ohci->host; + int phyid = -1, isroot = 0; + unsigned long flags; + + /* Read and clear the interrupt event register. Don't clear + * the busReset event, though. This is done when we get the + * selfIDComplete interrupt. */ + spin_lock_irqsave(&ohci->event_lock, flags); + event = reg_read(ohci, OHCI1394_IntEventClear); + reg_write(ohci, OHCI1394_IntEventClear, event & ~OHCI1394_busReset); + spin_unlock_irqrestore(&ohci->event_lock, flags); + + if (!event) + return IRQ_NONE; + + /* If event is ~(u32)0 cardbus card was ejected. In this case + * we just return, and clean up in the ohci1394_pci_remove + * function. */ + if (event == ~(u32) 0) { + DBGMSG("Device removed."); + return IRQ_NONE; + } + + DBGMSG("IntEvent: %08x", event); + + if (event & OHCI1394_unrecoverableError) { + int ctx; + PRINT(KERN_ERR, "Unrecoverable error!"); + + if (reg_read(ohci, OHCI1394_AsReqTrContextControlSet) & 0x800) + PRINT(KERN_ERR, "Async Req Tx Context died: " + "ctrl[%08x] cmdptr[%08x]", + reg_read(ohci, OHCI1394_AsReqTrContextControlSet), + reg_read(ohci, OHCI1394_AsReqTrCommandPtr)); + + if (reg_read(ohci, OHCI1394_AsRspTrContextControlSet) & 0x800) + PRINT(KERN_ERR, "Async Rsp Tx Context died: " + "ctrl[%08x] cmdptr[%08x]", + reg_read(ohci, OHCI1394_AsRspTrContextControlSet), + reg_read(ohci, OHCI1394_AsRspTrCommandPtr)); + + if (reg_read(ohci, OHCI1394_AsReqRcvContextControlSet) & 0x800) + PRINT(KERN_ERR, "Async Req Rcv Context died: " + "ctrl[%08x] cmdptr[%08x]", + reg_read(ohci, OHCI1394_AsReqRcvContextControlSet), + reg_read(ohci, OHCI1394_AsReqRcvCommandPtr)); + + if (reg_read(ohci, OHCI1394_AsRspRcvContextControlSet) & 0x800) + PRINT(KERN_ERR, "Async Rsp Rcv Context died: " + "ctrl[%08x] cmdptr[%08x]", + reg_read(ohci, OHCI1394_AsRspRcvContextControlSet), + reg_read(ohci, OHCI1394_AsRspRcvCommandPtr)); + + for (ctx = 0; ctx < ohci->nb_iso_xmit_ctx; ctx++) { + if (reg_read(ohci, OHCI1394_IsoXmitContextControlSet + (16 * ctx)) & 0x800) + PRINT(KERN_ERR, "Iso Xmit %d Context died: " + "ctrl[%08x] cmdptr[%08x]", ctx, + reg_read(ohci, OHCI1394_IsoXmitContextControlSet + (16 * ctx)), + reg_read(ohci, OHCI1394_IsoXmitCommandPtr + (16 * ctx))); + } + + for (ctx = 0; ctx < ohci->nb_iso_rcv_ctx; ctx++) { + if (reg_read(ohci, OHCI1394_IsoRcvContextControlSet + (32 * ctx)) & 0x800) + PRINT(KERN_ERR, "Iso Recv %d Context died: " + "ctrl[%08x] cmdptr[%08x] match[%08x]", ctx, + reg_read(ohci, OHCI1394_IsoRcvContextControlSet + (32 * ctx)), + reg_read(ohci, OHCI1394_IsoRcvCommandPtr + (32 * ctx)), + reg_read(ohci, OHCI1394_IsoRcvContextMatch + (32 * ctx))); + } + + event &= ~OHCI1394_unrecoverableError; + } + if (event & OHCI1394_postedWriteErr) { + PRINT(KERN_ERR, "physical posted write error"); + /* no recovery strategy yet, had to involve protocol drivers */ + event &= ~OHCI1394_postedWriteErr; + } + if (event & OHCI1394_cycleTooLong) { + if(printk_ratelimit()) + PRINT(KERN_WARNING, "isochronous cycle too long"); + else + DBGMSG("OHCI1394_cycleTooLong"); + reg_write(ohci, OHCI1394_LinkControlSet, + OHCI1394_LinkControl_CycleMaster); + event &= ~OHCI1394_cycleTooLong; + } + if (event & OHCI1394_cycleInconsistent) { + /* We subscribe to the cycleInconsistent event only to + * clear the corresponding event bit... otherwise, + * isochronous cycleMatch DMA won't work. */ + DBGMSG("OHCI1394_cycleInconsistent"); + event &= ~OHCI1394_cycleInconsistent; + } + if (event & OHCI1394_busReset) { + /* The busReset event bit can't be cleared during the + * selfID phase, so we disable busReset interrupts, to + * avoid burying the cpu in interrupt requests. */ + spin_lock_irqsave(&ohci->event_lock, flags); + reg_write(ohci, OHCI1394_IntMaskClear, OHCI1394_busReset); + + if (ohci->check_busreset) { + int loop_count = 0; + + udelay(10); + + while (reg_read(ohci, OHCI1394_IntEventSet) & OHCI1394_busReset) { + reg_write(ohci, OHCI1394_IntEventClear, OHCI1394_busReset); + + spin_unlock_irqrestore(&ohci->event_lock, flags); + udelay(10); + spin_lock_irqsave(&ohci->event_lock, flags); + + /* The loop counter check is to prevent the driver + * from remaining in this state forever. For the + * initial bus reset, the loop continues for ever + * and the system hangs, until some device is plugged-in + * or out manually into a port! The forced reset seems + * to solve this problem. This mainly effects nForce2. */ + if (loop_count > 10000) { + ohci_devctl(host, RESET_BUS, LONG_RESET); + DBGMSG("Detected bus-reset loop. Forced a bus reset!"); + loop_count = 0; + } + + loop_count++; + } + } + spin_unlock_irqrestore(&ohci->event_lock, flags); + if (!host->in_bus_reset) { + DBGMSG("irq_handler: Bus reset requested"); + + /* Subsystem call */ + hpsb_bus_reset(ohci->host); + } + event &= ~OHCI1394_busReset; + } + if (event & OHCI1394_reqTxComplete) { + struct dma_trm_ctx *d = &ohci->at_req_context; + DBGMSG("Got reqTxComplete interrupt " + "status=0x%08X", reg_read(ohci, d->ctrlSet)); + if (reg_read(ohci, d->ctrlSet) & 0x800) + ohci1394_stop_context(ohci, d->ctrlClear, + "reqTxComplete"); + else + dma_trm_tasklet((unsigned long)d); + //tasklet_schedule(&d->task); + event &= ~OHCI1394_reqTxComplete; + } + if (event & OHCI1394_respTxComplete) { + struct dma_trm_ctx *d = &ohci->at_resp_context; + DBGMSG("Got respTxComplete interrupt " + "status=0x%08X", reg_read(ohci, d->ctrlSet)); + if (reg_read(ohci, d->ctrlSet) & 0x800) + ohci1394_stop_context(ohci, d->ctrlClear, + "respTxComplete"); + else + tasklet_schedule(&d->task); + event &= ~OHCI1394_respTxComplete; + } + if (event & OHCI1394_RQPkt) { + struct dma_rcv_ctx *d = &ohci->ar_req_context; + DBGMSG("Got RQPkt interrupt status=0x%08X", + reg_read(ohci, d->ctrlSet)); + if (reg_read(ohci, d->ctrlSet) & 0x800) + ohci1394_stop_context(ohci, d->ctrlClear, "RQPkt"); + else + tasklet_schedule(&d->task); + event &= ~OHCI1394_RQPkt; + } + if (event & OHCI1394_RSPkt) { + struct dma_rcv_ctx *d = &ohci->ar_resp_context; + DBGMSG("Got RSPkt interrupt status=0x%08X", + reg_read(ohci, d->ctrlSet)); + if (reg_read(ohci, d->ctrlSet) & 0x800) + ohci1394_stop_context(ohci, d->ctrlClear, "RSPkt"); + else + tasklet_schedule(&d->task); + event &= ~OHCI1394_RSPkt; + } + if (event & OHCI1394_isochRx) { + quadlet_t rx_event; + + rx_event = reg_read(ohci, OHCI1394_IsoRecvIntEventSet); + reg_write(ohci, OHCI1394_IsoRecvIntEventClear, rx_event); + ohci_schedule_iso_tasklets(ohci, rx_event, 0); + event &= ~OHCI1394_isochRx; + } + if (event & OHCI1394_isochTx) { + quadlet_t tx_event; + + tx_event = reg_read(ohci, OHCI1394_IsoXmitIntEventSet); + reg_write(ohci, OHCI1394_IsoXmitIntEventClear, tx_event); + ohci_schedule_iso_tasklets(ohci, 0, tx_event); + event &= ~OHCI1394_isochTx; + } + if (event & OHCI1394_selfIDComplete) { + if (host->in_bus_reset) { + node_id = reg_read(ohci, OHCI1394_NodeID); + + if (!(node_id & 0x80000000)) { + PRINT(KERN_ERR, + "SelfID received, but NodeID invalid " + "(probably new bus reset occurred): %08X", + node_id); + goto selfid_not_valid; + } + + phyid = node_id & 0x0000003f; + isroot = (node_id & 0x40000000) != 0; + + DBGMSG("SelfID interrupt received " + "(phyid %d, %s)", phyid, + (isroot ? "root" : "not root")); + + handle_selfid(ohci, host, phyid, isroot); + + /* Clear the bus reset event and re-enable the + * busReset interrupt. */ + spin_lock_irqsave(&ohci->event_lock, flags); + reg_write(ohci, OHCI1394_IntEventClear, OHCI1394_busReset); + reg_write(ohci, OHCI1394_IntMaskSet, OHCI1394_busReset); + spin_unlock_irqrestore(&ohci->event_lock, flags); + + /* Turn on phys dma reception. + * + * TODO: Enable some sort of filtering management. + */ + if (phys_dma) { + reg_write(ohci, OHCI1394_PhyReqFilterHiSet, + 0xffffffff); + reg_write(ohci, OHCI1394_PhyReqFilterLoSet, + 0xffffffff); + } + + DBGMSG("PhyReqFilter=%08x%08x", + reg_read(ohci, OHCI1394_PhyReqFilterHiSet), + reg_read(ohci, OHCI1394_PhyReqFilterLoSet)); + + hpsb_selfid_complete(host, phyid, isroot); + } else + PRINT(KERN_ERR, + "SelfID received outside of bus reset sequence"); + +selfid_not_valid: + event &= ~OHCI1394_selfIDComplete; + } + + /* Make sure we handle everything, just in case we accidentally + * enabled an interrupt that we didn't write a handler for. */ + if (event) + PRINT(KERN_ERR, "Unhandled interrupt(s) 0x%08x", + event); + + return IRQ_HANDLED; +} + +/* Put the buffer back into the dma context */ +static void insert_dma_buffer(struct dma_rcv_ctx *d, int idx) +{ + struct ti_ohci *ohci = (struct ti_ohci*)(d->ohci); + DBGMSG("Inserting dma buf ctx=%d idx=%d", d->ctx, idx); + + d->prg_cpu[idx]->status = cpu_to_le32(d->buf_size); + d->prg_cpu[idx]->branchAddress &= le32_to_cpu(0xfffffff0); + idx = (idx + d->num_desc - 1 ) % d->num_desc; + d->prg_cpu[idx]->branchAddress |= le32_to_cpu(0x00000001); + + /* To avoid a race, ensure 1394 interface hardware sees the inserted + * context program descriptors before it sees the wakeup bit set. */ + wmb(); + + /* wake up the dma context if necessary */ + if (!(reg_read(ohci, d->ctrlSet) & 0x400)) { + PRINT(KERN_INFO, + "Waking dma ctx=%d ... processing is probably too slow", + d->ctx); + } + + /* do this always, to avoid race condition */ + reg_write(ohci, d->ctrlSet, 0x1000); +} + +#define cond_le32_to_cpu(data, noswap) \ + (noswap ? data : le32_to_cpu(data)) + +static const int TCODE_SIZE[16] = {20, 0, 16, -1, 16, 20, 20, 0, + -1, 0, -1, 0, -1, -1, 16, -1}; + +/* + * Determine the length of a packet in the buffer + * Optimization suggested by Pascal Drolet <pascal.drolet@informission.ca> + */ +static inline int packet_length(struct dma_rcv_ctx *d, int idx, + quadlet_t *buf_ptr, int offset, + unsigned char tcode, int noswap) +{ + int length = -1; + + if (d->type == DMA_CTX_ASYNC_REQ || d->type == DMA_CTX_ASYNC_RESP) { + length = TCODE_SIZE[tcode]; + if (length == 0) { + if (offset + 12 >= d->buf_size) { + length = (cond_le32_to_cpu(d->buf_cpu[(idx + 1) % d->num_desc] + [3 - ((d->buf_size - offset) >> 2)], noswap) >> 16); + } else { + length = (cond_le32_to_cpu(buf_ptr[3], noswap) >> 16); + } + length += 20; + } + } else if (d->type == DMA_CTX_ISO) { + /* Assumption: buffer fill mode with header/trailer */ + length = (cond_le32_to_cpu(buf_ptr[0], noswap) >> 16) + 8; + } + + if (length > 0 && length % 4) + length += 4 - (length % 4); + + return length; +} + +/* Tasklet that processes dma receive buffers */ +static void dma_rcv_tasklet (unsigned long data) +{ + struct dma_rcv_ctx *d = (struct dma_rcv_ctx*)data; + struct ti_ohci *ohci = (struct ti_ohci*)(d->ohci); + unsigned int split_left, idx, offset, rescount; + unsigned char tcode; + int length, bytes_left, ack; + unsigned long flags; + quadlet_t *buf_ptr; + char *split_ptr; + char msg[256]; + + spin_lock_irqsave(&d->lock, flags); + + idx = d->buf_ind; + offset = d->buf_offset; + buf_ptr = d->buf_cpu[idx] + offset/4; + + rescount = le32_to_cpu(d->prg_cpu[idx]->status) & 0xffff; + bytes_left = d->buf_size - rescount - offset; + + while (bytes_left > 0) { + tcode = (cond_le32_to_cpu(buf_ptr[0], ohci->no_swap_incoming) >> 4) & 0xf; + + /* packet_length() will return < 4 for an error */ + length = packet_length(d, idx, buf_ptr, offset, tcode, ohci->no_swap_incoming); + + if (length < 4) { /* something is wrong */ + sprintf(msg,"Unexpected tcode 0x%x(0x%08x) in AR ctx=%d, length=%d", + tcode, cond_le32_to_cpu(buf_ptr[0], ohci->no_swap_incoming), + d->ctx, length); + ohci1394_stop_context(ohci, d->ctrlClear, msg); + spin_unlock_irqrestore(&d->lock, flags); + return; + } + + /* The first case is where we have a packet that crosses + * over more than one descriptor. The next case is where + * it's all in the first descriptor. */ + if ((offset + length) > d->buf_size) { + DBGMSG("Split packet rcv'd"); + if (length > d->split_buf_size) { + ohci1394_stop_context(ohci, d->ctrlClear, + "Split packet size exceeded"); + d->buf_ind = idx; + d->buf_offset = offset; + spin_unlock_irqrestore(&d->lock, flags); + return; + } + + if (le32_to_cpu(d->prg_cpu[(idx+1)%d->num_desc]->status) + == d->buf_size) { + /* Other part of packet not written yet. + * this should never happen I think + * anyway we'll get it on the next call. */ + PRINT(KERN_INFO, + "Got only half a packet!"); + d->buf_ind = idx; + d->buf_offset = offset; + spin_unlock_irqrestore(&d->lock, flags); + return; + } + + split_left = length; + split_ptr = (char *)d->spb; + memcpy(split_ptr,buf_ptr,d->buf_size-offset); + split_left -= d->buf_size-offset; + split_ptr += d->buf_size-offset; + insert_dma_buffer(d, idx); + idx = (idx+1) % d->num_desc; + buf_ptr = d->buf_cpu[idx]; + offset=0; + + while (split_left >= d->buf_size) { + memcpy(split_ptr,buf_ptr,d->buf_size); + split_ptr += d->buf_size; + split_left -= d->buf_size; + insert_dma_buffer(d, idx); + idx = (idx+1) % d->num_desc; + buf_ptr = d->buf_cpu[idx]; + } + + if (split_left > 0) { + memcpy(split_ptr, buf_ptr, split_left); + offset = split_left; + buf_ptr += offset/4; + } + } else { + DBGMSG("Single packet rcv'd"); + memcpy(d->spb, buf_ptr, length); + offset += length; + buf_ptr += length/4; + if (offset==d->buf_size) { + insert_dma_buffer(d, idx); + idx = (idx+1) % d->num_desc; + buf_ptr = d->buf_cpu[idx]; + offset=0; + } + } + + /* We get one phy packet to the async descriptor for each + * bus reset. We always ignore it. */ + if (tcode != OHCI1394_TCODE_PHY) { + if (!ohci->no_swap_incoming) + header_le32_to_cpu(d->spb, tcode); + DBGMSG("Packet received from node" + " %d ack=0x%02X spd=%d tcode=0x%X" + " length=%d ctx=%d tlabel=%d", + (d->spb[1]>>16)&0x3f, + (cond_le32_to_cpu(d->spb[length/4-1], ohci->no_swap_incoming)>>16)&0x1f, + (cond_le32_to_cpu(d->spb[length/4-1], ohci->no_swap_incoming)>>21)&0x3, + tcode, length, d->ctx, + (d->spb[0]>>10)&0x3f); + + ack = (((cond_le32_to_cpu(d->spb[length/4-1], ohci->no_swap_incoming)>>16)&0x1f) + == 0x11) ? 1 : 0; + + hpsb_packet_received(ohci->host, d->spb, + length-4, ack); + } +#ifdef OHCI1394_DEBUG + else + PRINT (KERN_DEBUG, "Got phy packet ctx=%d ... discarded", + d->ctx); +#endif + + rescount = le32_to_cpu(d->prg_cpu[idx]->status) & 0xffff; + + bytes_left = d->buf_size - rescount - offset; + + } + + d->buf_ind = idx; + d->buf_offset = offset; + + spin_unlock_irqrestore(&d->lock, flags); +} + +/* Bottom half that processes sent packets */ +static void dma_trm_tasklet (unsigned long data) +{ + struct dma_trm_ctx *d = (struct dma_trm_ctx*)data; + struct ti_ohci *ohci = (struct ti_ohci*)(d->ohci); + struct hpsb_packet *packet, *ptmp; + unsigned long flags; + u32 status, ack; + size_t datasize; + + spin_lock_irqsave(&d->lock, flags); + + list_for_each_entry_safe(packet, ptmp, &d->fifo_list, driver_list) { + datasize = packet->data_size; + if (datasize && packet->type != hpsb_raw) + status = le32_to_cpu( + d->prg_cpu[d->sent_ind]->end.status) >> 16; + else + status = le32_to_cpu( + d->prg_cpu[d->sent_ind]->begin.status) >> 16; + + if (status == 0) + /* this packet hasn't been sent yet*/ + break; + +#ifdef OHCI1394_DEBUG + if (datasize) + if (((le32_to_cpu(d->prg_cpu[d->sent_ind]->data[0])>>4)&0xf) == 0xa) + DBGMSG("Stream packet sent to channel %d tcode=0x%X " + "ack=0x%X spd=%d dataLength=%d ctx=%d", + (le32_to_cpu(d->prg_cpu[d->sent_ind]->data[0])>>8)&0x3f, + (le32_to_cpu(d->prg_cpu[d->sent_ind]->data[0])>>4)&0xf, + status&0x1f, (status>>5)&0x3, + le32_to_cpu(d->prg_cpu[d->sent_ind]->data[1])>>16, + d->ctx); + else + DBGMSG("Packet sent to node %d tcode=0x%X tLabel=" + "%d ack=0x%X spd=%d dataLength=%d ctx=%d", + (le32_to_cpu(d->prg_cpu[d->sent_ind]->data[1])>>16)&0x3f, + (le32_to_cpu(d->prg_cpu[d->sent_ind]->data[0])>>4)&0xf, + (le32_to_cpu(d->prg_cpu[d->sent_ind]->data[0])>>10)&0x3f, + status&0x1f, (status>>5)&0x3, + le32_to_cpu(d->prg_cpu[d->sent_ind]->data[3])>>16, + d->ctx); + else + DBGMSG("Packet sent to node %d tcode=0x%X tLabel=" + "%d ack=0x%X spd=%d data=0x%08X ctx=%d", + (le32_to_cpu(d->prg_cpu[d->sent_ind]->data[1]) + >>16)&0x3f, + (le32_to_cpu(d->prg_cpu[d->sent_ind]->data[0]) + >>4)&0xf, + (le32_to_cpu(d->prg_cpu[d->sent_ind]->data[0]) + >>10)&0x3f, + status&0x1f, (status>>5)&0x3, + le32_to_cpu(d->prg_cpu[d->sent_ind]->data[3]), + d->ctx); +#endif + + if (status & 0x10) { + ack = status & 0xf; + } else { + switch (status & 0x1f) { + case EVT_NO_STATUS: /* that should never happen */ + case EVT_RESERVED_A: /* that should never happen */ + case EVT_LONG_PACKET: /* that should never happen */ + PRINT(KERN_WARNING, "Received OHCI evt_* error 0x%x", status & 0x1f); + ack = ACKX_SEND_ERROR; + break; + case EVT_MISSING_ACK: + ack = ACKX_TIMEOUT; + break; + case EVT_UNDERRUN: + ack = ACKX_SEND_ERROR; + break; + case EVT_OVERRUN: /* that should never happen */ + PRINT(KERN_WARNING, "Received OHCI evt_* error 0x%x", status & 0x1f); + ack = ACKX_SEND_ERROR; + break; + case EVT_DESCRIPTOR_READ: + case EVT_DATA_READ: + case EVT_DATA_WRITE: + ack = ACKX_SEND_ERROR; + break; + case EVT_BUS_RESET: /* that should never happen */ + PRINT(KERN_WARNING, "Received OHCI evt_* error 0x%x", status & 0x1f); + ack = ACKX_SEND_ERROR; + break; + case EVT_TIMEOUT: + ack = ACKX_TIMEOUT; + break; + case EVT_TCODE_ERR: + ack = ACKX_SEND_ERROR; + break; + case EVT_RESERVED_B: /* that should never happen */ + case EVT_RESERVED_C: /* that should never happen */ + PRINT(KERN_WARNING, "Received OHCI evt_* error 0x%x", status & 0x1f); + ack = ACKX_SEND_ERROR; + break; + case EVT_UNKNOWN: + case EVT_FLUSHED: + ack = ACKX_SEND_ERROR; + break; + default: + PRINT(KERN_ERR, "Unhandled OHCI evt_* error 0x%x", status & 0x1f); + ack = ACKX_SEND_ERROR; + BUG(); + } + } + + list_del_init(&packet->driver_list); + hpsb_packet_sent(ohci->host, packet, ack); + + if (datasize) + pci_unmap_single(ohci->dev, + cpu_to_le32(d->prg_cpu[d->sent_ind]->end.address), + datasize, PCI_DMA_TODEVICE); + + d->sent_ind = (d->sent_ind+1)%d->num_desc; + d->free_prgs++; + } + + dma_trm_flush(ohci, d); + + spin_unlock_irqrestore(&d->lock, flags); +} + +static void free_dma_rcv_ctx(struct dma_rcv_ctx *d) +{ + int i; + struct ti_ohci *ohci = d->ohci; + + if (ohci == NULL) + return; + + DBGMSG("Freeing dma_rcv_ctx %d", d->ctx); + + if (d->buf_cpu) { + for (i=0; i<d->num_desc; i++) + if (d->buf_cpu[i] && d->buf_bus[i]) + pci_free_consistent( + ohci->dev, d->buf_size, + d->buf_cpu[i], d->buf_bus[i]); + kfree(d->buf_cpu); + kfree(d->buf_bus); + } + if (d->prg_cpu) { + for (i=0; i<d->num_desc; i++) + if (d->prg_cpu[i] && d->prg_bus[i]) + pci_pool_free(d->prg_pool, d->prg_cpu[i], + d->prg_bus[i]); + pci_pool_destroy(d->prg_pool); + kfree(d->prg_cpu); + kfree(d->prg_bus); + } + kfree(d->spb); + + /* Mark this context as freed. */ + d->ohci = NULL; +} + +static int +alloc_dma_rcv_ctx(struct ti_ohci *ohci, struct dma_rcv_ctx *d, + enum context_type type, int ctx, int num_desc, + int buf_size, int split_buf_size, int context_base) +{ + int i, len; + static int num_allocs; + static char pool_name[20]; + + d->ohci = ohci; + d->type = type; + d->ctx = ctx; + + d->num_desc = num_desc; + d->buf_size = buf_size; + d->split_buf_size = split_buf_size; + + d->ctrlSet = 0; + d->ctrlClear = 0; + d->cmdPtr = 0; + + d->buf_cpu = kzalloc(d->num_desc * sizeof(*d->buf_cpu), GFP_ATOMIC); + d->buf_bus = kzalloc(d->num_desc * sizeof(*d->buf_bus), GFP_ATOMIC); + + if (d->buf_cpu == NULL || d->buf_bus == NULL) { + PRINT(KERN_ERR, "Failed to allocate %s", "DMA buffer"); + free_dma_rcv_ctx(d); + return -ENOMEM; + } + + d->prg_cpu = kzalloc(d->num_desc * sizeof(*d->prg_cpu), GFP_ATOMIC); + d->prg_bus = kzalloc(d->num_desc * sizeof(*d->prg_bus), GFP_ATOMIC); + + if (d->prg_cpu == NULL || d->prg_bus == NULL) { + PRINT(KERN_ERR, "Failed to allocate %s", "DMA prg"); + free_dma_rcv_ctx(d); + return -ENOMEM; + } + + d->spb = kmalloc(d->split_buf_size, GFP_ATOMIC); + + if (d->spb == NULL) { + PRINT(KERN_ERR, "Failed to allocate %s", "split buffer"); + free_dma_rcv_ctx(d); + return -ENOMEM; + } + + len = sprintf(pool_name, "ohci1394_rcv_prg"); + sprintf(pool_name+len, "%d", num_allocs); + d->prg_pool = pci_pool_create(pool_name, ohci->dev, + sizeof(struct dma_cmd), 4, 0); + if(d->prg_pool == NULL) + { + PRINT(KERN_ERR, "pci_pool_create failed for %s", pool_name); + free_dma_rcv_ctx(d); + return -ENOMEM; + } + num_allocs++; + + for (i=0; i<d->num_desc; i++) { + d->buf_cpu[i] = pci_alloc_consistent(ohci->dev, + d->buf_size, + d->buf_bus+i); + + if (d->buf_cpu[i] != NULL) { + memset(d->buf_cpu[i], 0, d->buf_size); + } else { + PRINT(KERN_ERR, + "Failed to allocate %s", "DMA buffer"); + free_dma_rcv_ctx(d); + return -ENOMEM; + } + + d->prg_cpu[i] = pci_pool_alloc(d->prg_pool, GFP_KERNEL, d->prg_bus+i); + + if (d->prg_cpu[i] != NULL) { + memset(d->prg_cpu[i], 0, sizeof(struct dma_cmd)); + } else { + PRINT(KERN_ERR, + "Failed to allocate %s", "DMA prg"); + free_dma_rcv_ctx(d); + return -ENOMEM; + } + } + + spin_lock_init(&d->lock); + + d->ctrlSet = context_base + OHCI1394_ContextControlSet; + d->ctrlClear = context_base + OHCI1394_ContextControlClear; + d->cmdPtr = context_base + OHCI1394_ContextCommandPtr; + + tasklet_init(&d->task, dma_rcv_tasklet, (unsigned long) d); + return 0; +} + +static void free_dma_trm_ctx(struct dma_trm_ctx *d) +{ + int i; + struct ti_ohci *ohci = d->ohci; + + if (ohci == NULL) + return; + + DBGMSG("Freeing dma_trm_ctx %d", d->ctx); + + if (d->prg_cpu) { + for (i=0; i<d->num_desc; i++) + if (d->prg_cpu[i] && d->prg_bus[i]) + pci_pool_free(d->prg_pool, d->prg_cpu[i], + d->prg_bus[i]); + pci_pool_destroy(d->prg_pool); + kfree(d->prg_cpu); + kfree(d->prg_bus); + } + + /* Mark this context as freed. */ + d->ohci = NULL; +} + +static int +alloc_dma_trm_ctx(struct ti_ohci *ohci, struct dma_trm_ctx *d, + enum context_type type, int ctx, int num_desc, + int context_base) +{ + int i, len; + static char pool_name[20]; + static int num_allocs=0; + + d->ohci = ohci; + d->type = type; + d->ctx = ctx; + d->num_desc = num_desc; + d->ctrlSet = 0; + d->ctrlClear = 0; + d->cmdPtr = 0; + + d->prg_cpu = kzalloc(d->num_desc * sizeof(*d->prg_cpu), GFP_KERNEL); + d->prg_bus = kzalloc(d->num_desc * sizeof(*d->prg_bus), GFP_KERNEL); + + if (d->prg_cpu == NULL || d->prg_bus == NULL) { + PRINT(KERN_ERR, "Failed to allocate %s", "AT DMA prg"); + free_dma_trm_ctx(d); + return -ENOMEM; + } + + len = sprintf(pool_name, "ohci1394_trm_prg"); + sprintf(pool_name+len, "%d", num_allocs); + d->prg_pool = pci_pool_create(pool_name, ohci->dev, + sizeof(struct at_dma_prg), 4, 0); + if (d->prg_pool == NULL) { + PRINT(KERN_ERR, "pci_pool_create failed for %s", pool_name); + free_dma_trm_ctx(d); + return -ENOMEM; + } + num_allocs++; + + for (i = 0; i < d->num_desc; i++) { + d->prg_cpu[i] = pci_pool_alloc(d->prg_pool, GFP_KERNEL, d->prg_bus+i); + + if (d->prg_cpu[i] != NULL) { + memset(d->prg_cpu[i], 0, sizeof(struct at_dma_prg)); + } else { + PRINT(KERN_ERR, + "Failed to allocate %s", "AT DMA prg"); + free_dma_trm_ctx(d); + return -ENOMEM; + } + } + + spin_lock_init(&d->lock); + + /* initialize tasklet */ + d->ctrlSet = context_base + OHCI1394_ContextControlSet; + d->ctrlClear = context_base + OHCI1394_ContextControlClear; + d->cmdPtr = context_base + OHCI1394_ContextCommandPtr; + tasklet_init(&d->task, dma_trm_tasklet, (unsigned long)d); + return 0; +} + +static void ohci_set_hw_config_rom(struct hpsb_host *host, quadlet_t *config_rom) +{ + struct ti_ohci *ohci = host->hostdata; + + reg_write(ohci, OHCI1394_ConfigROMhdr, be32_to_cpu(config_rom[0])); + reg_write(ohci, OHCI1394_BusOptions, be32_to_cpu(config_rom[2])); + + memcpy(ohci->csr_config_rom_cpu, config_rom, OHCI_CONFIG_ROM_LEN); +} + + +static quadlet_t ohci_hw_csr_reg(struct hpsb_host *host, int reg, + quadlet_t data, quadlet_t compare) +{ + struct ti_ohci *ohci = host->hostdata; + int i; + + reg_write(ohci, OHCI1394_CSRData, data); + reg_write(ohci, OHCI1394_CSRCompareData, compare); + reg_write(ohci, OHCI1394_CSRControl, reg & 0x3); + + for (i = 0; i < OHCI_LOOP_COUNT; i++) { + if (reg_read(ohci, OHCI1394_CSRControl) & 0x80000000) + break; + + mdelay(1); + } + + return reg_read(ohci, OHCI1394_CSRData); +} + +static struct hpsb_host_driver ohci1394_driver = { + .owner = THIS_MODULE, + .name = OHCI1394_DRIVER_NAME, + .set_hw_config_rom = ohci_set_hw_config_rom, + .transmit_packet = ohci_transmit, + .devctl = ohci_devctl, + .isoctl = ohci_isoctl, + .hw_csr_reg = ohci_hw_csr_reg, +}; + +/*********************************** + * PCI Driver Interface functions * + ***********************************/ + +#ifdef CONFIG_PPC_PMAC +static void ohci1394_pmac_on(struct pci_dev *dev) +{ + if (machine_is(powermac)) { + struct device_node *ofn = pci_device_to_OF_node(dev); + + if (ofn) { + pmac_call_feature(PMAC_FTR_1394_CABLE_POWER, ofn, 0, 1); + pmac_call_feature(PMAC_FTR_1394_ENABLE, ofn, 0, 1); + } + } +} + +static void ohci1394_pmac_off(struct pci_dev *dev) +{ + if (machine_is(powermac)) { + struct device_node *ofn = pci_device_to_OF_node(dev); + + if (ofn) { + pmac_call_feature(PMAC_FTR_1394_ENABLE, ofn, 0, 0); + pmac_call_feature(PMAC_FTR_1394_CABLE_POWER, ofn, 0, 0); + } + } +} +#else +#define ohci1394_pmac_on(dev) +#define ohci1394_pmac_off(dev) +#endif /* CONFIG_PPC_PMAC */ + +static int __devinit ohci1394_pci_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + struct hpsb_host *host; + struct ti_ohci *ohci; /* shortcut to currently handled device */ + resource_size_t ohci_base; + int err = -ENOMEM; + + ohci1394_pmac_on(dev); + if (pci_enable_device(dev)) { + PRINT_G(KERN_ERR, "Failed to enable OHCI hardware"); + err = -ENXIO; + goto err; + } + pci_set_master(dev); + + host = hpsb_alloc_host(&ohci1394_driver, sizeof(struct ti_ohci), &dev->dev); + if (!host) { + PRINT_G(KERN_ERR, "Failed to allocate %s", "host structure"); + goto err; + } + ohci = host->hostdata; + ohci->dev = dev; + ohci->host = host; + ohci->init_state = OHCI_INIT_ALLOC_HOST; + host->pdev = dev; + pci_set_drvdata(dev, ohci); + + /* We don't want hardware swapping */ + pci_write_config_dword(dev, OHCI1394_PCI_HCI_Control, 0); + + /* Some oddball Apple controllers do not order the selfid + * properly, so we make up for it here. */ +#ifndef __LITTLE_ENDIAN + /* XXX: Need a better way to check this. I'm wondering if we can + * read the values of the OHCI1394_PCI_HCI_Control and the + * noByteSwapData registers to see if they were not cleared to + * zero. Should this work? Obviously it's not defined what these + * registers will read when they aren't supported. Bleh! */ + if (dev->vendor == PCI_VENDOR_ID_APPLE && + dev->device == PCI_DEVICE_ID_APPLE_UNI_N_FW) { + ohci->no_swap_incoming = 1; + ohci->selfid_swap = 0; + } else + ohci->selfid_swap = 1; +#endif + + +#ifndef PCI_DEVICE_ID_NVIDIA_NFORCE2_FW +#define PCI_DEVICE_ID_NVIDIA_NFORCE2_FW 0x006e +#endif + + /* These chipsets require a bit of extra care when checking after + * a busreset. */ + if ((dev->vendor == PCI_VENDOR_ID_APPLE && + dev->device == PCI_DEVICE_ID_APPLE_UNI_N_FW) || + (dev->vendor == PCI_VENDOR_ID_NVIDIA && + dev->device == PCI_DEVICE_ID_NVIDIA_NFORCE2_FW)) + ohci->check_busreset = 1; + + /* We hardwire the MMIO length, since some CardBus adaptors + * fail to report the right length. Anyway, the ohci spec + * clearly says it's 2kb, so this shouldn't be a problem. */ + ohci_base = pci_resource_start(dev, 0); + if (pci_resource_len(dev, 0) < OHCI1394_REGISTER_SIZE) + PRINT(KERN_WARNING, "PCI resource length of 0x%llx too small!", + (unsigned long long)pci_resource_len(dev, 0)); + + if (!request_mem_region(ohci_base, OHCI1394_REGISTER_SIZE, + OHCI1394_DRIVER_NAME)) { + PRINT_G(KERN_ERR, "MMIO resource (0x%llx - 0x%llx) unavailable", + (unsigned long long)ohci_base, + (unsigned long long)ohci_base + OHCI1394_REGISTER_SIZE); + goto err; + } + ohci->init_state = OHCI_INIT_HAVE_MEM_REGION; + + ohci->registers = ioremap(ohci_base, OHCI1394_REGISTER_SIZE); + if (ohci->registers == NULL) { + PRINT_G(KERN_ERR, "Failed to remap registers"); + err = -ENXIO; + goto err; + } + ohci->init_state = OHCI_INIT_HAVE_IOMAPPING; + DBGMSG("Remapped memory spaces reg 0x%p", ohci->registers); + + /* csr_config rom allocation */ + ohci->csr_config_rom_cpu = + pci_alloc_consistent(ohci->dev, OHCI_CONFIG_ROM_LEN, + &ohci->csr_config_rom_bus); + if (ohci->csr_config_rom_cpu == NULL) { + PRINT_G(KERN_ERR, "Failed to allocate %s", "buffer config rom"); + goto err; + } + ohci->init_state = OHCI_INIT_HAVE_CONFIG_ROM_BUFFER; + + /* self-id dma buffer allocation */ + ohci->selfid_buf_cpu = + pci_alloc_consistent(ohci->dev, OHCI1394_SI_DMA_BUF_SIZE, + &ohci->selfid_buf_bus); + if (ohci->selfid_buf_cpu == NULL) { + PRINT_G(KERN_ERR, "Failed to allocate %s", "self-ID buffer"); + goto err; + } + ohci->init_state = OHCI_INIT_HAVE_SELFID_BUFFER; + + if ((unsigned long)ohci->selfid_buf_cpu & 0x1fff) + PRINT(KERN_INFO, "SelfID buffer %p is not aligned on " + "8Kb boundary... may cause problems on some CXD3222 chip", + ohci->selfid_buf_cpu); + + /* No self-id errors at startup */ + ohci->self_id_errors = 0; + + ohci->init_state = OHCI_INIT_HAVE_TXRX_BUFFERS__MAYBE; + /* AR DMA request context allocation */ + if (alloc_dma_rcv_ctx(ohci, &ohci->ar_req_context, + DMA_CTX_ASYNC_REQ, 0, AR_REQ_NUM_DESC, + AR_REQ_BUF_SIZE, AR_REQ_SPLIT_BUF_SIZE, + OHCI1394_AsReqRcvContextBase) < 0) { + PRINT_G(KERN_ERR, "Failed to allocate %s", "AR Req context"); + goto err; + } + /* AR DMA response context allocation */ + if (alloc_dma_rcv_ctx(ohci, &ohci->ar_resp_context, + DMA_CTX_ASYNC_RESP, 0, AR_RESP_NUM_DESC, + AR_RESP_BUF_SIZE, AR_RESP_SPLIT_BUF_SIZE, + OHCI1394_AsRspRcvContextBase) < 0) { + PRINT_G(KERN_ERR, "Failed to allocate %s", "AR Resp context"); + goto err; + } + /* AT DMA request context */ + if (alloc_dma_trm_ctx(ohci, &ohci->at_req_context, + DMA_CTX_ASYNC_REQ, 0, AT_REQ_NUM_DESC, + OHCI1394_AsReqTrContextBase) < 0) { + PRINT_G(KERN_ERR, "Failed to allocate %s", "AT Req context"); + goto err; + } + /* AT DMA response context */ + if (alloc_dma_trm_ctx(ohci, &ohci->at_resp_context, + DMA_CTX_ASYNC_RESP, 1, AT_RESP_NUM_DESC, + OHCI1394_AsRspTrContextBase) < 0) { + PRINT_G(KERN_ERR, "Failed to allocate %s", "AT Resp context"); + goto err; + } + /* Start off with a soft reset, to clear everything to a sane + * state. */ + ohci_soft_reset(ohci); + + /* Now enable LPS, which we need in order to start accessing + * most of the registers. In fact, on some cards (ALI M5251), + * accessing registers in the SClk domain without LPS enabled + * will lock up the machine. Wait 50msec to make sure we have + * full link enabled. */ + reg_write(ohci, OHCI1394_HCControlSet, OHCI1394_HCControl_LPS); + + /* Disable and clear interrupts */ + reg_write(ohci, OHCI1394_IntEventClear, 0xffffffff); + reg_write(ohci, OHCI1394_IntMaskClear, 0xffffffff); + + mdelay(50); + + /* Determine the number of available IR and IT contexts. */ + ohci->nb_iso_rcv_ctx = + get_nb_iso_ctx(ohci, OHCI1394_IsoRecvIntMaskSet); + ohci->nb_iso_xmit_ctx = + get_nb_iso_ctx(ohci, OHCI1394_IsoXmitIntMaskSet); + + /* Set the usage bits for non-existent contexts so they can't + * be allocated */ + ohci->ir_ctx_usage = ~0 << ohci->nb_iso_rcv_ctx; + ohci->it_ctx_usage = ~0 << ohci->nb_iso_xmit_ctx; + + INIT_LIST_HEAD(&ohci->iso_tasklet_list); + spin_lock_init(&ohci->iso_tasklet_list_lock); + ohci->ISO_channel_usage = 0; + spin_lock_init(&ohci->IR_channel_lock); + + spin_lock_init(&ohci->event_lock); + + /* + * interrupts are disabled, all right, but... due to IRQF_SHARED we + * might get called anyway. We'll see no event, of course, but + * we need to get to that "no event", so enough should be initialized + * by that point. + */ + if (request_irq(dev->irq, ohci_irq_handler, IRQF_SHARED, + OHCI1394_DRIVER_NAME, ohci)) { + PRINT_G(KERN_ERR, "Failed to allocate interrupt %d", dev->irq); + goto err; + } + ohci->init_state = OHCI_INIT_HAVE_IRQ; + ohci_initialize(ohci); + + /* Set certain csr values */ + host->csr.guid_hi = reg_read(ohci, OHCI1394_GUIDHi); + host->csr.guid_lo = reg_read(ohci, OHCI1394_GUIDLo); + host->csr.cyc_clk_acc = 100; /* how do we determine clk accuracy? */ + host->csr.max_rec = (reg_read(ohci, OHCI1394_BusOptions) >> 12) & 0xf; + host->csr.lnk_spd = reg_read(ohci, OHCI1394_BusOptions) & 0x7; + + if (phys_dma) { + host->low_addr_space = + (u64) reg_read(ohci, OHCI1394_PhyUpperBound) << 16; + if (!host->low_addr_space) + host->low_addr_space = OHCI1394_PHYS_UPPER_BOUND_FIXED; + } + host->middle_addr_space = OHCI1394_MIDDLE_ADDRESS_SPACE; + + /* Tell the highlevel this host is ready */ + if (hpsb_add_host(host)) { + PRINT_G(KERN_ERR, "Failed to register host with highlevel"); + goto err; + } + ohci->init_state = OHCI_INIT_DONE; + + return 0; +err: + ohci1394_pci_remove(dev); + return err; +} + +static void ohci1394_pci_remove(struct pci_dev *dev) +{ + struct ti_ohci *ohci; + struct device *device; + + ohci = pci_get_drvdata(dev); + if (!ohci) + goto out; + + device = get_device(&ohci->host->device); + + switch (ohci->init_state) { + case OHCI_INIT_DONE: + hpsb_remove_host(ohci->host); + + /* Clear out BUS Options */ + reg_write(ohci, OHCI1394_ConfigROMhdr, 0); + reg_write(ohci, OHCI1394_BusOptions, + (reg_read(ohci, OHCI1394_BusOptions) & 0x0000f007) | + 0x00ff0000); + memset(ohci->csr_config_rom_cpu, 0, OHCI_CONFIG_ROM_LEN); + + case OHCI_INIT_HAVE_IRQ: + /* Clear interrupt registers */ + reg_write(ohci, OHCI1394_IntMaskClear, 0xffffffff); + reg_write(ohci, OHCI1394_IntEventClear, 0xffffffff); + reg_write(ohci, OHCI1394_IsoXmitIntMaskClear, 0xffffffff); + reg_write(ohci, OHCI1394_IsoXmitIntEventClear, 0xffffffff); + reg_write(ohci, OHCI1394_IsoRecvIntMaskClear, 0xffffffff); + reg_write(ohci, OHCI1394_IsoRecvIntEventClear, 0xffffffff); + + /* Disable IRM Contender */ + set_phy_reg(ohci, 4, ~0xc0 & get_phy_reg(ohci, 4)); + + /* Clear link control register */ + reg_write(ohci, OHCI1394_LinkControlClear, 0xffffffff); + + /* Let all other nodes know to ignore us */ + ohci_devctl(ohci->host, RESET_BUS, LONG_RESET_NO_FORCE_ROOT); + + /* Soft reset before we start - this disables + * interrupts and clears linkEnable and LPS. */ + ohci_soft_reset(ohci); + free_irq(dev->irq, ohci); + + case OHCI_INIT_HAVE_TXRX_BUFFERS__MAYBE: + /* The ohci_soft_reset() stops all DMA contexts, so we + * dont need to do this. */ + free_dma_rcv_ctx(&ohci->ar_req_context); + free_dma_rcv_ctx(&ohci->ar_resp_context); + free_dma_trm_ctx(&ohci->at_req_context); + free_dma_trm_ctx(&ohci->at_resp_context); + + case OHCI_INIT_HAVE_SELFID_BUFFER: + pci_free_consistent(dev, OHCI1394_SI_DMA_BUF_SIZE, + ohci->selfid_buf_cpu, + ohci->selfid_buf_bus); + + case OHCI_INIT_HAVE_CONFIG_ROM_BUFFER: + pci_free_consistent(dev, OHCI_CONFIG_ROM_LEN, + ohci->csr_config_rom_cpu, + ohci->csr_config_rom_bus); + + case OHCI_INIT_HAVE_IOMAPPING: + iounmap(ohci->registers); + + case OHCI_INIT_HAVE_MEM_REGION: + release_mem_region(pci_resource_start(dev, 0), + OHCI1394_REGISTER_SIZE); + + case OHCI_INIT_ALLOC_HOST: + pci_set_drvdata(dev, NULL); + } + + if (device) + put_device(device); +out: + ohci1394_pmac_off(dev); +} + +#ifdef CONFIG_PM +static int ohci1394_pci_suspend(struct pci_dev *dev, pm_message_t state) +{ + int err; + struct ti_ohci *ohci = pci_get_drvdata(dev); + + if (!ohci) { + printk(KERN_ERR "%s: tried to suspend nonexisting host\n", + OHCI1394_DRIVER_NAME); + return -ENXIO; + } + DBGMSG("suspend called"); + + /* Clear the async DMA contexts and stop using the controller */ + hpsb_bus_reset(ohci->host); + + /* See ohci1394_pci_remove() for comments on this sequence */ + reg_write(ohci, OHCI1394_ConfigROMhdr, 0); + reg_write(ohci, OHCI1394_BusOptions, + (reg_read(ohci, OHCI1394_BusOptions) & 0x0000f007) | + 0x00ff0000); + reg_write(ohci, OHCI1394_IntMaskClear, 0xffffffff); + reg_write(ohci, OHCI1394_IntEventClear, 0xffffffff); + reg_write(ohci, OHCI1394_IsoXmitIntMaskClear, 0xffffffff); + reg_write(ohci, OHCI1394_IsoXmitIntEventClear, 0xffffffff); + reg_write(ohci, OHCI1394_IsoRecvIntMaskClear, 0xffffffff); + reg_write(ohci, OHCI1394_IsoRecvIntEventClear, 0xffffffff); + set_phy_reg(ohci, 4, ~0xc0 & get_phy_reg(ohci, 4)); + reg_write(ohci, OHCI1394_LinkControlClear, 0xffffffff); + ohci_devctl(ohci->host, RESET_BUS, LONG_RESET_NO_FORCE_ROOT); + ohci_soft_reset(ohci); + + err = pci_save_state(dev); + if (err) { + PRINT(KERN_ERR, "pci_save_state failed with %d", err); + return err; + } + err = pci_set_power_state(dev, pci_choose_state(dev, state)); + if (err) + DBGMSG("pci_set_power_state failed with %d", err); + ohci1394_pmac_off(dev); + + return 0; +} + +static int ohci1394_pci_resume(struct pci_dev *dev) +{ + int err; + struct ti_ohci *ohci = pci_get_drvdata(dev); + + if (!ohci) { + printk(KERN_ERR "%s: tried to resume nonexisting host\n", + OHCI1394_DRIVER_NAME); + return -ENXIO; + } + DBGMSG("resume called"); + + ohci1394_pmac_on(dev); + pci_set_power_state(dev, PCI_D0); + pci_restore_state(dev); + err = pci_enable_device(dev); + if (err) { + PRINT(KERN_ERR, "pci_enable_device failed with %d", err); + return err; + } + + /* See ohci1394_pci_probe() for comments on this sequence */ + ohci_soft_reset(ohci); + reg_write(ohci, OHCI1394_HCControlSet, OHCI1394_HCControl_LPS); + reg_write(ohci, OHCI1394_IntEventClear, 0xffffffff); + reg_write(ohci, OHCI1394_IntMaskClear, 0xffffffff); + mdelay(50); + ohci_initialize(ohci); + + hpsb_resume_host(ohci->host); + return 0; +} +#endif /* CONFIG_PM */ + +static struct pci_device_id ohci1394_pci_tbl[] = { + { + .class = PCI_CLASS_SERIAL_FIREWIRE_OHCI, + .class_mask = PCI_ANY_ID, + .vendor = PCI_ANY_ID, + .device = PCI_ANY_ID, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { 0, }, +}; + +MODULE_DEVICE_TABLE(pci, ohci1394_pci_tbl); + +static struct pci_driver ohci1394_pci_driver = { + .name = OHCI1394_DRIVER_NAME, + .id_table = ohci1394_pci_tbl, + .probe = ohci1394_pci_probe, + .remove = ohci1394_pci_remove, +#ifdef CONFIG_PM + .resume = ohci1394_pci_resume, + .suspend = ohci1394_pci_suspend, +#endif +}; + +/*********************************** + * OHCI1394 Video Interface * + ***********************************/ + +/* essentially the only purpose of this code is to allow another + module to hook into ohci's interrupt handler */ + +/* returns zero if successful, one if DMA context is locked up */ +int ohci1394_stop_context(struct ti_ohci *ohci, int reg, char *msg) +{ + int i=0; + + /* stop the channel program if it's still running */ + reg_write(ohci, reg, 0x8000); + + /* Wait until it effectively stops */ + while (reg_read(ohci, reg) & 0x400) { + i++; + if (i>5000) { + PRINT(KERN_ERR, + "Runaway loop while stopping context: %s...", msg ? msg : ""); + return 1; + } + + mb(); + udelay(10); + } + if (msg) PRINT(KERN_ERR, "%s: dma prg stopped", msg); + return 0; +} + +void ohci1394_init_iso_tasklet(struct ohci1394_iso_tasklet *tasklet, int type, + void (*func)(unsigned long), unsigned long data) +{ + tasklet_init(&tasklet->tasklet, func, data); + tasklet->type = type; + /* We init the tasklet->link field, so we can list_del() it + * without worrying whether it was added to the list or not. */ + INIT_LIST_HEAD(&tasklet->link); +} + +int ohci1394_register_iso_tasklet(struct ti_ohci *ohci, + struct ohci1394_iso_tasklet *tasklet) +{ + unsigned long flags, *usage; + int n, i, r = -EBUSY; + + if (tasklet->type == OHCI_ISO_TRANSMIT) { + n = ohci->nb_iso_xmit_ctx; + usage = &ohci->it_ctx_usage; + } + else { + n = ohci->nb_iso_rcv_ctx; + usage = &ohci->ir_ctx_usage; + + /* only one receive context can be multichannel (OHCI sec 10.4.1) */ + if (tasklet->type == OHCI_ISO_MULTICHANNEL_RECEIVE) { + if (test_and_set_bit(0, &ohci->ir_multichannel_used)) { + return r; + } + } + } + + spin_lock_irqsave(&ohci->iso_tasklet_list_lock, flags); + + for (i = 0; i < n; i++) + if (!test_and_set_bit(i, usage)) { + tasklet->context = i; + list_add_tail(&tasklet->link, &ohci->iso_tasklet_list); + r = 0; + break; + } + + spin_unlock_irqrestore(&ohci->iso_tasklet_list_lock, flags); + + return r; +} + +void ohci1394_unregister_iso_tasklet(struct ti_ohci *ohci, + struct ohci1394_iso_tasklet *tasklet) +{ + unsigned long flags; + + tasklet_kill(&tasklet->tasklet); + + spin_lock_irqsave(&ohci->iso_tasklet_list_lock, flags); + + if (tasklet->type == OHCI_ISO_TRANSMIT) + clear_bit(tasklet->context, &ohci->it_ctx_usage); + else { + clear_bit(tasklet->context, &ohci->ir_ctx_usage); + + if (tasklet->type == OHCI_ISO_MULTICHANNEL_RECEIVE) { + clear_bit(0, &ohci->ir_multichannel_used); + } + } + + list_del(&tasklet->link); + + spin_unlock_irqrestore(&ohci->iso_tasklet_list_lock, flags); +} + +EXPORT_SYMBOL(ohci1394_stop_context); +EXPORT_SYMBOL(ohci1394_init_iso_tasklet); +EXPORT_SYMBOL(ohci1394_register_iso_tasklet); +EXPORT_SYMBOL(ohci1394_unregister_iso_tasklet); + +/*********************************** + * General module initialization * + ***********************************/ + +MODULE_AUTHOR("Sebastien Rougeaux <sebastien.rougeaux@anu.edu.au>"); +MODULE_DESCRIPTION("Driver for PCI OHCI IEEE-1394 controllers"); +MODULE_LICENSE("GPL"); + +static void __exit ohci1394_cleanup (void) +{ + pci_unregister_driver(&ohci1394_pci_driver); +} + +static int __init ohci1394_init(void) +{ + return pci_register_driver(&ohci1394_pci_driver); +} + +module_init(ohci1394_init); +module_exit(ohci1394_cleanup); diff --git a/drivers/ieee1394/ohci1394.h b/drivers/ieee1394/ohci1394.h new file mode 100644 index 0000000..7fb8ab9 --- /dev/null +++ b/drivers/ieee1394/ohci1394.h @@ -0,0 +1,453 @@ +/* + * ohci1394.h - driver for OHCI 1394 boards + * Copyright (C)1999,2000 Sebastien Rougeaux <sebastien.rougeaux@anu.edu.au> + * Gord Peters <GordPeters@smarttech.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _OHCI1394_H +#define _OHCI1394_H + +#include "ieee1394_types.h" +#include <asm/io.h> + +#define OHCI1394_DRIVER_NAME "ohci1394" + +#define OHCI1394_MAX_AT_REQ_RETRIES 0xf +#define OHCI1394_MAX_AT_RESP_RETRIES 0x2 +#define OHCI1394_MAX_PHYS_RESP_RETRIES 0x8 +#define OHCI1394_MAX_SELF_ID_ERRORS 16 + +#define AR_REQ_NUM_DESC 4 /* number of AR req descriptors */ +#define AR_REQ_BUF_SIZE PAGE_SIZE /* size of AR req buffers */ +#define AR_REQ_SPLIT_BUF_SIZE PAGE_SIZE /* split packet buffer */ + +#define AR_RESP_NUM_DESC 4 /* number of AR resp descriptors */ +#define AR_RESP_BUF_SIZE PAGE_SIZE /* size of AR resp buffers */ +#define AR_RESP_SPLIT_BUF_SIZE PAGE_SIZE /* split packet buffer */ + +#define IR_NUM_DESC 16 /* number of IR descriptors */ +#define IR_BUF_SIZE PAGE_SIZE /* 4096 bytes/buffer */ +#define IR_SPLIT_BUF_SIZE PAGE_SIZE /* split packet buffer */ + +#define IT_NUM_DESC 16 /* number of IT descriptors */ + +#define AT_REQ_NUM_DESC 32 /* number of AT req descriptors */ +#define AT_RESP_NUM_DESC 32 /* number of AT resp descriptors */ + +#define OHCI_LOOP_COUNT 100 /* Number of loops for reg read waits */ + +#define OHCI_CONFIG_ROM_LEN 1024 /* Length of the mapped configrom space */ + +#define OHCI1394_SI_DMA_BUF_SIZE 8192 /* length of the selfid buffer */ + +/* PCI configuration space addresses */ +#define OHCI1394_PCI_HCI_Control 0x40 + +struct dma_cmd { + u32 control; + u32 address; + u32 branchAddress; + u32 status; +}; + +/* + * FIXME: + * It is important that a single at_dma_prg does not cross a page boundary + * The proper way to do it would be to do the check dynamically as the + * programs are inserted into the AT fifo. + */ +struct at_dma_prg { + struct dma_cmd begin; + quadlet_t data[4]; + struct dma_cmd end; + quadlet_t pad[4]; /* FIXME: quick hack for memory alignment */ +}; + +/* identify whether a DMA context is asynchronous or isochronous */ +enum context_type { DMA_CTX_ASYNC_REQ, DMA_CTX_ASYNC_RESP, DMA_CTX_ISO }; + +/* DMA receive context */ +struct dma_rcv_ctx { + struct ti_ohci *ohci; + enum context_type type; + int ctx; + unsigned int num_desc; + + unsigned int buf_size; + unsigned int split_buf_size; + + /* dma block descriptors */ + struct dma_cmd **prg_cpu; + dma_addr_t *prg_bus; + struct pci_pool *prg_pool; + + /* dma buffers */ + quadlet_t **buf_cpu; + dma_addr_t *buf_bus; + + unsigned int buf_ind; + unsigned int buf_offset; + quadlet_t *spb; + spinlock_t lock; + struct tasklet_struct task; + int ctrlClear; + int ctrlSet; + int cmdPtr; + int ctxtMatch; +}; + +/* DMA transmit context */ +struct dma_trm_ctx { + struct ti_ohci *ohci; + enum context_type type; + int ctx; + unsigned int num_desc; + + /* dma block descriptors */ + struct at_dma_prg **prg_cpu; + dma_addr_t *prg_bus; + struct pci_pool *prg_pool; + + unsigned int prg_ind; + unsigned int sent_ind; + int free_prgs; + quadlet_t *branchAddrPtr; + + /* list of packets inserted in the AT FIFO */ + struct list_head fifo_list; + + /* list of pending packets to be inserted in the AT FIFO */ + struct list_head pending_list; + + spinlock_t lock; + struct tasklet_struct task; + int ctrlClear; + int ctrlSet; + int cmdPtr; +}; + +struct ohci1394_iso_tasklet { + struct tasklet_struct tasklet; + struct list_head link; + int context; + enum { OHCI_ISO_TRANSMIT, OHCI_ISO_RECEIVE, + OHCI_ISO_MULTICHANNEL_RECEIVE } type; +}; + +struct ti_ohci { + struct pci_dev *dev; + + enum { + OHCI_INIT_ALLOC_HOST, + OHCI_INIT_HAVE_MEM_REGION, + OHCI_INIT_HAVE_IOMAPPING, + OHCI_INIT_HAVE_CONFIG_ROM_BUFFER, + OHCI_INIT_HAVE_SELFID_BUFFER, + OHCI_INIT_HAVE_TXRX_BUFFERS__MAYBE, + OHCI_INIT_HAVE_IRQ, + OHCI_INIT_DONE, + } init_state; + + /* remapped memory spaces */ + void __iomem *registers; + + /* dma buffer for self-id packets */ + quadlet_t *selfid_buf_cpu; + dma_addr_t selfid_buf_bus; + + /* buffer for csr config rom */ + quadlet_t *csr_config_rom_cpu; + dma_addr_t csr_config_rom_bus; + int csr_config_rom_length; + + unsigned int max_packet_size; + + /* async receive */ + struct dma_rcv_ctx ar_resp_context; + struct dma_rcv_ctx ar_req_context; + + /* async transmit */ + struct dma_trm_ctx at_resp_context; + struct dma_trm_ctx at_req_context; + + /* iso receive */ + int nb_iso_rcv_ctx; + unsigned long ir_ctx_usage; /* use test_and_set_bit() for atomicity */ + unsigned long ir_multichannel_used; /* ditto */ + spinlock_t IR_channel_lock; + + /* iso transmit */ + int nb_iso_xmit_ctx; + unsigned long it_ctx_usage; /* use test_and_set_bit() for atomicity */ + + u64 ISO_channel_usage; + + /* IEEE-1394 part follows */ + struct hpsb_host *host; + + int phyid, isroot; + + spinlock_t phy_reg_lock; + spinlock_t event_lock; + + int self_id_errors; + + /* Tasklets for iso receive and transmit, used by video1394 + * and dv1394 */ + struct list_head iso_tasklet_list; + spinlock_t iso_tasklet_list_lock; + + /* Swap the selfid buffer? */ + unsigned int selfid_swap:1; + /* Some Apple chipset seem to swap incoming headers for us */ + unsigned int no_swap_incoming:1; + + /* Force extra paranoia checking on bus-reset handling */ + unsigned int check_busreset:1; +}; + +static inline int cross_bound(unsigned long addr, unsigned int size) +{ + if (size == 0) + return 0; + + if (size > PAGE_SIZE) + return 1; + + if (addr >> PAGE_SHIFT != (addr + size - 1) >> PAGE_SHIFT) + return 1; + + return 0; +} + +/* + * Register read and write helper functions. + */ +static inline void reg_write(const struct ti_ohci *ohci, int offset, u32 data) +{ + writel(data, ohci->registers + offset); +} + +static inline u32 reg_read(const struct ti_ohci *ohci, int offset) +{ + return readl(ohci->registers + offset); +} + + +/* 2 KiloBytes of register space */ +#define OHCI1394_REGISTER_SIZE 0x800 + +/* Offsets relative to context bases defined below */ + +#define OHCI1394_ContextControlSet 0x000 +#define OHCI1394_ContextControlClear 0x004 +#define OHCI1394_ContextCommandPtr 0x00C + +/* register map */ +#define OHCI1394_Version 0x000 +#define OHCI1394_GUID_ROM 0x004 +#define OHCI1394_ATRetries 0x008 +#define OHCI1394_CSRData 0x00C +#define OHCI1394_CSRCompareData 0x010 +#define OHCI1394_CSRControl 0x014 +#define OHCI1394_ConfigROMhdr 0x018 +#define OHCI1394_BusID 0x01C +#define OHCI1394_BusOptions 0x020 +#define OHCI1394_GUIDHi 0x024 +#define OHCI1394_GUIDLo 0x028 +#define OHCI1394_ConfigROMmap 0x034 +#define OHCI1394_PostedWriteAddressLo 0x038 +#define OHCI1394_PostedWriteAddressHi 0x03C +#define OHCI1394_VendorID 0x040 +#define OHCI1394_HCControlSet 0x050 +#define OHCI1394_HCControlClear 0x054 +#define OHCI1394_HCControl_noByteSwap 0x40000000 +#define OHCI1394_HCControl_programPhyEnable 0x00800000 +#define OHCI1394_HCControl_aPhyEnhanceEnable 0x00400000 +#define OHCI1394_HCControl_LPS 0x00080000 +#define OHCI1394_HCControl_postedWriteEnable 0x00040000 +#define OHCI1394_HCControl_linkEnable 0x00020000 +#define OHCI1394_HCControl_softReset 0x00010000 +#define OHCI1394_SelfIDBuffer 0x064 +#define OHCI1394_SelfIDCount 0x068 +#define OHCI1394_IRMultiChanMaskHiSet 0x070 +#define OHCI1394_IRMultiChanMaskHiClear 0x074 +#define OHCI1394_IRMultiChanMaskLoSet 0x078 +#define OHCI1394_IRMultiChanMaskLoClear 0x07C +#define OHCI1394_IntEventSet 0x080 +#define OHCI1394_IntEventClear 0x084 +#define OHCI1394_IntMaskSet 0x088 +#define OHCI1394_IntMaskClear 0x08C +#define OHCI1394_IsoXmitIntEventSet 0x090 +#define OHCI1394_IsoXmitIntEventClear 0x094 +#define OHCI1394_IsoXmitIntMaskSet 0x098 +#define OHCI1394_IsoXmitIntMaskClear 0x09C +#define OHCI1394_IsoRecvIntEventSet 0x0A0 +#define OHCI1394_IsoRecvIntEventClear 0x0A4 +#define OHCI1394_IsoRecvIntMaskSet 0x0A8 +#define OHCI1394_IsoRecvIntMaskClear 0x0AC +#define OHCI1394_InitialBandwidthAvailable 0x0B0 +#define OHCI1394_InitialChannelsAvailableHi 0x0B4 +#define OHCI1394_InitialChannelsAvailableLo 0x0B8 +#define OHCI1394_FairnessControl 0x0DC +#define OHCI1394_LinkControlSet 0x0E0 +#define OHCI1394_LinkControlClear 0x0E4 +#define OHCI1394_LinkControl_RcvSelfID 0x00000200 +#define OHCI1394_LinkControl_RcvPhyPkt 0x00000400 +#define OHCI1394_LinkControl_CycleTimerEnable 0x00100000 +#define OHCI1394_LinkControl_CycleMaster 0x00200000 +#define OHCI1394_LinkControl_CycleSource 0x00400000 +#define OHCI1394_NodeID 0x0E8 +#define OHCI1394_PhyControl 0x0EC +#define OHCI1394_IsochronousCycleTimer 0x0F0 +#define OHCI1394_AsReqFilterHiSet 0x100 +#define OHCI1394_AsReqFilterHiClear 0x104 +#define OHCI1394_AsReqFilterLoSet 0x108 +#define OHCI1394_AsReqFilterLoClear 0x10C +#define OHCI1394_PhyReqFilterHiSet 0x110 +#define OHCI1394_PhyReqFilterHiClear 0x114 +#define OHCI1394_PhyReqFilterLoSet 0x118 +#define OHCI1394_PhyReqFilterLoClear 0x11C +#define OHCI1394_PhyUpperBound 0x120 + +#define OHCI1394_AsReqTrContextBase 0x180 +#define OHCI1394_AsReqTrContextControlSet 0x180 +#define OHCI1394_AsReqTrContextControlClear 0x184 +#define OHCI1394_AsReqTrCommandPtr 0x18C + +#define OHCI1394_AsRspTrContextBase 0x1A0 +#define OHCI1394_AsRspTrContextControlSet 0x1A0 +#define OHCI1394_AsRspTrContextControlClear 0x1A4 +#define OHCI1394_AsRspTrCommandPtr 0x1AC + +#define OHCI1394_AsReqRcvContextBase 0x1C0 +#define OHCI1394_AsReqRcvContextControlSet 0x1C0 +#define OHCI1394_AsReqRcvContextControlClear 0x1C4 +#define OHCI1394_AsReqRcvCommandPtr 0x1CC + +#define OHCI1394_AsRspRcvContextBase 0x1E0 +#define OHCI1394_AsRspRcvContextControlSet 0x1E0 +#define OHCI1394_AsRspRcvContextControlClear 0x1E4 +#define OHCI1394_AsRspRcvCommandPtr 0x1EC + +/* Isochronous transmit registers */ +/* Add (16 * n) for context n */ +#define OHCI1394_IsoXmitContextBase 0x200 +#define OHCI1394_IsoXmitContextControlSet 0x200 +#define OHCI1394_IsoXmitContextControlClear 0x204 +#define OHCI1394_IsoXmitCommandPtr 0x20C + +/* Isochronous receive registers */ +/* Add (32 * n) for context n */ +#define OHCI1394_IsoRcvContextBase 0x400 +#define OHCI1394_IsoRcvContextControlSet 0x400 +#define OHCI1394_IsoRcvContextControlClear 0x404 +#define OHCI1394_IsoRcvCommandPtr 0x40C +#define OHCI1394_IsoRcvContextMatch 0x410 + +/* Interrupts Mask/Events */ + +#define OHCI1394_reqTxComplete 0x00000001 +#define OHCI1394_respTxComplete 0x00000002 +#define OHCI1394_ARRQ 0x00000004 +#define OHCI1394_ARRS 0x00000008 +#define OHCI1394_RQPkt 0x00000010 +#define OHCI1394_RSPkt 0x00000020 +#define OHCI1394_isochTx 0x00000040 +#define OHCI1394_isochRx 0x00000080 +#define OHCI1394_postedWriteErr 0x00000100 +#define OHCI1394_lockRespErr 0x00000200 +#define OHCI1394_selfIDComplete 0x00010000 +#define OHCI1394_busReset 0x00020000 +#define OHCI1394_phy 0x00080000 +#define OHCI1394_cycleSynch 0x00100000 +#define OHCI1394_cycle64Seconds 0x00200000 +#define OHCI1394_cycleLost 0x00400000 +#define OHCI1394_cycleInconsistent 0x00800000 +#define OHCI1394_unrecoverableError 0x01000000 +#define OHCI1394_cycleTooLong 0x02000000 +#define OHCI1394_phyRegRcvd 0x04000000 +#define OHCI1394_masterIntEnable 0x80000000 + +/* DMA Control flags */ +#define DMA_CTL_OUTPUT_MORE 0x00000000 +#define DMA_CTL_OUTPUT_LAST 0x10000000 +#define DMA_CTL_INPUT_MORE 0x20000000 +#define DMA_CTL_INPUT_LAST 0x30000000 +#define DMA_CTL_UPDATE 0x08000000 +#define DMA_CTL_IMMEDIATE 0x02000000 +#define DMA_CTL_IRQ 0x00300000 +#define DMA_CTL_BRANCH 0x000c0000 +#define DMA_CTL_WAIT 0x00030000 + +/* OHCI evt_* error types, table 3-2 of the OHCI 1.1 spec. */ +#define EVT_NO_STATUS 0x0 /* No event status */ +#define EVT_RESERVED_A 0x1 /* Reserved, not used !!! */ +#define EVT_LONG_PACKET 0x2 /* The revc data was longer than the buf */ +#define EVT_MISSING_ACK 0x3 /* A subaction gap was detected before an ack + arrived, or recv'd ack had a parity error */ +#define EVT_UNDERRUN 0x4 /* Underrun on corresponding FIFO, packet + truncated */ +#define EVT_OVERRUN 0x5 /* A recv FIFO overflowed on reception of ISO + packet */ +#define EVT_DESCRIPTOR_READ 0x6 /* An unrecoverable error occurred while host was + reading a descriptor block */ +#define EVT_DATA_READ 0x7 /* An error occurred while host controller was + attempting to read from host memory in the data + stage of descriptor processing */ +#define EVT_DATA_WRITE 0x8 /* An error occurred while host controller was + attempting to write either during the data stage + of descriptor processing, or when processing a single + 16-bit host memory write */ +#define EVT_BUS_RESET 0x9 /* Identifies a PHY packet in the recv buffer as + being a synthesized bus reset packet */ +#define EVT_TIMEOUT 0xa /* Indicates that the asynchronous transmit response + packet expired and was not transmitted, or that an + IT DMA context experienced a skip processing overflow */ +#define EVT_TCODE_ERR 0xb /* A bad tCode is associated with this packet. + The packet was flushed */ +#define EVT_RESERVED_B 0xc /* Reserved, not used !!! */ +#define EVT_RESERVED_C 0xd /* Reserved, not used !!! */ +#define EVT_UNKNOWN 0xe /* An error condition has occurred that cannot be + represented by any other event codes defined herein. */ +#define EVT_FLUSHED 0xf /* Send by the link side of output FIFO when asynchronous + packets are being flushed due to a bus reset. */ + +#define OHCI1394_TCODE_PHY 0xE + +/* Node offset map (phys DMA area, posted write area). + * The value of OHCI1394_PHYS_UPPER_BOUND_PROGRAMMED may be modified but must + * be lower than OHCI1394_MIDDLE_ADDRESS_SPACE. + * OHCI1394_PHYS_UPPER_BOUND_FIXED and OHCI1394_MIDDLE_ADDRESS_SPACE are + * constants given by the OHCI spec. + */ +#define OHCI1394_PHYS_UPPER_BOUND_FIXED 0x000100000000ULL /* 4 GB */ +#define OHCI1394_PHYS_UPPER_BOUND_PROGRAMMED 0x010000000000ULL /* 1 TB */ +#define OHCI1394_MIDDLE_ADDRESS_SPACE 0xffff00000000ULL + +void ohci1394_init_iso_tasklet(struct ohci1394_iso_tasklet *tasklet, + int type, + void (*func)(unsigned long), + unsigned long data); +int ohci1394_register_iso_tasklet(struct ti_ohci *ohci, + struct ohci1394_iso_tasklet *tasklet); +void ohci1394_unregister_iso_tasklet(struct ti_ohci *ohci, + struct ohci1394_iso_tasklet *tasklet); +int ohci1394_stop_context(struct ti_ohci *ohci, int reg, char *msg); +struct ti_ohci *ohci1394_get_struct(int card_num); + +#endif diff --git a/drivers/ieee1394/pcilynx.c b/drivers/ieee1394/pcilynx.c new file mode 100644 index 0000000..7aee1ac --- /dev/null +++ b/drivers/ieee1394/pcilynx.c @@ -0,0 +1,1555 @@ +/* + * pcilynx.c - Texas Instruments PCILynx driver + * Copyright (C) 1999,2000 Andreas Bombe <andreas.bombe@munich.netsurf.de>, + * Stephan Linz <linz@mazet.de> + * Manfred Weihs <weihs@ict.tuwien.ac.at> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * Contributions: + * + * Manfred Weihs <weihs@ict.tuwien.ac.at> + * reading bus info block (containing GUID) from serial + * eeprom via i2c and storing it in config ROM + * Reworked code for initiating bus resets + * (long, short, with or without hold-off) + * Enhancements in async and iso send code + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/kdev_t.h> +#include <linux/dma-mapping.h> +#include <asm/byteorder.h> +#include <asm/atomic.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/irq.h> + +#include "csr1212.h" +#include "ieee1394.h" +#include "ieee1394_types.h" +#include "hosts.h" +#include "ieee1394_core.h" +#include "highlevel.h" +#include "pcilynx.h" + +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +/* print general (card independent) information */ +#define PRINT_G(level, fmt, args...) printk(level "pcilynx: " fmt "\n" , ## args) +/* print card specific information */ +#define PRINT(level, card, fmt, args...) printk(level "pcilynx%d: " fmt "\n" , card , ## args) + +#ifdef CONFIG_IEEE1394_VERBOSEDEBUG +#define PRINT_GD(level, fmt, args...) printk(level "pcilynx: " fmt "\n" , ## args) +#define PRINTD(level, card, fmt, args...) printk(level "pcilynx%d: " fmt "\n" , card , ## args) +#else +#define PRINT_GD(level, fmt, args...) do {} while (0) +#define PRINTD(level, card, fmt, args...) do {} while (0) +#endif + + +/* Module Parameters */ +static int skip_eeprom; +module_param(skip_eeprom, int, 0444); +MODULE_PARM_DESC(skip_eeprom, "Use generic bus info block instead of serial eeprom (default = 0)."); + + +static struct hpsb_host_driver lynx_driver; +static unsigned int card_id; + + + +/* + * I2C stuff + */ + +/* the i2c stuff was inspired by i2c-philips-par.c */ + +static void bit_setscl(void *data, int state) +{ + if (state) { + ((struct ti_lynx *) data)->i2c_driven_state |= 0x00000040; + } else { + ((struct ti_lynx *) data)->i2c_driven_state &= ~0x00000040; + } + reg_write((struct ti_lynx *) data, SERIAL_EEPROM_CONTROL, ((struct ti_lynx *) data)->i2c_driven_state); +} + +static void bit_setsda(void *data, int state) +{ + if (state) { + ((struct ti_lynx *) data)->i2c_driven_state |= 0x00000010; + } else { + ((struct ti_lynx *) data)->i2c_driven_state &= ~0x00000010; + } + reg_write((struct ti_lynx *) data, SERIAL_EEPROM_CONTROL, ((struct ti_lynx *) data)->i2c_driven_state); +} + +static int bit_getscl(void *data) +{ + return reg_read((struct ti_lynx *) data, SERIAL_EEPROM_CONTROL) & 0x00000040; +} + +static int bit_getsda(void *data) +{ + return reg_read((struct ti_lynx *) data, SERIAL_EEPROM_CONTROL) & 0x00000010; +} + +static struct i2c_algo_bit_data bit_data = { + .setsda = bit_setsda, + .setscl = bit_setscl, + .getsda = bit_getsda, + .getscl = bit_getscl, + .udelay = 5, + .timeout = 100, +}; + + +/* + * PCL handling functions. + */ + +static pcl_t alloc_pcl(struct ti_lynx *lynx) +{ + u8 m; + int i, j; + + spin_lock(&lynx->lock); + /* FIXME - use ffz() to make this readable */ + for (i = 0; i < (LOCALRAM_SIZE / 1024); i++) { + m = lynx->pcl_bmap[i]; + for (j = 0; j < 8; j++) { + if (m & 1<<j) { + continue; + } + m |= 1<<j; + lynx->pcl_bmap[i] = m; + spin_unlock(&lynx->lock); + return 8 * i + j; + } + } + spin_unlock(&lynx->lock); + + return -1; +} + + +#if 0 +static void free_pcl(struct ti_lynx *lynx, pcl_t pclid) +{ + int off, bit; + + off = pclid / 8; + bit = pclid % 8; + + if (pclid < 0) { + return; + } + + spin_lock(&lynx->lock); + if (lynx->pcl_bmap[off] & 1<<bit) { + lynx->pcl_bmap[off] &= ~(1<<bit); + } else { + PRINT(KERN_ERR, lynx->id, + "attempted to free unallocated PCL %d", pclid); + } + spin_unlock(&lynx->lock); +} + +/* functions useful for debugging */ +static void pretty_print_pcl(const struct ti_pcl *pcl) +{ + int i; + + printk("PCL next %08x, userdata %08x, status %08x, remtrans %08x, nextbuf %08x\n", + pcl->next, pcl->user_data, pcl->pcl_status, + pcl->remaining_transfer_count, pcl->next_data_buffer); + + printk("PCL"); + for (i=0; i<13; i++) { + printk(" c%x:%08x d%x:%08x", + i, pcl->buffer[i].control, i, pcl->buffer[i].pointer); + if (!(i & 0x3) && (i != 12)) printk("\nPCL"); + } + printk("\n"); +} + +static void print_pcl(const struct ti_lynx *lynx, pcl_t pclid) +{ + struct ti_pcl pcl; + + get_pcl(lynx, pclid, &pcl); + pretty_print_pcl(&pcl); +} +#endif + + + +/*********************************** + * IEEE-1394 functionality section * + ***********************************/ + + +static int get_phy_reg(struct ti_lynx *lynx, int addr) +{ + int retval; + int i = 0; + + unsigned long flags; + + if (addr > 15) { + PRINT(KERN_ERR, lynx->id, + "%s: PHY register address %d out of range", + __func__, addr); + return -1; + } + + spin_lock_irqsave(&lynx->phy_reg_lock, flags); + + reg_write(lynx, LINK_PHY, LINK_PHY_READ | LINK_PHY_ADDR(addr)); + do { + retval = reg_read(lynx, LINK_PHY); + + if (i > 10000) { + PRINT(KERN_ERR, lynx->id, "%s: runaway loop, aborting", + __func__); + retval = -1; + break; + } + i++; + } while ((retval & 0xf00) != LINK_PHY_RADDR(addr)); + + reg_write(lynx, LINK_INT_STATUS, LINK_INT_PHY_REG_RCVD); + spin_unlock_irqrestore(&lynx->phy_reg_lock, flags); + + if (retval != -1) { + return retval & 0xff; + } else { + return -1; + } +} + +static int set_phy_reg(struct ti_lynx *lynx, int addr, int val) +{ + unsigned long flags; + + if (addr > 15) { + PRINT(KERN_ERR, lynx->id, + "%s: PHY register address %d out of range", __func__, addr); + return -1; + } + + if (val > 0xff) { + PRINT(KERN_ERR, lynx->id, + "%s: PHY register value %d out of range", __func__, val); + return -1; + } + + spin_lock_irqsave(&lynx->phy_reg_lock, flags); + + reg_write(lynx, LINK_PHY, LINK_PHY_WRITE | LINK_PHY_ADDR(addr) + | LINK_PHY_WDATA(val)); + + spin_unlock_irqrestore(&lynx->phy_reg_lock, flags); + + return 0; +} + +static int sel_phy_reg_page(struct ti_lynx *lynx, int page) +{ + int reg; + + if (page > 7) { + PRINT(KERN_ERR, lynx->id, + "%s: PHY page %d out of range", __func__, page); + return -1; + } + + reg = get_phy_reg(lynx, 7); + if (reg != -1) { + reg &= 0x1f; + reg |= (page << 5); + set_phy_reg(lynx, 7, reg); + return 0; + } else { + return -1; + } +} + +#if 0 /* not needed at this time */ +static int sel_phy_reg_port(struct ti_lynx *lynx, int port) +{ + int reg; + + if (port > 15) { + PRINT(KERN_ERR, lynx->id, + "%s: PHY port %d out of range", __func__, port); + return -1; + } + + reg = get_phy_reg(lynx, 7); + if (reg != -1) { + reg &= 0xf0; + reg |= port; + set_phy_reg(lynx, 7, reg); + return 0; + } else { + return -1; + } +} +#endif + +static u32 get_phy_vendorid(struct ti_lynx *lynx) +{ + u32 pvid = 0; + sel_phy_reg_page(lynx, 1); + pvid |= (get_phy_reg(lynx, 10) << 16); + pvid |= (get_phy_reg(lynx, 11) << 8); + pvid |= get_phy_reg(lynx, 12); + PRINT(KERN_INFO, lynx->id, "PHY vendor id 0x%06x", pvid); + return pvid; +} + +static u32 get_phy_productid(struct ti_lynx *lynx) +{ + u32 id = 0; + sel_phy_reg_page(lynx, 1); + id |= (get_phy_reg(lynx, 13) << 16); + id |= (get_phy_reg(lynx, 14) << 8); + id |= get_phy_reg(lynx, 15); + PRINT(KERN_INFO, lynx->id, "PHY product id 0x%06x", id); + return id; +} + +static quadlet_t generate_own_selfid(struct ti_lynx *lynx, + struct hpsb_host *host) +{ + quadlet_t lsid; + char phyreg[7]; + int i; + + phyreg[0] = lynx->phy_reg0; + for (i = 1; i < 7; i++) { + phyreg[i] = get_phy_reg(lynx, i); + } + + /* FIXME? We assume a TSB21LV03A phy here. This code doesn't support + more than 3 ports on the PHY anyway. */ + + lsid = 0x80400000 | ((phyreg[0] & 0xfc) << 22); + lsid |= (phyreg[1] & 0x3f) << 16; /* gap count */ + lsid |= (phyreg[2] & 0xc0) << 8; /* max speed */ + if (!hpsb_disable_irm) + lsid |= (phyreg[6] & 0x01) << 11; /* contender (phy dependent) */ + /* lsid |= 1 << 11; *//* set contender (hack) */ + lsid |= (phyreg[6] & 0x10) >> 3; /* initiated reset */ + + for (i = 0; i < (phyreg[2] & 0xf); i++) { /* ports */ + if (phyreg[3 + i] & 0x4) { + lsid |= (((phyreg[3 + i] & 0x8) | 0x10) >> 3) + << (6 - i*2); + } else { + lsid |= 1 << (6 - i*2); + } + } + + cpu_to_be32s(&lsid); + PRINT(KERN_DEBUG, lynx->id, "generated own selfid 0x%x", lsid); + return lsid; +} + +static void handle_selfid(struct ti_lynx *lynx, struct hpsb_host *host) +{ + quadlet_t *q = lynx->rcv_page; + int phyid, isroot, size; + quadlet_t lsid = 0; + int i; + + if (lynx->phy_reg0 == -1 || lynx->selfid_size == -1) return; + + size = lynx->selfid_size; + phyid = lynx->phy_reg0; + + i = (size > 16 ? 16 : size) / 4 - 1; + while (i >= 0) { + cpu_to_be32s(&q[i]); + i--; + } + + if (!lynx->phyic.reg_1394a) { + lsid = generate_own_selfid(lynx, host); + } + + isroot = (phyid & 2) != 0; + phyid >>= 2; + PRINT(KERN_INFO, lynx->id, "SelfID process finished (phyid %d, %s)", + phyid, (isroot ? "root" : "not root")); + reg_write(lynx, LINK_ID, (0xffc0 | phyid) << 16); + + if (!lynx->phyic.reg_1394a && !size) { + hpsb_selfid_received(host, lsid); + } + + while (size > 0) { + struct selfid *sid = (struct selfid *)q; + + if (!lynx->phyic.reg_1394a && !sid->extended + && (sid->phy_id == (phyid + 1))) { + hpsb_selfid_received(host, lsid); + } + + if (q[0] == ~q[1]) { + PRINT(KERN_DEBUG, lynx->id, "SelfID packet 0x%x rcvd", + q[0]); + hpsb_selfid_received(host, q[0]); + } else { + PRINT(KERN_INFO, lynx->id, + "inconsistent selfid 0x%x/0x%x", q[0], q[1]); + } + q += 2; + size -= 8; + } + + if (!lynx->phyic.reg_1394a && isroot && phyid != 0) { + hpsb_selfid_received(host, lsid); + } + + hpsb_selfid_complete(host, phyid, isroot); + + if (host->in_bus_reset) return; /* in bus reset again */ + + if (isroot) reg_set_bits(lynx, LINK_CONTROL, LINK_CONTROL_CYCMASTER); //FIXME: I do not think, we need this here + reg_set_bits(lynx, LINK_CONTROL, + LINK_CONTROL_RCV_CMP_VALID | LINK_CONTROL_TX_ASYNC_EN + | LINK_CONTROL_RX_ASYNC_EN | LINK_CONTROL_CYCTIMEREN); +} + + + +/* This must be called with the respective queue_lock held. */ +static void send_next(struct ti_lynx *lynx, int what) +{ + struct ti_pcl pcl; + struct lynx_send_data *d; + struct hpsb_packet *packet; + +#if 0 /* has been removed from ieee1394 core */ + d = (what == hpsb_iso ? &lynx->iso_send : &lynx->async); +#else + d = &lynx->async; +#endif + if (!list_empty(&d->pcl_queue)) { + PRINT(KERN_ERR, lynx->id, "trying to queue a new packet in nonempty fifo"); + BUG(); + } + + packet = driver_packet(d->queue.next); + list_move_tail(&packet->driver_list, &d->pcl_queue); + + d->header_dma = pci_map_single(lynx->dev, packet->header, + packet->header_size, PCI_DMA_TODEVICE); + if (packet->data_size) { + d->data_dma = pci_map_single(lynx->dev, packet->data, + packet->data_size, + PCI_DMA_TODEVICE); + } else { + d->data_dma = 0; + } + + pcl.next = PCL_NEXT_INVALID; + pcl.async_error_next = PCL_NEXT_INVALID; + pcl.pcl_status = 0; + pcl.buffer[0].control = packet->speed_code << 14 | packet->header_size; +#ifndef __BIG_ENDIAN + pcl.buffer[0].control |= PCL_BIGENDIAN; +#endif + pcl.buffer[0].pointer = d->header_dma; + pcl.buffer[1].control = PCL_LAST_BUFF | packet->data_size; + pcl.buffer[1].pointer = d->data_dma; + + switch (packet->type) { + case hpsb_async: + pcl.buffer[0].control |= PCL_CMD_XMT; + break; +#if 0 /* has been removed from ieee1394 core */ + case hpsb_iso: + pcl.buffer[0].control |= PCL_CMD_XMT | PCL_ISOMODE; + break; +#endif + case hpsb_raw: + pcl.buffer[0].control |= PCL_CMD_UNFXMT; + break; + } + + put_pcl(lynx, d->pcl, &pcl); + run_pcl(lynx, d->pcl_start, d->channel); +} + + +/* called from subsystem core */ +static int lynx_transmit(struct hpsb_host *host, struct hpsb_packet *packet) +{ + struct ti_lynx *lynx = host->hostdata; + struct lynx_send_data *d; + unsigned long flags; + + if (packet->data_size >= 4096) { + PRINT(KERN_ERR, lynx->id, "transmit packet data too big (%Zd)", + packet->data_size); + return -EOVERFLOW; + } + + switch (packet->type) { + case hpsb_async: + case hpsb_raw: + d = &lynx->async; + break; +#if 0 /* has been removed from ieee1394 core */ + case hpsb_iso: + d = &lynx->iso_send; + break; +#endif + default: + PRINT(KERN_ERR, lynx->id, "invalid packet type %d", + packet->type); + return -EINVAL; + } + + if (packet->tcode == TCODE_WRITEQ + || packet->tcode == TCODE_READQ_RESPONSE) { + cpu_to_be32s(&packet->header[3]); + } + + spin_lock_irqsave(&d->queue_lock, flags); + + list_add_tail(&packet->driver_list, &d->queue); + if (list_empty(&d->pcl_queue)) + send_next(lynx, packet->type); + + spin_unlock_irqrestore(&d->queue_lock, flags); + + return 0; +} + + +/* called from subsystem core */ +static int lynx_devctl(struct hpsb_host *host, enum devctl_cmd cmd, int arg) +{ + struct ti_lynx *lynx = host->hostdata; + int retval = 0; + struct hpsb_packet *packet; + LIST_HEAD(packet_list); + unsigned long flags; + int phy_reg; + + switch (cmd) { + case RESET_BUS: + if (reg_read(lynx, LINK_INT_STATUS) & LINK_INT_PHY_BUSRESET) { + retval = 0; + break; + } + + switch (arg) { + case SHORT_RESET: + if (lynx->phyic.reg_1394a) { + phy_reg = get_phy_reg(lynx, 5); + if (phy_reg == -1) { + PRINT(KERN_ERR, lynx->id, "cannot reset bus, because read phy reg failed"); + retval = -1; + break; + } + phy_reg |= 0x40; + + PRINT(KERN_INFO, lynx->id, "resetting bus (short bus reset) on request"); + + lynx->selfid_size = -1; + lynx->phy_reg0 = -1; + set_phy_reg(lynx, 5, phy_reg); /* set ISBR */ + break; + } else { + PRINT(KERN_INFO, lynx->id, "cannot do short bus reset, because of old phy"); + /* fall through to long bus reset */ + } + case LONG_RESET: + phy_reg = get_phy_reg(lynx, 1); + if (phy_reg == -1) { + PRINT(KERN_ERR, lynx->id, "cannot reset bus, because read phy reg failed"); + retval = -1; + break; + } + phy_reg |= 0x40; + + PRINT(KERN_INFO, lynx->id, "resetting bus (long bus reset) on request"); + + lynx->selfid_size = -1; + lynx->phy_reg0 = -1; + set_phy_reg(lynx, 1, phy_reg); /* clear RHB, set IBR */ + break; + case SHORT_RESET_NO_FORCE_ROOT: + if (lynx->phyic.reg_1394a) { + phy_reg = get_phy_reg(lynx, 1); + if (phy_reg == -1) { + PRINT(KERN_ERR, lynx->id, "cannot reset bus, because read phy reg failed"); + retval = -1; + break; + } + if (phy_reg & 0x80) { + phy_reg &= ~0x80; + set_phy_reg(lynx, 1, phy_reg); /* clear RHB */ + } + + phy_reg = get_phy_reg(lynx, 5); + if (phy_reg == -1) { + PRINT(KERN_ERR, lynx->id, "cannot reset bus, because read phy reg failed"); + retval = -1; + break; + } + phy_reg |= 0x40; + + PRINT(KERN_INFO, lynx->id, "resetting bus (short bus reset, no force_root) on request"); + + lynx->selfid_size = -1; + lynx->phy_reg0 = -1; + set_phy_reg(lynx, 5, phy_reg); /* set ISBR */ + break; + } else { + PRINT(KERN_INFO, lynx->id, "cannot do short bus reset, because of old phy"); + /* fall through to long bus reset */ + } + case LONG_RESET_NO_FORCE_ROOT: + phy_reg = get_phy_reg(lynx, 1); + if (phy_reg == -1) { + PRINT(KERN_ERR, lynx->id, "cannot reset bus, because read phy reg failed"); + retval = -1; + break; + } + phy_reg &= ~0x80; + phy_reg |= 0x40; + + PRINT(KERN_INFO, lynx->id, "resetting bus (long bus reset, no force_root) on request"); + + lynx->selfid_size = -1; + lynx->phy_reg0 = -1; + set_phy_reg(lynx, 1, phy_reg); /* clear RHB, set IBR */ + break; + case SHORT_RESET_FORCE_ROOT: + if (lynx->phyic.reg_1394a) { + phy_reg = get_phy_reg(lynx, 1); + if (phy_reg == -1) { + PRINT(KERN_ERR, lynx->id, "cannot reset bus, because read phy reg failed"); + retval = -1; + break; + } + if (!(phy_reg & 0x80)) { + phy_reg |= 0x80; + set_phy_reg(lynx, 1, phy_reg); /* set RHB */ + } + + phy_reg = get_phy_reg(lynx, 5); + if (phy_reg == -1) { + PRINT(KERN_ERR, lynx->id, "cannot reset bus, because read phy reg failed"); + retval = -1; + break; + } + phy_reg |= 0x40; + + PRINT(KERN_INFO, lynx->id, "resetting bus (short bus reset, force_root set) on request"); + + lynx->selfid_size = -1; + lynx->phy_reg0 = -1; + set_phy_reg(lynx, 5, phy_reg); /* set ISBR */ + break; + } else { + PRINT(KERN_INFO, lynx->id, "cannot do short bus reset, because of old phy"); + /* fall through to long bus reset */ + } + case LONG_RESET_FORCE_ROOT: + phy_reg = get_phy_reg(lynx, 1); + if (phy_reg == -1) { + PRINT(KERN_ERR, lynx->id, "cannot reset bus, because read phy reg failed"); + retval = -1; + break; + } + phy_reg |= 0xc0; + + PRINT(KERN_INFO, lynx->id, "resetting bus (long bus reset, force_root set) on request"); + + lynx->selfid_size = -1; + lynx->phy_reg0 = -1; + set_phy_reg(lynx, 1, phy_reg); /* set IBR and RHB */ + break; + default: + PRINT(KERN_ERR, lynx->id, "unknown argument for reset_bus command %d", arg); + retval = -1; + } + + break; + + case GET_CYCLE_COUNTER: + retval = reg_read(lynx, CYCLE_TIMER); + break; + + case SET_CYCLE_COUNTER: + reg_write(lynx, CYCLE_TIMER, arg); + break; + + case SET_BUS_ID: + reg_write(lynx, LINK_ID, + (arg << 22) | (reg_read(lynx, LINK_ID) & 0x003f0000)); + break; + + case ACT_CYCLE_MASTER: + if (arg) { + reg_set_bits(lynx, LINK_CONTROL, + LINK_CONTROL_CYCMASTER); + } else { + reg_clear_bits(lynx, LINK_CONTROL, + LINK_CONTROL_CYCMASTER); + } + break; + + case CANCEL_REQUESTS: + spin_lock_irqsave(&lynx->async.queue_lock, flags); + + reg_write(lynx, DMA_CHAN_CTRL(CHANNEL_ASYNC_SEND), 0); + list_splice_init(&lynx->async.queue, &packet_list); + + if (list_empty(&lynx->async.pcl_queue)) { + spin_unlock_irqrestore(&lynx->async.queue_lock, flags); + PRINTD(KERN_DEBUG, lynx->id, "no async packet in PCL to cancel"); + } else { + struct ti_pcl pcl; + u32 ack; + + PRINT(KERN_INFO, lynx->id, "cancelling async packet, that was already in PCL"); + + get_pcl(lynx, lynx->async.pcl, &pcl); + + packet = driver_packet(lynx->async.pcl_queue.next); + list_del_init(&packet->driver_list); + + pci_unmap_single(lynx->dev, lynx->async.header_dma, + packet->header_size, PCI_DMA_TODEVICE); + if (packet->data_size) { + pci_unmap_single(lynx->dev, lynx->async.data_dma, + packet->data_size, PCI_DMA_TODEVICE); + } + + spin_unlock_irqrestore(&lynx->async.queue_lock, flags); + + if (pcl.pcl_status & DMA_CHAN_STAT_PKTCMPL) { + if (pcl.pcl_status & DMA_CHAN_STAT_SPECIALACK) { + ack = (pcl.pcl_status >> 15) & 0xf; + PRINTD(KERN_INFO, lynx->id, "special ack %d", ack); + ack = (ack == 1 ? ACKX_TIMEOUT : ACKX_SEND_ERROR); + } else { + ack = (pcl.pcl_status >> 15) & 0xf; + } + } else { + PRINT(KERN_INFO, lynx->id, "async packet was not completed"); + ack = ACKX_ABORTED; + } + hpsb_packet_sent(host, packet, ack); + } + + while (!list_empty(&packet_list)) { + packet = driver_packet(packet_list.next); + list_del_init(&packet->driver_list); + hpsb_packet_sent(host, packet, ACKX_ABORTED); + } + + break; +#if 0 /* has been removed from ieee1394 core */ + case ISO_LISTEN_CHANNEL: + spin_lock_irqsave(&lynx->iso_rcv.lock, flags); + + if (lynx->iso_rcv.chan_count++ == 0) { + reg_write(lynx, DMA_WORD1_CMP_ENABLE(CHANNEL_ISO_RCV), + DMA_WORD1_CMP_ENABLE_MASTER); + } + + spin_unlock_irqrestore(&lynx->iso_rcv.lock, flags); + break; + + case ISO_UNLISTEN_CHANNEL: + spin_lock_irqsave(&lynx->iso_rcv.lock, flags); + + if (--lynx->iso_rcv.chan_count == 0) { + reg_write(lynx, DMA_WORD1_CMP_ENABLE(CHANNEL_ISO_RCV), + 0); + } + + spin_unlock_irqrestore(&lynx->iso_rcv.lock, flags); + break; +#endif + default: + PRINT(KERN_ERR, lynx->id, "unknown devctl command %d", cmd); + retval = -1; + } + + return retval; +} + + +/*************************************** + * IEEE-1394 functionality section END * + ***************************************/ + + +/******************************************************** + * Global stuff (interrupt handler, init/shutdown code) * + ********************************************************/ + + +static irqreturn_t lynx_irq_handler(int irq, void *dev_id) +{ + struct ti_lynx *lynx = (struct ti_lynx *)dev_id; + struct hpsb_host *host = lynx->host; + u32 intmask; + u32 linkint; + + linkint = reg_read(lynx, LINK_INT_STATUS); + intmask = reg_read(lynx, PCI_INT_STATUS); + + if (!(intmask & PCI_INT_INT_PEND)) + return IRQ_NONE; + + PRINTD(KERN_DEBUG, lynx->id, "interrupt: 0x%08x / 0x%08x", intmask, + linkint); + + reg_write(lynx, LINK_INT_STATUS, linkint); + reg_write(lynx, PCI_INT_STATUS, intmask); + + if (intmask & PCI_INT_1394) { + if (linkint & LINK_INT_PHY_TIMEOUT) { + PRINT(KERN_INFO, lynx->id, "PHY timeout occurred"); + } + if (linkint & LINK_INT_PHY_BUSRESET) { + PRINT(KERN_INFO, lynx->id, "bus reset interrupt"); + lynx->selfid_size = -1; + lynx->phy_reg0 = -1; + if (!host->in_bus_reset) + hpsb_bus_reset(host); + } + if (linkint & LINK_INT_PHY_REG_RCVD) { + u32 reg; + + spin_lock(&lynx->phy_reg_lock); + reg = reg_read(lynx, LINK_PHY); + spin_unlock(&lynx->phy_reg_lock); + + if (!host->in_bus_reset) { + PRINT(KERN_INFO, lynx->id, + "phy reg received without reset"); + } else if (reg & 0xf00) { + PRINT(KERN_INFO, lynx->id, + "unsolicited phy reg %d received", + (reg >> 8) & 0xf); + } else { + lynx->phy_reg0 = reg & 0xff; + handle_selfid(lynx, host); + } + } + if (linkint & LINK_INT_ISO_STUCK) { + PRINT(KERN_INFO, lynx->id, "isochronous transmitter stuck"); + } + if (linkint & LINK_INT_ASYNC_STUCK) { + PRINT(KERN_INFO, lynx->id, "asynchronous transmitter stuck"); + } + if (linkint & LINK_INT_SENT_REJECT) { + PRINT(KERN_INFO, lynx->id, "sent reject"); + } + if (linkint & LINK_INT_TX_INVALID_TC) { + PRINT(KERN_INFO, lynx->id, "invalid transaction code"); + } + if (linkint & LINK_INT_GRF_OVERFLOW) { + /* flush FIFO if overflow happens during reset */ + if (host->in_bus_reset) + reg_write(lynx, FIFO_CONTROL, + FIFO_CONTROL_GRF_FLUSH); + PRINT(KERN_INFO, lynx->id, "GRF overflow"); + } + if (linkint & LINK_INT_ITF_UNDERFLOW) { + PRINT(KERN_INFO, lynx->id, "ITF underflow"); + } + if (linkint & LINK_INT_ATF_UNDERFLOW) { + PRINT(KERN_INFO, lynx->id, "ATF underflow"); + } + } + + if (intmask & PCI_INT_DMA_HLT(CHANNEL_ISO_RCV)) { + PRINTD(KERN_DEBUG, lynx->id, "iso receive"); + + spin_lock(&lynx->iso_rcv.lock); + + lynx->iso_rcv.stat[lynx->iso_rcv.next] = + reg_read(lynx, DMA_CHAN_STAT(CHANNEL_ISO_RCV)); + + lynx->iso_rcv.used++; + lynx->iso_rcv.next = (lynx->iso_rcv.next + 1) % NUM_ISORCV_PCL; + + if ((lynx->iso_rcv.next == lynx->iso_rcv.last) + || !lynx->iso_rcv.chan_count) { + PRINTD(KERN_DEBUG, lynx->id, "stopped"); + reg_write(lynx, DMA_WORD1_CMP_ENABLE(CHANNEL_ISO_RCV), 0); + } + + run_sub_pcl(lynx, lynx->iso_rcv.pcl_start, lynx->iso_rcv.next, + CHANNEL_ISO_RCV); + + spin_unlock(&lynx->iso_rcv.lock); + + tasklet_schedule(&lynx->iso_rcv.tq); + } + + if (intmask & PCI_INT_DMA_HLT(CHANNEL_ASYNC_SEND)) { + PRINTD(KERN_DEBUG, lynx->id, "async sent"); + spin_lock(&lynx->async.queue_lock); + + if (list_empty(&lynx->async.pcl_queue)) { + spin_unlock(&lynx->async.queue_lock); + PRINT(KERN_WARNING, lynx->id, "async dma halted, but no queued packet (maybe it was cancelled)"); + } else { + struct ti_pcl pcl; + u32 ack; + struct hpsb_packet *packet; + + get_pcl(lynx, lynx->async.pcl, &pcl); + + packet = driver_packet(lynx->async.pcl_queue.next); + list_del_init(&packet->driver_list); + + pci_unmap_single(lynx->dev, lynx->async.header_dma, + packet->header_size, PCI_DMA_TODEVICE); + if (packet->data_size) { + pci_unmap_single(lynx->dev, lynx->async.data_dma, + packet->data_size, PCI_DMA_TODEVICE); + } + + if (!list_empty(&lynx->async.queue)) { + send_next(lynx, hpsb_async); + } + + spin_unlock(&lynx->async.queue_lock); + + if (pcl.pcl_status & DMA_CHAN_STAT_PKTCMPL) { + if (pcl.pcl_status & DMA_CHAN_STAT_SPECIALACK) { + ack = (pcl.pcl_status >> 15) & 0xf; + PRINTD(KERN_INFO, lynx->id, "special ack %d", ack); + ack = (ack == 1 ? ACKX_TIMEOUT : ACKX_SEND_ERROR); + } else { + ack = (pcl.pcl_status >> 15) & 0xf; + } + } else { + PRINT(KERN_INFO, lynx->id, "async packet was not completed"); + ack = ACKX_SEND_ERROR; + } + hpsb_packet_sent(host, packet, ack); + } + } + + if (intmask & PCI_INT_DMA_HLT(CHANNEL_ISO_SEND)) { + PRINTD(KERN_DEBUG, lynx->id, "iso sent"); + spin_lock(&lynx->iso_send.queue_lock); + + if (list_empty(&lynx->iso_send.pcl_queue)) { + spin_unlock(&lynx->iso_send.queue_lock); + PRINT(KERN_ERR, lynx->id, "iso send dma halted, but no queued packet"); + } else { + struct ti_pcl pcl; + u32 ack; + struct hpsb_packet *packet; + + get_pcl(lynx, lynx->iso_send.pcl, &pcl); + + packet = driver_packet(lynx->iso_send.pcl_queue.next); + list_del_init(&packet->driver_list); + + pci_unmap_single(lynx->dev, lynx->iso_send.header_dma, + packet->header_size, PCI_DMA_TODEVICE); + if (packet->data_size) { + pci_unmap_single(lynx->dev, lynx->iso_send.data_dma, + packet->data_size, PCI_DMA_TODEVICE); + } +#if 0 /* has been removed from ieee1394 core */ + if (!list_empty(&lynx->iso_send.queue)) { + send_next(lynx, hpsb_iso); + } +#endif + spin_unlock(&lynx->iso_send.queue_lock); + + if (pcl.pcl_status & DMA_CHAN_STAT_PKTCMPL) { + if (pcl.pcl_status & DMA_CHAN_STAT_SPECIALACK) { + ack = (pcl.pcl_status >> 15) & 0xf; + PRINTD(KERN_INFO, lynx->id, "special ack %d", ack); + ack = (ack == 1 ? ACKX_TIMEOUT : ACKX_SEND_ERROR); + } else { + ack = (pcl.pcl_status >> 15) & 0xf; + } + } else { + PRINT(KERN_INFO, lynx->id, "iso send packet was not completed"); + ack = ACKX_SEND_ERROR; + } + + hpsb_packet_sent(host, packet, ack); //FIXME: maybe we should just use ACK_COMPLETE and ACKX_SEND_ERROR + } + } + + if (intmask & PCI_INT_DMA_HLT(CHANNEL_ASYNC_RCV)) { + /* general receive DMA completed */ + int stat = reg_read(lynx, DMA_CHAN_STAT(CHANNEL_ASYNC_RCV)); + + PRINTD(KERN_DEBUG, lynx->id, "received packet size %d", + stat & 0x1fff); + + if (stat & DMA_CHAN_STAT_SELFID) { + lynx->selfid_size = stat & 0x1fff; + handle_selfid(lynx, host); + } else { + quadlet_t *q_data = lynx->rcv_page; + if ((*q_data >> 4 & 0xf) == TCODE_READQ_RESPONSE + || (*q_data >> 4 & 0xf) == TCODE_WRITEQ) { + cpu_to_be32s(q_data + 3); + } + hpsb_packet_received(host, q_data, stat & 0x1fff, 0); + } + + run_pcl(lynx, lynx->rcv_pcl_start, CHANNEL_ASYNC_RCV); + } + + return IRQ_HANDLED; +} + + +static void iso_rcv_bh(struct ti_lynx *lynx) +{ + unsigned int idx; + quadlet_t *data; + unsigned long flags; + + spin_lock_irqsave(&lynx->iso_rcv.lock, flags); + + while (lynx->iso_rcv.used) { + idx = lynx->iso_rcv.last; + spin_unlock_irqrestore(&lynx->iso_rcv.lock, flags); + + data = lynx->iso_rcv.page[idx / ISORCV_PER_PAGE] + + (idx % ISORCV_PER_PAGE) * MAX_ISORCV_SIZE; + + if ((*data >> 16) + 4 != (lynx->iso_rcv.stat[idx] & 0x1fff)) { + PRINT(KERN_ERR, lynx->id, + "iso length mismatch 0x%08x/0x%08x", *data, + lynx->iso_rcv.stat[idx]); + } + + if (lynx->iso_rcv.stat[idx] + & (DMA_CHAN_STAT_PCIERR | DMA_CHAN_STAT_PKTERR)) { + PRINT(KERN_INFO, lynx->id, + "iso receive error on %d to 0x%p", idx, data); + } else { + hpsb_packet_received(lynx->host, data, + lynx->iso_rcv.stat[idx] & 0x1fff, + 0); + } + + spin_lock_irqsave(&lynx->iso_rcv.lock, flags); + lynx->iso_rcv.last = (idx + 1) % NUM_ISORCV_PCL; + lynx->iso_rcv.used--; + } + + if (lynx->iso_rcv.chan_count) { + reg_write(lynx, DMA_WORD1_CMP_ENABLE(CHANNEL_ISO_RCV), + DMA_WORD1_CMP_ENABLE_MASTER); + } + spin_unlock_irqrestore(&lynx->iso_rcv.lock, flags); +} + + +static void remove_card(struct pci_dev *dev) +{ + struct ti_lynx *lynx; + struct device *lynx_dev; + int i; + + lynx = pci_get_drvdata(dev); + if (!lynx) return; + pci_set_drvdata(dev, NULL); + + lynx_dev = get_device(&lynx->host->device); + + switch (lynx->state) { + case is_host: + reg_write(lynx, PCI_INT_ENABLE, 0); + hpsb_remove_host(lynx->host); + case have_intr: + reg_write(lynx, PCI_INT_ENABLE, 0); + free_irq(lynx->dev->irq, lynx); + + /* Disable IRM Contender and LCtrl */ + if (lynx->phyic.reg_1394a) + set_phy_reg(lynx, 4, ~0xc0 & get_phy_reg(lynx, 4)); + + /* Let all other nodes know to ignore us */ + lynx_devctl(lynx->host, RESET_BUS, LONG_RESET_NO_FORCE_ROOT); + + case have_iomappings: + reg_set_bits(lynx, MISC_CONTROL, MISC_CONTROL_SWRESET); + /* Fix buggy cards with autoboot pin not tied low: */ + reg_write(lynx, DMA0_CHAN_CTRL, 0); + iounmap(lynx->registers); + iounmap(lynx->local_rom); + iounmap(lynx->local_ram); + iounmap(lynx->aux_port); + case have_1394_buffers: + for (i = 0; i < ISORCV_PAGES; i++) { + if (lynx->iso_rcv.page[i]) { + pci_free_consistent(lynx->dev, PAGE_SIZE, + lynx->iso_rcv.page[i], + lynx->iso_rcv.page_dma[i]); + } + } + pci_free_consistent(lynx->dev, PAGE_SIZE, lynx->rcv_page, + lynx->rcv_page_dma); + case have_aux_buf: + case have_pcl_mem: + pci_free_consistent(lynx->dev, LOCALRAM_SIZE, lynx->pcl_mem, + lynx->pcl_mem_dma); + case clear: + /* do nothing - already freed */ + ; + } + + tasklet_kill(&lynx->iso_rcv.tq); + + if (lynx_dev) + put_device(lynx_dev); +} + + +static int __devinit add_card(struct pci_dev *dev, + const struct pci_device_id *devid_is_unused) +{ +#define FAIL(fmt, args...) do { \ + PRINT_G(KERN_ERR, fmt , ## args); \ + remove_card(dev); \ + return error; \ + } while (0) + + char irq_buf[16]; + struct hpsb_host *host; + struct ti_lynx *lynx; /* shortcut to currently handled device */ + struct ti_pcl pcl; + u32 *pcli; + int i; + int error; + + error = -ENXIO; + + if (pci_set_dma_mask(dev, DMA_32BIT_MASK)) + FAIL("DMA address limits not supported for PCILynx hardware"); + if (pci_enable_device(dev)) + FAIL("failed to enable PCILynx hardware"); + pci_set_master(dev); + + error = -ENOMEM; + + host = hpsb_alloc_host(&lynx_driver, sizeof(struct ti_lynx), &dev->dev); + if (!host) FAIL("failed to allocate control structure memory"); + + lynx = host->hostdata; + lynx->id = card_id++; + lynx->dev = dev; + lynx->state = clear; + lynx->host = host; + host->pdev = dev; + pci_set_drvdata(dev, lynx); + + spin_lock_init(&lynx->lock); + spin_lock_init(&lynx->phy_reg_lock); + + lynx->pcl_mem = pci_alloc_consistent(dev, LOCALRAM_SIZE, + &lynx->pcl_mem_dma); + + if (lynx->pcl_mem != NULL) { + lynx->state = have_pcl_mem; + PRINT(KERN_INFO, lynx->id, + "allocated PCL memory %d Bytes @ 0x%p", LOCALRAM_SIZE, + lynx->pcl_mem); + } else { + FAIL("failed to allocate PCL memory area"); + } + + lynx->rcv_page = pci_alloc_consistent(dev, PAGE_SIZE, + &lynx->rcv_page_dma); + if (lynx->rcv_page == NULL) { + FAIL("failed to allocate receive buffer"); + } + lynx->state = have_1394_buffers; + + for (i = 0; i < ISORCV_PAGES; i++) { + lynx->iso_rcv.page[i] = + pci_alloc_consistent(dev, PAGE_SIZE, + &lynx->iso_rcv.page_dma[i]); + if (lynx->iso_rcv.page[i] == NULL) { + FAIL("failed to allocate iso receive buffers"); + } + } + + lynx->registers = ioremap_nocache(pci_resource_start(dev,0), + PCILYNX_MAX_REGISTER); + lynx->local_ram = ioremap(pci_resource_start(dev,1), PCILYNX_MAX_MEMORY); + lynx->aux_port = ioremap(pci_resource_start(dev,2), PCILYNX_MAX_MEMORY); + lynx->local_rom = ioremap(pci_resource_start(dev,PCI_ROM_RESOURCE), + PCILYNX_MAX_MEMORY); + lynx->state = have_iomappings; + + if (lynx->registers == NULL) { + FAIL("failed to remap registers - card not accessible"); + } + + reg_set_bits(lynx, MISC_CONTROL, MISC_CONTROL_SWRESET); + /* Fix buggy cards with autoboot pin not tied low: */ + reg_write(lynx, DMA0_CHAN_CTRL, 0); + + sprintf (irq_buf, "%d", dev->irq); + + if (!request_irq(dev->irq, lynx_irq_handler, IRQF_SHARED, + PCILYNX_DRIVER_NAME, lynx)) { + PRINT(KERN_INFO, lynx->id, "allocated interrupt %s", irq_buf); + lynx->state = have_intr; + } else { + FAIL("failed to allocate shared interrupt %s", irq_buf); + } + + /* alloc_pcl return values are not checked, it is expected that the + * provided PCL space is sufficient for the initial allocations */ + lynx->rcv_pcl = alloc_pcl(lynx); + lynx->rcv_pcl_start = alloc_pcl(lynx); + lynx->async.pcl = alloc_pcl(lynx); + lynx->async.pcl_start = alloc_pcl(lynx); + lynx->iso_send.pcl = alloc_pcl(lynx); + lynx->iso_send.pcl_start = alloc_pcl(lynx); + + for (i = 0; i < NUM_ISORCV_PCL; i++) { + lynx->iso_rcv.pcl[i] = alloc_pcl(lynx); + } + lynx->iso_rcv.pcl_start = alloc_pcl(lynx); + + /* all allocations successful - simple init stuff follows */ + + reg_write(lynx, PCI_INT_ENABLE, PCI_INT_DMA_ALL); + + tasklet_init(&lynx->iso_rcv.tq, (void (*)(unsigned long))iso_rcv_bh, + (unsigned long)lynx); + + spin_lock_init(&lynx->iso_rcv.lock); + + spin_lock_init(&lynx->async.queue_lock); + lynx->async.channel = CHANNEL_ASYNC_SEND; + spin_lock_init(&lynx->iso_send.queue_lock); + lynx->iso_send.channel = CHANNEL_ISO_SEND; + + PRINT(KERN_INFO, lynx->id, "remapped memory spaces reg 0x%p, rom 0x%p, " + "ram 0x%p, aux 0x%p", lynx->registers, lynx->local_rom, + lynx->local_ram, lynx->aux_port); + + /* now, looking for PHY register set */ + if ((get_phy_reg(lynx, 2) & 0xe0) == 0xe0) { + lynx->phyic.reg_1394a = 1; + PRINT(KERN_INFO, lynx->id, + "found 1394a conform PHY (using extended register set)"); + lynx->phyic.vendor = get_phy_vendorid(lynx); + lynx->phyic.product = get_phy_productid(lynx); + } else { + lynx->phyic.reg_1394a = 0; + PRINT(KERN_INFO, lynx->id, "found old 1394 PHY"); + } + + lynx->selfid_size = -1; + lynx->phy_reg0 = -1; + + INIT_LIST_HEAD(&lynx->async.queue); + INIT_LIST_HEAD(&lynx->async.pcl_queue); + INIT_LIST_HEAD(&lynx->iso_send.queue); + INIT_LIST_HEAD(&lynx->iso_send.pcl_queue); + + pcl.next = pcl_bus(lynx, lynx->rcv_pcl); + put_pcl(lynx, lynx->rcv_pcl_start, &pcl); + + pcl.next = PCL_NEXT_INVALID; + pcl.async_error_next = PCL_NEXT_INVALID; + + pcl.buffer[0].control = PCL_CMD_RCV | 16; +#ifndef __BIG_ENDIAN + pcl.buffer[0].control |= PCL_BIGENDIAN; +#endif + pcl.buffer[1].control = PCL_LAST_BUFF | 4080; + + pcl.buffer[0].pointer = lynx->rcv_page_dma; + pcl.buffer[1].pointer = lynx->rcv_page_dma + 16; + put_pcl(lynx, lynx->rcv_pcl, &pcl); + + pcl.next = pcl_bus(lynx, lynx->async.pcl); + pcl.async_error_next = pcl_bus(lynx, lynx->async.pcl); + put_pcl(lynx, lynx->async.pcl_start, &pcl); + + pcl.next = pcl_bus(lynx, lynx->iso_send.pcl); + pcl.async_error_next = PCL_NEXT_INVALID; + put_pcl(lynx, lynx->iso_send.pcl_start, &pcl); + + pcl.next = PCL_NEXT_INVALID; + pcl.async_error_next = PCL_NEXT_INVALID; + pcl.buffer[0].control = PCL_CMD_RCV | 4; +#ifndef __BIG_ENDIAN + pcl.buffer[0].control |= PCL_BIGENDIAN; +#endif + pcl.buffer[1].control = PCL_LAST_BUFF | 2044; + + for (i = 0; i < NUM_ISORCV_PCL; i++) { + int page = i / ISORCV_PER_PAGE; + int sec = i % ISORCV_PER_PAGE; + + pcl.buffer[0].pointer = lynx->iso_rcv.page_dma[page] + + sec * MAX_ISORCV_SIZE; + pcl.buffer[1].pointer = pcl.buffer[0].pointer + 4; + put_pcl(lynx, lynx->iso_rcv.pcl[i], &pcl); + } + + pcli = (u32 *)&pcl; + for (i = 0; i < NUM_ISORCV_PCL; i++) { + pcli[i] = pcl_bus(lynx, lynx->iso_rcv.pcl[i]); + } + put_pcl(lynx, lynx->iso_rcv.pcl_start, &pcl); + + /* FIFO sizes from left to right: ITF=48 ATF=48 GRF=160 */ + reg_write(lynx, FIFO_SIZES, 0x003030a0); + /* 20 byte threshold before triggering PCI transfer */ + reg_write(lynx, DMA_GLOBAL_REGISTER, 0x2<<24); + /* threshold on both send FIFOs before transmitting: + FIFO size - cache line size - 1 */ + i = reg_read(lynx, PCI_LATENCY_CACHELINE) & 0xff; + i = 0x30 - i - 1; + reg_write(lynx, FIFO_XMIT_THRESHOLD, (i << 8) | i); + + reg_set_bits(lynx, PCI_INT_ENABLE, PCI_INT_1394); + + reg_write(lynx, LINK_INT_ENABLE, LINK_INT_PHY_TIMEOUT + | LINK_INT_PHY_REG_RCVD | LINK_INT_PHY_BUSRESET + | LINK_INT_ISO_STUCK | LINK_INT_ASYNC_STUCK + | LINK_INT_SENT_REJECT | LINK_INT_TX_INVALID_TC + | LINK_INT_GRF_OVERFLOW | LINK_INT_ITF_UNDERFLOW + | LINK_INT_ATF_UNDERFLOW); + + reg_write(lynx, DMA_WORD0_CMP_VALUE(CHANNEL_ASYNC_RCV), 0); + reg_write(lynx, DMA_WORD0_CMP_ENABLE(CHANNEL_ASYNC_RCV), 0xa<<4); + reg_write(lynx, DMA_WORD1_CMP_VALUE(CHANNEL_ASYNC_RCV), 0); + reg_write(lynx, DMA_WORD1_CMP_ENABLE(CHANNEL_ASYNC_RCV), + DMA_WORD1_CMP_MATCH_LOCAL_NODE | DMA_WORD1_CMP_MATCH_BROADCAST + | DMA_WORD1_CMP_MATCH_EXACT | DMA_WORD1_CMP_MATCH_BUS_BCAST + | DMA_WORD1_CMP_ENABLE_SELF_ID | DMA_WORD1_CMP_ENABLE_MASTER); + + run_pcl(lynx, lynx->rcv_pcl_start, CHANNEL_ASYNC_RCV); + + reg_write(lynx, DMA_WORD0_CMP_VALUE(CHANNEL_ISO_RCV), 0); + reg_write(lynx, DMA_WORD0_CMP_ENABLE(CHANNEL_ISO_RCV), 0x9<<4); + reg_write(lynx, DMA_WORD1_CMP_VALUE(CHANNEL_ISO_RCV), 0); + reg_write(lynx, DMA_WORD1_CMP_ENABLE(CHANNEL_ISO_RCV), 0); + + run_sub_pcl(lynx, lynx->iso_rcv.pcl_start, 0, CHANNEL_ISO_RCV); + + reg_write(lynx, LINK_CONTROL, LINK_CONTROL_RCV_CMP_VALID + | LINK_CONTROL_TX_ISO_EN | LINK_CONTROL_RX_ISO_EN + | LINK_CONTROL_TX_ASYNC_EN | LINK_CONTROL_RX_ASYNC_EN + | LINK_CONTROL_RESET_TX | LINK_CONTROL_RESET_RX); + + if (!lynx->phyic.reg_1394a) { + if (!hpsb_disable_irm) { + /* attempt to enable contender bit -FIXME- would this + * work elsewhere? */ + reg_set_bits(lynx, GPIO_CTRL_A, 0x1); + reg_write(lynx, GPIO_DATA_BASE + 0x3c, 0x1); + } + } else { + /* set the contender (if appropriate) and LCtrl bit in the + * extended PHY register set. (Should check that PHY_02_EXTENDED + * is set in register 2?) + */ + i = get_phy_reg(lynx, 4); + i |= PHY_04_LCTRL; + if (hpsb_disable_irm) + i &= ~PHY_04_CONTENDER; + else + i |= PHY_04_CONTENDER; + if (i != -1) set_phy_reg(lynx, 4, i); + } + + if (!skip_eeprom) + { + /* needed for i2c communication with serial eeprom */ + struct i2c_adapter *i2c_ad; + struct i2c_algo_bit_data i2c_adapter_data; + + error = -ENOMEM; + i2c_ad = kzalloc(sizeof(*i2c_ad), GFP_KERNEL); + if (!i2c_ad) FAIL("failed to allocate I2C adapter memory"); + + i2c_ad->id = I2C_HW_B_PCILYNX; + strlcpy(i2c_ad->name, "PCILynx I2C", sizeof(i2c_ad->name)); + i2c_adapter_data = bit_data; + i2c_ad->algo_data = &i2c_adapter_data; + i2c_adapter_data.data = lynx; + i2c_ad->dev.parent = &dev->dev; + + PRINTD(KERN_DEBUG, lynx->id,"original eeprom control: %d", + reg_read(lynx, SERIAL_EEPROM_CONTROL)); + + /* reset hardware to sane state */ + lynx->i2c_driven_state = 0x00000070; + reg_write(lynx, SERIAL_EEPROM_CONTROL, lynx->i2c_driven_state); + + if (i2c_bit_add_bus(i2c_ad) < 0) + { + kfree(i2c_ad); + error = -ENXIO; + FAIL("unable to register i2c"); + } + else + { + /* do i2c stuff */ + unsigned char i2c_cmd = 0x10; + struct i2c_msg msg[2] = { { 0x50, 0, 1, &i2c_cmd }, + { 0x50, I2C_M_RD, 20, (unsigned char*) lynx->bus_info_block } + }; + + /* we use i2c_transfer because we have no i2c_client + at hand */ + if (i2c_transfer(i2c_ad, msg, 2) < 0) { + PRINT(KERN_ERR, lynx->id, "unable to read bus info block from i2c"); + } else { + PRINT(KERN_INFO, lynx->id, "got bus info block from serial eeprom"); + /* FIXME: probably we shoud rewrite the max_rec, max_ROM(1394a), + * generation(1394a) and link_spd(1394a) field and recalculate + * the CRC */ + + for (i = 0; i < 5 ; i++) + PRINTD(KERN_DEBUG, lynx->id, "Businfo block quadlet %i: %08x", + i, be32_to_cpu(lynx->bus_info_block[i])); + + /* info_length, crc_length and 1394 magic number to check, if it is really a bus info block */ + if (((be32_to_cpu(lynx->bus_info_block[0]) & 0xffff0000) == 0x04040000) && + (lynx->bus_info_block[1] == __constant_cpu_to_be32(0x31333934))) + { + PRINT(KERN_DEBUG, lynx->id, "read a valid bus info block from"); + } else { + kfree(i2c_ad); + error = -ENXIO; + FAIL("read something from serial eeprom, but it does not seem to be a valid bus info block"); + } + + } + + i2c_del_adapter(i2c_ad); + kfree(i2c_ad); + } + } + + host->csr.guid_hi = be32_to_cpu(lynx->bus_info_block[3]); + host->csr.guid_lo = be32_to_cpu(lynx->bus_info_block[4]); + host->csr.cyc_clk_acc = (be32_to_cpu(lynx->bus_info_block[2]) >> 16) & 0xff; + host->csr.max_rec = (be32_to_cpu(lynx->bus_info_block[2]) >> 12) & 0xf; + if (!lynx->phyic.reg_1394a) + host->csr.lnk_spd = (get_phy_reg(lynx, 2) & 0xc0) >> 6; + else + host->csr.lnk_spd = be32_to_cpu(lynx->bus_info_block[2]) & 0x7; + + if (hpsb_add_host(host)) { + error = -ENOMEM; + FAIL("Failed to register host with highlevel"); + } + + lynx->state = is_host; + + return 0; +#undef FAIL +} + + +static struct pci_device_id pci_table[] = { + { + .vendor = PCI_VENDOR_ID_TI, + .device = PCI_DEVICE_ID_TI_PCILYNX, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { } /* Terminating entry */ +}; + +static struct pci_driver lynx_pci_driver = { + .name = PCILYNX_DRIVER_NAME, + .id_table = pci_table, + .probe = add_card, + .remove = remove_card, +}; + +static struct hpsb_host_driver lynx_driver = { + .owner = THIS_MODULE, + .name = PCILYNX_DRIVER_NAME, + .set_hw_config_rom = NULL, + .transmit_packet = lynx_transmit, + .devctl = lynx_devctl, + .isoctl = NULL, +}; + +MODULE_AUTHOR("Andreas E. Bombe <andreas.bombe@munich.netsurf.de>"); +MODULE_DESCRIPTION("driver for Texas Instruments PCI Lynx IEEE-1394 controller"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("pcilynx"); +MODULE_DEVICE_TABLE(pci, pci_table); + +static int __init pcilynx_init(void) +{ + int ret; + + ret = pci_register_driver(&lynx_pci_driver); + if (ret < 0) { + PRINT_G(KERN_ERR, "PCI module init failed"); + return ret; + } + + return 0; +} + +static void __exit pcilynx_cleanup(void) +{ + pci_unregister_driver(&lynx_pci_driver); +} + + +module_init(pcilynx_init); +module_exit(pcilynx_cleanup); diff --git a/drivers/ieee1394/pcilynx.h b/drivers/ieee1394/pcilynx.h new file mode 100644 index 0000000..ec27321 --- /dev/null +++ b/drivers/ieee1394/pcilynx.h @@ -0,0 +1,468 @@ +#ifndef __PCILYNX_H__ +#define __PCILYNX_H__ + + +#define PCILYNX_DRIVER_NAME "pcilynx" +#define PCILYNX_MAJOR 177 + +#define PCILYNX_MINOR_AUX_START 0 +#define PCILYNX_MINOR_ROM_START 16 +#define PCILYNX_MINOR_RAM_START 32 + +#define PCILYNX_MAX_REGISTER 0xfff +#define PCILYNX_MAX_MEMORY 0xffff + +#define PCI_DEVICE_ID_TI_PCILYNX 0x8000 +#define MAX_PCILYNX_CARDS 4 +#define LOCALRAM_SIZE 4096 + +#define NUM_ISORCV_PCL 4 +#define MAX_ISORCV_SIZE 2048 +#define ISORCV_PER_PAGE (PAGE_SIZE / MAX_ISORCV_SIZE) +#define ISORCV_PAGES (NUM_ISORCV_PCL / ISORCV_PER_PAGE) + +#define CHANNEL_LOCALBUS 0 +#define CHANNEL_ASYNC_RCV 1 +#define CHANNEL_ISO_RCV 2 +#define CHANNEL_ASYNC_SEND 3 +#define CHANNEL_ISO_SEND 4 + +#define PCILYNX_CONFIG_ROM_LENGTH 1024 + +typedef int pcl_t; + +struct ti_lynx { + int id; /* sequential card number */ + + spinlock_t lock; + + struct pci_dev *dev; + + struct { + unsigned reg_1394a:1; + u32 vendor; + u32 product; + } phyic; + + enum { clear, have_intr, have_aux_buf, have_pcl_mem, + have_1394_buffers, have_iomappings, is_host } state; + + /* remapped memory spaces */ + void __iomem *registers; + void __iomem *local_rom; + void __iomem *local_ram; + void __iomem *aux_port; + quadlet_t bus_info_block[5]; + + /* + * use local RAM of LOCALRAM_SIZE bytes for PCLs, which allows for + * LOCALRAM_SIZE * 8 PCLs (each sized 128 bytes); + * the following is an allocation bitmap + */ + u8 pcl_bmap[LOCALRAM_SIZE / 1024]; + + /* point to PCLs memory area if needed */ + void *pcl_mem; + dma_addr_t pcl_mem_dma; + + /* PCLs for local mem / aux transfers */ + pcl_t dmem_pcl; + + /* IEEE-1394 part follows */ + struct hpsb_host *host; + + int phyid, isroot; + int selfid_size; + int phy_reg0; + + spinlock_t phy_reg_lock; + + pcl_t rcv_pcl_start, rcv_pcl; + void *rcv_page; + dma_addr_t rcv_page_dma; + int rcv_active; + + struct lynx_send_data { + pcl_t pcl_start, pcl; + struct list_head queue; + struct list_head pcl_queue; /* this queue contains at most one packet */ + spinlock_t queue_lock; + dma_addr_t header_dma, data_dma; + int channel; + } async, iso_send; + + struct { + pcl_t pcl[NUM_ISORCV_PCL]; + u32 stat[NUM_ISORCV_PCL]; + void *page[ISORCV_PAGES]; + dma_addr_t page_dma[ISORCV_PAGES]; + pcl_t pcl_start; + int chan_count; + int next, last, used, running; + struct tasklet_struct tq; + spinlock_t lock; + } iso_rcv; + + u32 i2c_driven_state; /* the state we currently drive the Serial EEPROM Control register */ +}; + +/* the per-file data structure for mem space access */ +struct memdata { + struct ti_lynx *lynx; + int cid; + atomic_t aux_intr_last_seen; + /* enum values are the same as LBUS_ADDR_SEL_* values below */ + enum { rom = 0x10000, aux = 0x20000, ram = 0 } type; +}; + + + +/* + * Register read and write helper functions. + */ +static inline void reg_write(const struct ti_lynx *lynx, int offset, u32 data) +{ + writel(data, lynx->registers + offset); +} + +static inline u32 reg_read(const struct ti_lynx *lynx, int offset) +{ + return readl(lynx->registers + offset); +} + +static inline void reg_set_bits(const struct ti_lynx *lynx, int offset, + u32 mask) +{ + reg_write(lynx, offset, (reg_read(lynx, offset) | mask)); +} + +static inline void reg_clear_bits(const struct ti_lynx *lynx, int offset, + u32 mask) +{ + reg_write(lynx, offset, (reg_read(lynx, offset) & ~mask)); +} + + + +/* chip register definitions follow */ + +#define PCI_LATENCY_CACHELINE 0x0c + +#define MISC_CONTROL 0x40 +#define MISC_CONTROL_SWRESET (1<<0) + +#define SERIAL_EEPROM_CONTROL 0x44 + +#define PCI_INT_STATUS 0x48 +#define PCI_INT_ENABLE 0x4c +/* status and enable have identical bit numbers */ +#define PCI_INT_INT_PEND (1<<31) +#define PCI_INT_FORCED_INT (1<<30) +#define PCI_INT_SLV_ADR_PERR (1<<28) +#define PCI_INT_SLV_DAT_PERR (1<<27) +#define PCI_INT_MST_DAT_PERR (1<<26) +#define PCI_INT_MST_DEV_TIMEOUT (1<<25) +#define PCI_INT_INTERNAL_SLV_TIMEOUT (1<<23) +#define PCI_INT_AUX_TIMEOUT (1<<18) +#define PCI_INT_AUX_INT (1<<17) +#define PCI_INT_1394 (1<<16) +#define PCI_INT_DMA4_PCL (1<<9) +#define PCI_INT_DMA4_HLT (1<<8) +#define PCI_INT_DMA3_PCL (1<<7) +#define PCI_INT_DMA3_HLT (1<<6) +#define PCI_INT_DMA2_PCL (1<<5) +#define PCI_INT_DMA2_HLT (1<<4) +#define PCI_INT_DMA1_PCL (1<<3) +#define PCI_INT_DMA1_HLT (1<<2) +#define PCI_INT_DMA0_PCL (1<<1) +#define PCI_INT_DMA0_HLT (1<<0) +/* all DMA interrupts combined: */ +#define PCI_INT_DMA_ALL 0x3ff + +#define PCI_INT_DMA_HLT(chan) (1 << (chan * 2)) +#define PCI_INT_DMA_PCL(chan) (1 << (chan * 2 + 1)) + +#define LBUS_ADDR 0xb4 +#define LBUS_ADDR_SEL_RAM (0x0<<16) +#define LBUS_ADDR_SEL_ROM (0x1<<16) +#define LBUS_ADDR_SEL_AUX (0x2<<16) +#define LBUS_ADDR_SEL_ZV (0x3<<16) + +#define GPIO_CTRL_A 0xb8 +#define GPIO_CTRL_B 0xbc +#define GPIO_DATA_BASE 0xc0 + +#define DMA_BREG(base, chan) (base + chan * 0x20) +#define DMA_SREG(base, chan) (base + chan * 0x10) + +#define DMA0_PREV_PCL 0x100 +#define DMA1_PREV_PCL 0x120 +#define DMA2_PREV_PCL 0x140 +#define DMA3_PREV_PCL 0x160 +#define DMA4_PREV_PCL 0x180 +#define DMA_PREV_PCL(chan) (DMA_BREG(DMA0_PREV_PCL, chan)) + +#define DMA0_CURRENT_PCL 0x104 +#define DMA1_CURRENT_PCL 0x124 +#define DMA2_CURRENT_PCL 0x144 +#define DMA3_CURRENT_PCL 0x164 +#define DMA4_CURRENT_PCL 0x184 +#define DMA_CURRENT_PCL(chan) (DMA_BREG(DMA0_CURRENT_PCL, chan)) + +#define DMA0_CHAN_STAT 0x10c +#define DMA1_CHAN_STAT 0x12c +#define DMA2_CHAN_STAT 0x14c +#define DMA3_CHAN_STAT 0x16c +#define DMA4_CHAN_STAT 0x18c +#define DMA_CHAN_STAT(chan) (DMA_BREG(DMA0_CHAN_STAT, chan)) +/* CHAN_STATUS registers share bits */ +#define DMA_CHAN_STAT_SELFID (1<<31) +#define DMA_CHAN_STAT_ISOPKT (1<<30) +#define DMA_CHAN_STAT_PCIERR (1<<29) +#define DMA_CHAN_STAT_PKTERR (1<<28) +#define DMA_CHAN_STAT_PKTCMPL (1<<27) +#define DMA_CHAN_STAT_SPECIALACK (1<<14) + + +#define DMA0_CHAN_CTRL 0x110 +#define DMA1_CHAN_CTRL 0x130 +#define DMA2_CHAN_CTRL 0x150 +#define DMA3_CHAN_CTRL 0x170 +#define DMA4_CHAN_CTRL 0x190 +#define DMA_CHAN_CTRL(chan) (DMA_BREG(DMA0_CHAN_CTRL, chan)) +/* CHAN_CTRL registers share bits */ +#define DMA_CHAN_CTRL_ENABLE (1<<31) +#define DMA_CHAN_CTRL_BUSY (1<<30) +#define DMA_CHAN_CTRL_LINK (1<<29) + +#define DMA0_READY 0x114 +#define DMA1_READY 0x134 +#define DMA2_READY 0x154 +#define DMA3_READY 0x174 +#define DMA4_READY 0x194 +#define DMA_READY(chan) (DMA_BREG(DMA0_READY, chan)) + +#define DMA_GLOBAL_REGISTER 0x908 + +#define FIFO_SIZES 0xa00 + +#define FIFO_CONTROL 0xa10 +#define FIFO_CONTROL_GRF_FLUSH (1<<4) +#define FIFO_CONTROL_ITF_FLUSH (1<<3) +#define FIFO_CONTROL_ATF_FLUSH (1<<2) + +#define FIFO_XMIT_THRESHOLD 0xa14 + +#define DMA0_WORD0_CMP_VALUE 0xb00 +#define DMA1_WORD0_CMP_VALUE 0xb10 +#define DMA2_WORD0_CMP_VALUE 0xb20 +#define DMA3_WORD0_CMP_VALUE 0xb30 +#define DMA4_WORD0_CMP_VALUE 0xb40 +#define DMA_WORD0_CMP_VALUE(chan) (DMA_SREG(DMA0_WORD0_CMP_VALUE, chan)) + +#define DMA0_WORD0_CMP_ENABLE 0xb04 +#define DMA1_WORD0_CMP_ENABLE 0xb14 +#define DMA2_WORD0_CMP_ENABLE 0xb24 +#define DMA3_WORD0_CMP_ENABLE 0xb34 +#define DMA4_WORD0_CMP_ENABLE 0xb44 +#define DMA_WORD0_CMP_ENABLE(chan) (DMA_SREG(DMA0_WORD0_CMP_ENABLE,chan)) + +#define DMA0_WORD1_CMP_VALUE 0xb08 +#define DMA1_WORD1_CMP_VALUE 0xb18 +#define DMA2_WORD1_CMP_VALUE 0xb28 +#define DMA3_WORD1_CMP_VALUE 0xb38 +#define DMA4_WORD1_CMP_VALUE 0xb48 +#define DMA_WORD1_CMP_VALUE(chan) (DMA_SREG(DMA0_WORD1_CMP_VALUE, chan)) + +#define DMA0_WORD1_CMP_ENABLE 0xb0c +#define DMA1_WORD1_CMP_ENABLE 0xb1c +#define DMA2_WORD1_CMP_ENABLE 0xb2c +#define DMA3_WORD1_CMP_ENABLE 0xb3c +#define DMA4_WORD1_CMP_ENABLE 0xb4c +#define DMA_WORD1_CMP_ENABLE(chan) (DMA_SREG(DMA0_WORD1_CMP_ENABLE,chan)) +/* word 1 compare enable flags */ +#define DMA_WORD1_CMP_MATCH_OTHERBUS (1<<15) +#define DMA_WORD1_CMP_MATCH_BROADCAST (1<<14) +#define DMA_WORD1_CMP_MATCH_BUS_BCAST (1<<13) +#define DMA_WORD1_CMP_MATCH_LOCAL_NODE (1<<12) +#define DMA_WORD1_CMP_MATCH_EXACT (1<<11) +#define DMA_WORD1_CMP_ENABLE_SELF_ID (1<<10) +#define DMA_WORD1_CMP_ENABLE_MASTER (1<<8) + +#define LINK_ID 0xf00 +#define LINK_ID_BUS(id) (id<<22) +#define LINK_ID_NODE(id) (id<<16) + +#define LINK_CONTROL 0xf04 +#define LINK_CONTROL_BUSY (1<<29) +#define LINK_CONTROL_TX_ISO_EN (1<<26) +#define LINK_CONTROL_RX_ISO_EN (1<<25) +#define LINK_CONTROL_TX_ASYNC_EN (1<<24) +#define LINK_CONTROL_RX_ASYNC_EN (1<<23) +#define LINK_CONTROL_RESET_TX (1<<21) +#define LINK_CONTROL_RESET_RX (1<<20) +#define LINK_CONTROL_CYCMASTER (1<<11) +#define LINK_CONTROL_CYCSOURCE (1<<10) +#define LINK_CONTROL_CYCTIMEREN (1<<9) +#define LINK_CONTROL_RCV_CMP_VALID (1<<7) +#define LINK_CONTROL_SNOOP_ENABLE (1<<6) + +#define CYCLE_TIMER 0xf08 + +#define LINK_PHY 0xf0c +#define LINK_PHY_READ (1<<31) +#define LINK_PHY_WRITE (1<<30) +#define LINK_PHY_ADDR(addr) (addr<<24) +#define LINK_PHY_WDATA(data) (data<<16) +#define LINK_PHY_RADDR(addr) (addr<<8) + + +#define LINK_INT_STATUS 0xf14 +#define LINK_INT_ENABLE 0xf18 +/* status and enable have identical bit numbers */ +#define LINK_INT_LINK_INT (1<<31) +#define LINK_INT_PHY_TIMEOUT (1<<30) +#define LINK_INT_PHY_REG_RCVD (1<<29) +#define LINK_INT_PHY_BUSRESET (1<<28) +#define LINK_INT_TX_RDY (1<<26) +#define LINK_INT_RX_DATA_RDY (1<<25) +#define LINK_INT_ISO_STUCK (1<<20) +#define LINK_INT_ASYNC_STUCK (1<<19) +#define LINK_INT_SENT_REJECT (1<<17) +#define LINK_INT_HDR_ERR (1<<16) +#define LINK_INT_TX_INVALID_TC (1<<15) +#define LINK_INT_CYC_SECOND (1<<11) +#define LINK_INT_CYC_START (1<<10) +#define LINK_INT_CYC_DONE (1<<9) +#define LINK_INT_CYC_PENDING (1<<8) +#define LINK_INT_CYC_LOST (1<<7) +#define LINK_INT_CYC_ARB_FAILED (1<<6) +#define LINK_INT_GRF_OVERFLOW (1<<5) +#define LINK_INT_ITF_UNDERFLOW (1<<4) +#define LINK_INT_ATF_UNDERFLOW (1<<3) +#define LINK_INT_ISOARB_FAILED (1<<0) + +/* PHY specifics */ +#define PHY_VENDORID_TI 0x800028 +#define PHY_PRODUCTID_TSB41LV03 0x000000 + + +/* this is the physical layout of a PCL, its size is 128 bytes */ +struct ti_pcl { + u32 next; + u32 async_error_next; + u32 user_data; + u32 pcl_status; + u32 remaining_transfer_count; + u32 next_data_buffer; + struct { + u32 control; + u32 pointer; + } buffer[13] __attribute__ ((packed)); +} __attribute__ ((packed)); + +#include <linux/stddef.h> +#define pcloffs(MEMBER) (offsetof(struct ti_pcl, MEMBER)) + + +static inline void put_pcl(const struct ti_lynx *lynx, pcl_t pclid, + const struct ti_pcl *pcl) +{ + memcpy_le32((u32 *)(lynx->pcl_mem + pclid * sizeof(struct ti_pcl)), + (u32 *)pcl, sizeof(struct ti_pcl)); +} + +static inline void get_pcl(const struct ti_lynx *lynx, pcl_t pclid, + struct ti_pcl *pcl) +{ + memcpy_le32((u32 *)pcl, + (u32 *)(lynx->pcl_mem + pclid * sizeof(struct ti_pcl)), + sizeof(struct ti_pcl)); +} + +static inline u32 pcl_bus(const struct ti_lynx *lynx, pcl_t pclid) +{ + return lynx->pcl_mem_dma + pclid * sizeof(struct ti_pcl); +} + + +#if defined (__BIG_ENDIAN) +typedef struct ti_pcl pcltmp_t; + +static inline struct ti_pcl *edit_pcl(const struct ti_lynx *lynx, pcl_t pclid, + pcltmp_t *tmp) +{ + get_pcl(lynx, pclid, tmp); + return tmp; +} + +static inline void commit_pcl(const struct ti_lynx *lynx, pcl_t pclid, + pcltmp_t *tmp) +{ + put_pcl(lynx, pclid, tmp); +} + +#else +typedef int pcltmp_t; /* just a dummy */ + +static inline struct ti_pcl *edit_pcl(const struct ti_lynx *lynx, pcl_t pclid, + pcltmp_t *tmp) +{ + return lynx->pcl_mem + pclid * sizeof(struct ti_pcl); +} + +static inline void commit_pcl(const struct ti_lynx *lynx, pcl_t pclid, + pcltmp_t *tmp) +{ +} +#endif + + +static inline void run_sub_pcl(const struct ti_lynx *lynx, pcl_t pclid, int idx, + int dmachan) +{ + reg_write(lynx, DMA0_CURRENT_PCL + dmachan * 0x20, + pcl_bus(lynx, pclid) + idx * 4); + reg_write(lynx, DMA0_CHAN_CTRL + dmachan * 0x20, + DMA_CHAN_CTRL_ENABLE | DMA_CHAN_CTRL_LINK); +} + +static inline void run_pcl(const struct ti_lynx *lynx, pcl_t pclid, int dmachan) +{ + run_sub_pcl(lynx, pclid, 0, dmachan); +} + +#define PCL_NEXT_INVALID (1<<0) + +/* transfer commands */ +#define PCL_CMD_RCV (0x1<<24) +#define PCL_CMD_RCV_AND_UPDATE (0xa<<24) +#define PCL_CMD_XMT (0x2<<24) +#define PCL_CMD_UNFXMT (0xc<<24) +#define PCL_CMD_PCI_TO_LBUS (0x8<<24) +#define PCL_CMD_LBUS_TO_PCI (0x9<<24) + +/* aux commands */ +#define PCL_CMD_NOP (0x0<<24) +#define PCL_CMD_LOAD (0x3<<24) +#define PCL_CMD_STOREQ (0x4<<24) +#define PCL_CMD_STORED (0xb<<24) +#define PCL_CMD_STORE0 (0x5<<24) +#define PCL_CMD_STORE1 (0x6<<24) +#define PCL_CMD_COMPARE (0xe<<24) +#define PCL_CMD_SWAP_COMPARE (0xf<<24) +#define PCL_CMD_ADD (0xd<<24) +#define PCL_CMD_BRANCH (0x7<<24) + +/* BRANCH condition codes */ +#define PCL_COND_DMARDY_SET (0x1<<20) +#define PCL_COND_DMARDY_CLEAR (0x2<<20) + +#define PCL_GEN_INTR (1<<19) +#define PCL_LAST_BUFF (1<<18) +#define PCL_LAST_CMD (PCL_LAST_BUFF) +#define PCL_WAITSTAT (1<<17) +#define PCL_BIGENDIAN (1<<16) +#define PCL_ISOMODE (1<<12) + +#endif diff --git a/drivers/ieee1394/raw1394-private.h b/drivers/ieee1394/raw1394-private.h new file mode 100644 index 0000000..7a225a4 --- /dev/null +++ b/drivers/ieee1394/raw1394-private.h @@ -0,0 +1,81 @@ +#ifndef IEEE1394_RAW1394_PRIVATE_H +#define IEEE1394_RAW1394_PRIVATE_H + +/* header for definitions that are private to the raw1394 driver + and not visible to user-space */ + +#define RAW1394_DEVICE_MAJOR 171 +#define RAW1394_DEVICE_NAME "raw1394" + +#define RAW1394_MAX_USER_CSR_DIRS 16 + +struct iso_block_store { + atomic_t refcount; + size_t data_size; + quadlet_t data[0]; +}; + +enum raw1394_iso_state { RAW1394_ISO_INACTIVE = 0, + RAW1394_ISO_RECV = 1, + RAW1394_ISO_XMIT = 2 }; + +struct file_info { + struct list_head list; + + struct mutex state_mutex; + enum { opened, initialized, connected } state; + unsigned int protocol_version; + + struct hpsb_host *host; + + struct list_head req_pending; /* protected by reqlists_lock */ + struct list_head req_complete; /* protected by reqlists_lock */ + spinlock_t reqlists_lock; + wait_queue_head_t wait_complete; + + struct list_head addr_list; /* protected by host_info_lock */ + + u8 __user *fcp_buffer; + + u8 notification; /* (busreset-notification) RAW1394_NOTIFY_OFF/ON */ + + /* new rawiso API */ + enum raw1394_iso_state iso_state; + struct hpsb_iso *iso_handle; + + /* User space's CSR1212 dynamic ConfigROM directories */ + struct csr1212_keyval *csr1212_dirs[RAW1394_MAX_USER_CSR_DIRS]; + + /* Legacy ConfigROM update flag */ + u8 cfgrom_upd; +}; + +struct arm_addr { + struct list_head addr_list; /* file_info list */ + u64 start, end; + u64 arm_tag; + u8 access_rights; + u8 notification_options; + u8 client_transactions; + u64 recvb; + u16 rec_length; + u8 *addr_space_buffer; /* accessed by read/write/lock requests */ +}; + +struct pending_request { + struct list_head list; + struct file_info *file_info; + struct hpsb_packet *packet; + struct iso_block_store *ibs; + quadlet_t *data; + int free_data; + struct raw1394_request req; +}; + +struct host_info { + struct list_head list; + struct hpsb_host *host; + struct list_head file_info_list; /* protected by host_info_lock */ +}; + +#endif /* IEEE1394_RAW1394_PRIVATE_H */ diff --git a/drivers/ieee1394/raw1394.c b/drivers/ieee1394/raw1394.c new file mode 100644 index 0000000..bf7e761 --- /dev/null +++ b/drivers/ieee1394/raw1394.c @@ -0,0 +1,3089 @@ +/* + * 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 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 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 { + fi->fcp_buffer = int2ptr(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_async_request(struct file_info *fi, + struct pending_request *req, int node) +{ + unsigned long flags; + struct hpsb_packet *packet = NULL; + u64 addr = req->req.address & 0xffffffffffffULL; + + switch (req->req.type) { + case RAW1394_REQ_ASYNC_READ: + DBGMSG("read_request called"); + packet = + hpsb_make_readpacket(fi->host, node, addr, req->req.length); + + if (!packet) + return -ENOMEM; + + if (req->req.length == 4) + req->data = &packet->header[3]; + else + req->data = packet->data; + + break; + + case RAW1394_REQ_ASYNC_WRITE: + DBGMSG("write_request called"); + + packet = hpsb_make_writepacket(fi->host, node, addr, NULL, + req->req.length); + if (!packet) + return -ENOMEM; + + if (req->req.length == 4) { + if (copy_from_user + (&packet->header[3], int2ptr(req->req.sendb), + req->req.length)) + req->req.error = RAW1394_ERROR_MEMFAULT; + } else { + if (copy_from_user + (packet->data, int2ptr(req->req.sendb), + req->req.length)) + req->req.error = RAW1394_ERROR_MEMFAULT; + } + + req->req.length = 0; + break; + + case RAW1394_REQ_ASYNC_STREAM: + DBGMSG("stream_request called"); + + packet = + hpsb_make_streampacket(fi->host, NULL, req->req.length, + node & 0x3f /*channel */ , + (req->req.misc >> 16) & 0x3, + req->req.misc & 0xf); + if (!packet) + return -ENOMEM; + + if (copy_from_user(packet->data, int2ptr(req->req.sendb), + req->req.length)) + req->req.error = RAW1394_ERROR_MEMFAULT; + + req->req.length = 0; + break; + + case RAW1394_REQ_LOCK: + DBGMSG("lock_request called"); + 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, NULL, 0); + if (!packet) + return -ENOMEM; + + if (copy_from_user(packet->data, int2ptr(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: + DBGMSG("lock64_request called"); + if ((req->req.misc == EXTCODE_FETCH_ADD) + || (req->req.misc == EXTCODE_LITTLE_ADD)) { + if (req->req.length != 8) { + req->req.error = RAW1394_ERROR_INVALID_ARG; + break; + } + } else { + if (req->req.length != 16) { + req->req.error = RAW1394_ERROR_INVALID_ARG; + break; + } + } + packet = hpsb_make_lock64packet(fi->host, node, addr, + req->req.misc, NULL, 0); + if (!packet) + return -ENOMEM; + + if (copy_from_user(packet->data, int2ptr(req->req.sendb), + req->req.length)) { + req->req.error = RAW1394_ERROR_MEMFAULT; + break; + } + + req->data = packet->data; + req->req.length = 8; + break; + + default: + req->req.error = RAW1394_ERROR_STATE_ORDER; + } + + req->packet = packet; + + if (req->req.error) { + req->req.length = 0; + queue_complete_req(req); + return 0; + } + + hpsb_set_packet_complete_task(packet, + (void (*)(void *))queue_complete_cb, req); + + spin_lock_irqsave(&fi->reqlists_lock, flags); + list_add_tail(&req->list, &fi->req_pending); + spin_unlock_irqrestore(&fi->reqlists_lock, flags); + + packet->generation = req->req.generation; + + if (hpsb_send_packet(packet) < 0) { + req->req.error = RAW1394_ERROR_SEND_ERROR; + req->req.length = 0; + hpsb_free_tlabel(packet); + queue_complete_req(req); + } + return 0; +} + +static int handle_async_send(struct file_info *fi, struct pending_request *req) +{ + unsigned long flags; + struct hpsb_packet *packet; + int header_length = req->req.misc & 0xffff; + int expect_response = req->req.misc >> 16; + size_t data_size; + + if (header_length > req->req.length || header_length < 12 || + header_length > FIELD_SIZEOF(struct hpsb_packet, header)) { + req->req.error = RAW1394_ERROR_INVALID_ARG; + req->req.length = 0; + queue_complete_req(req); + return 0; + } + + data_size = req->req.length - header_length; + packet = hpsb_alloc_packet(data_size); + req->packet = packet; + if (!packet) + return -ENOMEM; + + if (copy_from_user(packet->header, int2ptr(req->req.sendb), + header_length)) { + req->req.error = RAW1394_ERROR_MEMFAULT; + req->req.length = 0; + queue_complete_req(req); + return 0; + } + + if (copy_from_user + (packet->data, int2ptr(req->req.sendb) + header_length, + data_size)) { + req->req.error = RAW1394_ERROR_MEMFAULT; + req->req.length = 0; + queue_complete_req(req); + return 0; + } + + packet->type = hpsb_async; + packet->node_id = packet->header[0] >> 16; + packet->tcode = (packet->header[0] >> 4) & 0xf; + packet->tlabel = (packet->header[0] >> 10) & 0x3f; + packet->host = fi->host; + packet->expect_response = expect_response; + packet->header_size = header_length; + packet->data_size = data_size; + + req->req.length = 0; + hpsb_set_packet_complete_task(packet, + (void (*)(void *))queue_complete_cb, req); + + spin_lock_irqsave(&fi->reqlists_lock, flags); + list_add_tail(&req->list, &fi->req_pending); + spin_unlock_irqrestore(&fi->reqlists_lock, flags); + + /* Update the generation of the packet just before sending. */ + packet->generation = req->req.generation; + + if (hpsb_send_packet(packet) < 0) { + req->req.error = RAW1394_ERROR_SEND_ERROR; + queue_complete_req(req); + } + + return 0; +} + +static int arm_read(struct hpsb_host *host, int nodeid, quadlet_t * buffer, + u64 addr, size_t length, u16 flags) +{ + unsigned long irqflags; + struct pending_request *req; + struct host_info *hi; + struct file_info *fi = NULL; + struct list_head *entry; + struct arm_addr *arm_addr = NULL; + struct arm_request *arm_req = NULL; + struct arm_response *arm_resp = NULL; + int found = 0, size = 0, rcode = -1; + struct arm_request_response *arm_req_resp = NULL; + + DBGMSG("arm_read called by node: %X " + "addr: %4.4x %8.8x length: %Zu", nodeid, + (u16) ((addr >> 32) & 0xFFFF), (u32) (addr & 0xFFFFFFFF), + length); + spin_lock_irqsave(&host_info_lock, irqflags); + hi = find_host_info(host); /* search address-entry */ + if (hi != NULL) { + list_for_each_entry(fi, &hi->file_info_list, list) { + entry = fi->addr_list.next; + while (entry != &(fi->addr_list)) { + arm_addr = + list_entry(entry, struct arm_addr, + addr_list); + if (((arm_addr->start) <= (addr)) + && ((arm_addr->end) >= (addr + length))) { + found = 1; + break; + } + entry = entry->next; + } + if (found) { + break; + } + } + } + rcode = -1; + if (!found) { + printk(KERN_ERR "raw1394: arm_read FAILED addr_entry not found" + " -> rcode_address_error\n"); + spin_unlock_irqrestore(&host_info_lock, irqflags); + return (RCODE_ADDRESS_ERROR); + } else { + DBGMSG("arm_read addr_entry FOUND"); + } + if (arm_addr->rec_length < length) { + DBGMSG("arm_read blocklength too big -> rcode_data_error"); + rcode = RCODE_DATA_ERROR; /* hardware error, data is unavailable */ + } + if (rcode == -1) { + if (arm_addr->access_rights & ARM_READ) { + if (!(arm_addr->client_transactions & ARM_READ)) { + memcpy(buffer, + (arm_addr->addr_space_buffer) + (addr - + (arm_addr-> + start)), + length); + DBGMSG("arm_read -> (rcode_complete)"); + rcode = RCODE_COMPLETE; + } + } else { + rcode = RCODE_TYPE_ERROR; /* function not allowed */ + DBGMSG("arm_read -> rcode_type_error (access denied)"); + } + } + if (arm_addr->notification_options & ARM_READ) { + DBGMSG("arm_read -> entering notification-section"); + req = __alloc_pending_request(GFP_ATOMIC); + if (!req) { + DBGMSG("arm_read -> rcode_conflict_error"); + spin_unlock_irqrestore(&host_info_lock, irqflags); + return (RCODE_CONFLICT_ERROR); /* A resource conflict was detected. + The request may be retried */ + } + if (rcode == RCODE_COMPLETE) { + size = + sizeof(struct arm_request) + + sizeof(struct arm_response) + + length * sizeof(byte_t) + + sizeof(struct arm_request_response); + } else { + size = + sizeof(struct arm_request) + + sizeof(struct arm_response) + + sizeof(struct arm_request_response); + } + req->data = kmalloc(size, GFP_ATOMIC); + if (!(req->data)) { + free_pending_request(req); + DBGMSG("arm_read -> rcode_conflict_error"); + spin_unlock_irqrestore(&host_info_lock, irqflags); + return (RCODE_CONFLICT_ERROR); /* A resource conflict was detected. + The request may be retried */ + } + req->free_data = 1; + req->file_info = fi; + req->req.type = RAW1394_REQ_ARM; + req->req.generation = get_hpsb_generation(host); + req->req.misc = + (((length << 16) & (0xFFFF0000)) | (ARM_READ & 0xFF)); + req->req.tag = arm_addr->arm_tag; + req->req.recvb = arm_addr->recvb; + req->req.length = size; + arm_req_resp = (struct arm_request_response *)(req->data); + arm_req = (struct arm_request *)((byte_t *) (req->data) + + (sizeof + (struct + arm_request_response))); + arm_resp = + (struct arm_response *)((byte_t *) (arm_req) + + (sizeof(struct arm_request))); + arm_req->buffer = NULL; + arm_resp->buffer = NULL; + if (rcode == RCODE_COMPLETE) { + byte_t *buf = + (byte_t *) arm_resp + sizeof(struct arm_response); + memcpy(buf, + (arm_addr->addr_space_buffer) + (addr - + (arm_addr-> + start)), + length); + arm_resp->buffer = + int2ptr((arm_addr->recvb) + + sizeof(struct arm_request_response) + + sizeof(struct arm_request) + + sizeof(struct arm_response)); + } + arm_resp->buffer_length = + (rcode == RCODE_COMPLETE) ? length : 0; + arm_resp->response_code = rcode; + arm_req->buffer_length = 0; + arm_req->generation = req->req.generation; + arm_req->extended_transaction_code = 0; + arm_req->destination_offset = addr; + arm_req->source_nodeid = nodeid; + arm_req->destination_nodeid = host->node_id; + arm_req->tlabel = (flags >> 10) & 0x3f; + arm_req->tcode = (flags >> 4) & 0x0f; + arm_req_resp->request = int2ptr((arm_addr->recvb) + + sizeof(struct + arm_request_response)); + arm_req_resp->response = + int2ptr((arm_addr->recvb) + + sizeof(struct arm_request_response) + + sizeof(struct arm_request)); + queue_complete_req(req); + } + spin_unlock_irqrestore(&host_info_lock, irqflags); + return (rcode); +} + +static int arm_write(struct hpsb_host *host, int nodeid, int destid, + quadlet_t * data, u64 addr, size_t length, u16 flags) +{ + unsigned long irqflags; + struct pending_request *req; + struct host_info *hi; + struct file_info *fi = NULL; + struct list_head *entry; + struct arm_addr *arm_addr = NULL; + struct arm_request *arm_req = NULL; + struct arm_response *arm_resp = NULL; + int found = 0, size = 0, rcode = -1, length_conflict = 0; + struct arm_request_response *arm_req_resp = NULL; + + DBGMSG("arm_write called by node: %X " + "addr: %4.4x %8.8x length: %Zu", nodeid, + (u16) ((addr >> 32) & 0xFFFF), (u32) (addr & 0xFFFFFFFF), + length); + spin_lock_irqsave(&host_info_lock, irqflags); + hi = find_host_info(host); /* search address-entry */ + if (hi != NULL) { + list_for_each_entry(fi, &hi->file_info_list, list) { + entry = fi->addr_list.next; + while (entry != &(fi->addr_list)) { + arm_addr = + list_entry(entry, struct arm_addr, + addr_list); + if (((arm_addr->start) <= (addr)) + && ((arm_addr->end) >= (addr + length))) { + found = 1; + break; + } + entry = entry->next; + } + if (found) { + break; + } + } + } + rcode = -1; + if (!found) { + printk(KERN_ERR "raw1394: arm_write FAILED addr_entry not found" + " -> rcode_address_error\n"); + spin_unlock_irqrestore(&host_info_lock, irqflags); + return (RCODE_ADDRESS_ERROR); + } else { + DBGMSG("arm_write addr_entry FOUND"); + } + if (arm_addr->rec_length < length) { + DBGMSG("arm_write blocklength too big -> rcode_data_error"); + length_conflict = 1; + rcode = RCODE_DATA_ERROR; /* hardware error, data is unavailable */ + } + if (rcode == -1) { + if (arm_addr->access_rights & ARM_WRITE) { + if (!(arm_addr->client_transactions & ARM_WRITE)) { + memcpy((arm_addr->addr_space_buffer) + + (addr - (arm_addr->start)), data, + length); + DBGMSG("arm_write -> (rcode_complete)"); + rcode = RCODE_COMPLETE; + } + } else { + rcode = RCODE_TYPE_ERROR; /* function not allowed */ + DBGMSG("arm_write -> rcode_type_error (access denied)"); + } + } + if (arm_addr->notification_options & ARM_WRITE) { + DBGMSG("arm_write -> entering notification-section"); + req = __alloc_pending_request(GFP_ATOMIC); + if (!req) { + DBGMSG("arm_write -> rcode_conflict_error"); + spin_unlock_irqrestore(&host_info_lock, irqflags); + return (RCODE_CONFLICT_ERROR); /* A resource conflict was detected. + The request my be retried */ + } + size = + sizeof(struct arm_request) + sizeof(struct arm_response) + + (length) * sizeof(byte_t) + + sizeof(struct arm_request_response); + req->data = kmalloc(size, GFP_ATOMIC); + if (!(req->data)) { + free_pending_request(req); + DBGMSG("arm_write -> rcode_conflict_error"); + spin_unlock_irqrestore(&host_info_lock, irqflags); + return (RCODE_CONFLICT_ERROR); /* A resource conflict was detected. + The request may be retried */ + } + req->free_data = 1; + req->file_info = fi; + req->req.type = RAW1394_REQ_ARM; + req->req.generation = get_hpsb_generation(host); + req->req.misc = + (((length << 16) & (0xFFFF0000)) | (ARM_WRITE & 0xFF)); + req->req.tag = arm_addr->arm_tag; + req->req.recvb = arm_addr->recvb; + req->req.length = size; + arm_req_resp = (struct arm_request_response *)(req->data); + arm_req = (struct arm_request *)((byte_t *) (req->data) + + (sizeof + (struct + arm_request_response))); + arm_resp = + (struct arm_response *)((byte_t *) (arm_req) + + (sizeof(struct arm_request))); + arm_resp->buffer = NULL; + memcpy((byte_t *) arm_resp + sizeof(struct arm_response), + data, length); + arm_req->buffer = int2ptr((arm_addr->recvb) + + sizeof(struct arm_request_response) + + sizeof(struct arm_request) + + sizeof(struct arm_response)); + arm_req->buffer_length = length; + arm_req->generation = req->req.generation; + arm_req->extended_transaction_code = 0; + arm_req->destination_offset = addr; + arm_req->source_nodeid = nodeid; + arm_req->destination_nodeid = destid; + arm_req->tlabel = (flags >> 10) & 0x3f; + arm_req->tcode = (flags >> 4) & 0x0f; + arm_resp->buffer_length = 0; + arm_resp->response_code = rcode; + arm_req_resp->request = int2ptr((arm_addr->recvb) + + sizeof(struct + arm_request_response)); + arm_req_resp->response = + int2ptr((arm_addr->recvb) + + sizeof(struct arm_request_response) + + sizeof(struct arm_request)); + queue_complete_req(req); + } + spin_unlock_irqrestore(&host_info_lock, irqflags); + return (rcode); +} + +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) +{ + unsigned long irqflags; + struct pending_request *req; + struct host_info *hi; + struct file_info *fi = NULL; + struct list_head *entry; + struct arm_addr *arm_addr = NULL; + struct arm_request *arm_req = NULL; + struct arm_response *arm_resp = NULL; + int found = 0, size = 0, rcode = -1; + quadlet_t old, new; + struct arm_request_response *arm_req_resp = NULL; + + if (((ext_tcode & 0xFF) == EXTCODE_FETCH_ADD) || + ((ext_tcode & 0xFF) == EXTCODE_LITTLE_ADD)) { + DBGMSG("arm_lock called by node: %X " + "addr: %4.4x %8.8x extcode: %2.2X data: %8.8X", + nodeid, (u16) ((addr >> 32) & 0xFFFF), + (u32) (addr & 0xFFFFFFFF), ext_tcode & 0xFF, + be32_to_cpu(data)); + } else { + DBGMSG("arm_lock called by node: %X " + "addr: %4.4x %8.8x extcode: %2.2X data: %8.8X arg: %8.8X", + nodeid, (u16) ((addr >> 32) & 0xFFFF), + (u32) (addr & 0xFFFFFFFF), ext_tcode & 0xFF, + be32_to_cpu(data), be32_to_cpu(arg)); + } + spin_lock_irqsave(&host_info_lock, irqflags); + hi = find_host_info(host); /* search address-entry */ + if (hi != NULL) { + list_for_each_entry(fi, &hi->file_info_list, list) { + entry = fi->addr_list.next; + while (entry != &(fi->addr_list)) { + arm_addr = + list_entry(entry, struct arm_addr, + addr_list); + if (((arm_addr->start) <= (addr)) + && ((arm_addr->end) >= + (addr + sizeof(*store)))) { + found = 1; + break; + } + entry = entry->next; + } + if (found) { + break; + } + } + } + rcode = -1; + if (!found) { + printk(KERN_ERR "raw1394: arm_lock FAILED addr_entry not found" + " -> rcode_address_error\n"); + spin_unlock_irqrestore(&host_info_lock, irqflags); + return (RCODE_ADDRESS_ERROR); + } else { + DBGMSG("arm_lock addr_entry FOUND"); + } + if (rcode == -1) { + if (arm_addr->access_rights & ARM_LOCK) { + if (!(arm_addr->client_transactions & ARM_LOCK)) { + memcpy(&old, + (arm_addr->addr_space_buffer) + (addr - + (arm_addr-> + start)), + sizeof(old)); + switch (ext_tcode) { + case (EXTCODE_MASK_SWAP): + new = data | (old & ~arg); + break; + case (EXTCODE_COMPARE_SWAP): + if (old == arg) { + new = data; + } else { + new = old; + } + break; + case (EXTCODE_FETCH_ADD): + new = + cpu_to_be32(be32_to_cpu(data) + + be32_to_cpu(old)); + break; + case (EXTCODE_LITTLE_ADD): + new = + cpu_to_le32(le32_to_cpu(data) + + le32_to_cpu(old)); + break; + case (EXTCODE_BOUNDED_ADD): + if (old != arg) { + new = + cpu_to_be32(be32_to_cpu + (data) + + be32_to_cpu + (old)); + } else { + new = old; + } + break; + case (EXTCODE_WRAP_ADD): + if (old != arg) { + new = + cpu_to_be32(be32_to_cpu + (data) + + be32_to_cpu + (old)); + } else { + new = data; + } + break; + default: + rcode = RCODE_TYPE_ERROR; /* function not allowed */ + printk(KERN_ERR + "raw1394: arm_lock FAILED " + "ext_tcode not allowed -> rcode_type_error\n"); + break; + } /*switch */ + if (rcode == -1) { + DBGMSG("arm_lock -> (rcode_complete)"); + rcode = RCODE_COMPLETE; + memcpy(store, &old, sizeof(*store)); + memcpy((arm_addr->addr_space_buffer) + + (addr - (arm_addr->start)), + &new, sizeof(*store)); + } + } + } else { + rcode = RCODE_TYPE_ERROR; /* function not allowed */ + DBGMSG("arm_lock -> rcode_type_error (access denied)"); + } + } + if (arm_addr->notification_options & ARM_LOCK) { + byte_t *buf1, *buf2; + DBGMSG("arm_lock -> entering notification-section"); + req = __alloc_pending_request(GFP_ATOMIC); + if (!req) { + DBGMSG("arm_lock -> rcode_conflict_error"); + spin_unlock_irqrestore(&host_info_lock, irqflags); + return (RCODE_CONFLICT_ERROR); /* A resource conflict was detected. + The request may be retried */ + } + size = sizeof(struct arm_request) + sizeof(struct arm_response) + 3 * sizeof(*store) + sizeof(struct arm_request_response); /* maximum */ + req->data = kmalloc(size, GFP_ATOMIC); + if (!(req->data)) { + free_pending_request(req); + DBGMSG("arm_lock -> rcode_conflict_error"); + spin_unlock_irqrestore(&host_info_lock, irqflags); + return (RCODE_CONFLICT_ERROR); /* A resource conflict was detected. + The request may be retried */ + } + req->free_data = 1; + arm_req_resp = (struct arm_request_response *)(req->data); + arm_req = (struct arm_request *)((byte_t *) (req->data) + + (sizeof + (struct + arm_request_response))); + arm_resp = + (struct arm_response *)((byte_t *) (arm_req) + + (sizeof(struct arm_request))); + buf1 = (byte_t *) arm_resp + sizeof(struct arm_response); + buf2 = buf1 + 2 * sizeof(*store); + if ((ext_tcode == EXTCODE_FETCH_ADD) || + (ext_tcode == EXTCODE_LITTLE_ADD)) { + arm_req->buffer_length = sizeof(*store); + memcpy(buf1, &data, sizeof(*store)); + + } else { + arm_req->buffer_length = 2 * sizeof(*store); + memcpy(buf1, &arg, sizeof(*store)); + memcpy(buf1 + sizeof(*store), &data, sizeof(*store)); + } + if (rcode == RCODE_COMPLETE) { + arm_resp->buffer_length = sizeof(*store); + memcpy(buf2, &old, sizeof(*store)); + } else { + arm_resp->buffer_length = 0; + } + req->file_info = fi; + req->req.type = RAW1394_REQ_ARM; + req->req.generation = get_hpsb_generation(host); + req->req.misc = ((((sizeof(*store)) << 16) & (0xFFFF0000)) | + (ARM_LOCK & 0xFF)); + req->req.tag = arm_addr->arm_tag; + req->req.recvb = arm_addr->recvb; + req->req.length = size; + arm_req->generation = req->req.generation; + arm_req->extended_transaction_code = ext_tcode; + arm_req->destination_offset = addr; + arm_req->source_nodeid = nodeid; + arm_req->destination_nodeid = host->node_id; + arm_req->tlabel = (flags >> 10) & 0x3f; + arm_req->tcode = (flags >> 4) & 0x0f; + arm_resp->response_code = rcode; + arm_req_resp->request = int2ptr((arm_addr->recvb) + + sizeof(struct + arm_request_response)); + arm_req_resp->response = + int2ptr((arm_addr->recvb) + + sizeof(struct arm_request_response) + + sizeof(struct arm_request)); + arm_req->buffer = + int2ptr((arm_addr->recvb) + + sizeof(struct arm_request_response) + + sizeof(struct arm_request) + + sizeof(struct arm_response)); + arm_resp->buffer = + int2ptr((arm_addr->recvb) + + sizeof(struct arm_request_response) + + sizeof(struct arm_request) + + sizeof(struct arm_response) + 2 * sizeof(*store)); + queue_complete_req(req); + } + spin_unlock_irqrestore(&host_info_lock, irqflags); + return (rcode); +} + +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) +{ + unsigned long irqflags; + struct pending_request *req; + struct host_info *hi; + struct file_info *fi = NULL; + struct list_head *entry; + struct arm_addr *arm_addr = NULL; + struct arm_request *arm_req = NULL; + struct arm_response *arm_resp = NULL; + int found = 0, size = 0, rcode = -1; + octlet_t old, new; + struct arm_request_response *arm_req_resp = NULL; + + if (((ext_tcode & 0xFF) == EXTCODE_FETCH_ADD) || + ((ext_tcode & 0xFF) == EXTCODE_LITTLE_ADD)) { + DBGMSG("arm_lock64 called by node: %X " + "addr: %4.4x %8.8x extcode: %2.2X data: %8.8X %8.8X ", + nodeid, (u16) ((addr >> 32) & 0xFFFF), + (u32) (addr & 0xFFFFFFFF), + ext_tcode & 0xFF, + (u32) ((be64_to_cpu(data) >> 32) & 0xFFFFFFFF), + (u32) (be64_to_cpu(data) & 0xFFFFFFFF)); + } else { + DBGMSG("arm_lock64 called by node: %X " + "addr: %4.4x %8.8x extcode: %2.2X data: %8.8X %8.8X arg: " + "%8.8X %8.8X ", + nodeid, (u16) ((addr >> 32) & 0xFFFF), + (u32) (addr & 0xFFFFFFFF), + ext_tcode & 0xFF, + (u32) ((be64_to_cpu(data) >> 32) & 0xFFFFFFFF), + (u32) (be64_to_cpu(data) & 0xFFFFFFFF), + (u32) ((be64_to_cpu(arg) >> 32) & 0xFFFFFFFF), + (u32) (be64_to_cpu(arg) & 0xFFFFFFFF)); + } + spin_lock_irqsave(&host_info_lock, irqflags); + hi = find_host_info(host); /* search addressentry in file_info's for host */ + if (hi != NULL) { + list_for_each_entry(fi, &hi->file_info_list, list) { + entry = fi->addr_list.next; + while (entry != &(fi->addr_list)) { + arm_addr = + list_entry(entry, struct arm_addr, + addr_list); + if (((arm_addr->start) <= (addr)) + && ((arm_addr->end) >= + (addr + sizeof(*store)))) { + found = 1; + break; + } + entry = entry->next; + } + if (found) { + break; + } + } + } + rcode = -1; + if (!found) { + printk(KERN_ERR + "raw1394: arm_lock64 FAILED addr_entry not found" + " -> rcode_address_error\n"); + spin_unlock_irqrestore(&host_info_lock, irqflags); + return (RCODE_ADDRESS_ERROR); + } else { + DBGMSG("arm_lock64 addr_entry FOUND"); + } + if (rcode == -1) { + if (arm_addr->access_rights & ARM_LOCK) { + if (!(arm_addr->client_transactions & ARM_LOCK)) { + memcpy(&old, + (arm_addr->addr_space_buffer) + (addr - + (arm_addr-> + start)), + sizeof(old)); + switch (ext_tcode) { + case (EXTCODE_MASK_SWAP): + new = data | (old & ~arg); + break; + case (EXTCODE_COMPARE_SWAP): + if (old == arg) { + new = data; + } else { + new = old; + } + break; + case (EXTCODE_FETCH_ADD): + new = + cpu_to_be64(be64_to_cpu(data) + + be64_to_cpu(old)); + break; + case (EXTCODE_LITTLE_ADD): + new = + cpu_to_le64(le64_to_cpu(data) + + le64_to_cpu(old)); + break; + case (EXTCODE_BOUNDED_ADD): + if (old != arg) { + new = + cpu_to_be64(be64_to_cpu + (data) + + be64_to_cpu + (old)); + } else { + new = old; + } + break; + case (EXTCODE_WRAP_ADD): + if (old != arg) { + new = + cpu_to_be64(be64_to_cpu + (data) + + be64_to_cpu + (old)); + } else { + new = data; + } + break; + default: + printk(KERN_ERR + "raw1394: arm_lock64 FAILED " + "ext_tcode not allowed -> rcode_type_error\n"); + rcode = RCODE_TYPE_ERROR; /* function not allowed */ + break; + } /*switch */ + if (rcode == -1) { + DBGMSG + ("arm_lock64 -> (rcode_complete)"); + rcode = RCODE_COMPLETE; + memcpy(store, &old, sizeof(*store)); + memcpy((arm_addr->addr_space_buffer) + + (addr - (arm_addr->start)), + &new, sizeof(*store)); + } + } + } else { + rcode = RCODE_TYPE_ERROR; /* function not allowed */ + DBGMSG + ("arm_lock64 -> rcode_type_error (access denied)"); + } + } + if (arm_addr->notification_options & ARM_LOCK) { + byte_t *buf1, *buf2; + DBGMSG("arm_lock64 -> entering notification-section"); + req = __alloc_pending_request(GFP_ATOMIC); + if (!req) { + spin_unlock_irqrestore(&host_info_lock, irqflags); + DBGMSG("arm_lock64 -> rcode_conflict_error"); + return (RCODE_CONFLICT_ERROR); /* A resource conflict was detected. + The request may be retried */ + } + size = sizeof(struct arm_request) + sizeof(struct arm_response) + 3 * sizeof(*store) + sizeof(struct arm_request_response); /* maximum */ + req->data = kmalloc(size, GFP_ATOMIC); + if (!(req->data)) { + free_pending_request(req); + spin_unlock_irqrestore(&host_info_lock, irqflags); + DBGMSG("arm_lock64 -> rcode_conflict_error"); + return (RCODE_CONFLICT_ERROR); /* A resource conflict was detected. + The request may be retried */ + } + req->free_data = 1; + arm_req_resp = (struct arm_request_response *)(req->data); + arm_req = (struct arm_request *)((byte_t *) (req->data) + + (sizeof + (struct + arm_request_response))); + arm_resp = + (struct arm_response *)((byte_t *) (arm_req) + + (sizeof(struct arm_request))); + buf1 = (byte_t *) arm_resp + sizeof(struct arm_response); + buf2 = buf1 + 2 * sizeof(*store); + if ((ext_tcode == EXTCODE_FETCH_ADD) || + (ext_tcode == EXTCODE_LITTLE_ADD)) { + arm_req->buffer_length = sizeof(*store); + memcpy(buf1, &data, sizeof(*store)); + + } else { + arm_req->buffer_length = 2 * sizeof(*store); + memcpy(buf1, &arg, sizeof(*store)); + memcpy(buf1 + sizeof(*store), &data, sizeof(*store)); + } + if (rcode == RCODE_COMPLETE) { + arm_resp->buffer_length = sizeof(*store); + memcpy(buf2, &old, sizeof(*store)); + } else { + arm_resp->buffer_length = 0; + } + req->file_info = fi; + req->req.type = RAW1394_REQ_ARM; + req->req.generation = get_hpsb_generation(host); + req->req.misc = ((((sizeof(*store)) << 16) & (0xFFFF0000)) | + (ARM_LOCK & 0xFF)); + req->req.tag = arm_addr->arm_tag; + req->req.recvb = arm_addr->recvb; + req->req.length = size; + arm_req->generation = req->req.generation; + arm_req->extended_transaction_code = ext_tcode; + arm_req->destination_offset = addr; + arm_req->source_nodeid = nodeid; + arm_req->destination_nodeid = host->node_id; + arm_req->tlabel = (flags >> 10) & 0x3f; + arm_req->tcode = (flags >> 4) & 0x0f; + arm_resp->response_code = rcode; + arm_req_resp->request = int2ptr((arm_addr->recvb) + + sizeof(struct + arm_request_response)); + arm_req_resp->response = + int2ptr((arm_addr->recvb) + + sizeof(struct arm_request_response) + + sizeof(struct arm_request)); + arm_req->buffer = + int2ptr((arm_addr->recvb) + + sizeof(struct arm_request_response) + + sizeof(struct arm_request) + + sizeof(struct arm_response)); + arm_resp->buffer = + int2ptr((arm_addr->recvb) + + sizeof(struct arm_request_response) + + sizeof(struct arm_request) + + sizeof(struct arm_response) + 2 * sizeof(*store)); + queue_complete_req(req); + } + spin_unlock_irqrestore(&host_info_lock, irqflags); + return (rcode); +} + +static int arm_register(struct file_info *fi, struct pending_request *req) +{ + int retval; + struct arm_addr *addr; + struct host_info *hi; + struct file_info *fi_hlp = NULL; + struct list_head *entry; + struct arm_addr *arm_addr = NULL; + int same_host, another_host; + unsigned long flags; + + DBGMSG("arm_register called " + "addr(Offset): %8.8x %8.8x length: %u " + "rights: %2.2X notify: %2.2X " + "max_blk_len: %4.4X", + (u32) ((req->req.address >> 32) & 0xFFFF), + (u32) (req->req.address & 0xFFFFFFFF), + req->req.length, ((req->req.misc >> 8) & 0xFF), + (req->req.misc & 0xFF), ((req->req.misc >> 16) & 0xFFFF)); + /* check addressrange */ + if ((((req->req.address) & ~(0xFFFFFFFFFFFFULL)) != 0) || + (((req->req.address + req->req.length) & ~(0xFFFFFFFFFFFFULL)) != + 0)) { + req->req.length = 0; + return (-EINVAL); + } + /* addr-list-entry for fileinfo */ + addr = kmalloc(sizeof(*addr), GFP_KERNEL); + if (!addr) { + req->req.length = 0; + return (-ENOMEM); + } + /* allocation of addr_space_buffer */ + addr->addr_space_buffer = vmalloc(req->req.length); + if (!(addr->addr_space_buffer)) { + kfree(addr); + req->req.length = 0; + return (-ENOMEM); + } + /* initialization of addr_space_buffer */ + if ((req->req.sendb) == (unsigned long)NULL) { + /* init: set 0 */ + memset(addr->addr_space_buffer, 0, req->req.length); + } else { + /* init: user -> kernel */ + if (copy_from_user + (addr->addr_space_buffer, int2ptr(req->req.sendb), + req->req.length)) { + vfree(addr->addr_space_buffer); + kfree(addr); + return (-EFAULT); + } + } + INIT_LIST_HEAD(&addr->addr_list); + addr->arm_tag = req->req.tag; + addr->start = req->req.address; + addr->end = req->req.address + req->req.length; + addr->access_rights = (u8) (req->req.misc & 0x0F); + addr->notification_options = (u8) ((req->req.misc >> 4) & 0x0F); + addr->client_transactions = (u8) ((req->req.misc >> 8) & 0x0F); + addr->access_rights |= addr->client_transactions; + addr->notification_options |= addr->client_transactions; + addr->recvb = req->req.recvb; + addr->rec_length = (u16) ((req->req.misc >> 16) & 0xFFFF); + + spin_lock_irqsave(&host_info_lock, flags); + hi = find_host_info(fi->host); + same_host = 0; + another_host = 0; + /* same host with address-entry containing same addressrange ? */ + list_for_each_entry(fi_hlp, &hi->file_info_list, list) { + entry = fi_hlp->addr_list.next; + while (entry != &(fi_hlp->addr_list)) { + arm_addr = + list_entry(entry, struct arm_addr, addr_list); + if ((arm_addr->start == addr->start) + && (arm_addr->end == addr->end)) { + DBGMSG("same host ownes same " + "addressrange -> EALREADY"); + same_host = 1; + break; + } + entry = entry->next; + } + if (same_host) { + break; + } + } + if (same_host) { + /* addressrange occupied by same host */ + spin_unlock_irqrestore(&host_info_lock, flags); + vfree(addr->addr_space_buffer); + kfree(addr); + return (-EALREADY); + } + /* another host with valid address-entry containing same addressrange */ + list_for_each_entry(hi, &host_info_list, list) { + if (hi->host != fi->host) { + list_for_each_entry(fi_hlp, &hi->file_info_list, list) { + entry = fi_hlp->addr_list.next; + while (entry != &(fi_hlp->addr_list)) { + arm_addr = + list_entry(entry, struct arm_addr, + addr_list); + if ((arm_addr->start == addr->start) + && (arm_addr->end == addr->end)) { + DBGMSG + ("another host ownes same " + "addressrange"); + another_host = 1; + break; + } + entry = entry->next; + } + if (another_host) { + break; + } + } + } + } + spin_unlock_irqrestore(&host_info_lock, flags); + + if (another_host) { + DBGMSG("another hosts entry is valid -> SUCCESS"); + if (copy_to_user(int2ptr(req->req.recvb), + &addr->start, sizeof(u64))) { + printk(KERN_ERR "raw1394: arm_register failed " + " address-range-entry is invalid -> EFAULT !!!\n"); + vfree(addr->addr_space_buffer); + kfree(addr); + return (-EFAULT); + } + free_pending_request(req); /* immediate success or fail */ + /* INSERT ENTRY */ + spin_lock_irqsave(&host_info_lock, flags); + list_add_tail(&addr->addr_list, &fi->addr_list); + spin_unlock_irqrestore(&host_info_lock, flags); + return 0; + } + retval = + hpsb_register_addrspace(&raw1394_highlevel, fi->host, &arm_ops, + req->req.address, + req->req.address + req->req.length); + if (retval) { + /* INSERT ENTRY */ + spin_lock_irqsave(&host_info_lock, flags); + list_add_tail(&addr->addr_list, &fi->addr_list); + spin_unlock_irqrestore(&host_info_lock, flags); + } else { + DBGMSG("arm_register failed errno: %d \n", retval); + vfree(addr->addr_space_buffer); + kfree(addr); + return (-EALREADY); + } + free_pending_request(req); /* immediate success or fail */ + return 0; +} + +static int arm_unregister(struct file_info *fi, struct pending_request *req) +{ + int found = 0; + int retval = 0; + struct list_head *entry; + struct arm_addr *addr = NULL; + struct host_info *hi; + struct file_info *fi_hlp = NULL; + struct arm_addr *arm_addr = NULL; + int another_host; + unsigned long flags; + + DBGMSG("arm_Unregister called addr(Offset): " + "%8.8x %8.8x", + (u32) ((req->req.address >> 32) & 0xFFFF), + (u32) (req->req.address & 0xFFFFFFFF)); + spin_lock_irqsave(&host_info_lock, flags); + /* get addr */ + entry = fi->addr_list.next; + while (entry != &(fi->addr_list)) { + addr = list_entry(entry, struct arm_addr, addr_list); + if (addr->start == req->req.address) { + found = 1; + break; + } + entry = entry->next; + } + if (!found) { + DBGMSG("arm_Unregister addr not found"); + spin_unlock_irqrestore(&host_info_lock, flags); + return (-EINVAL); + } + DBGMSG("arm_Unregister addr found"); + another_host = 0; + /* another host with valid address-entry containing + same addressrange */ + list_for_each_entry(hi, &host_info_list, list) { + if (hi->host != fi->host) { + list_for_each_entry(fi_hlp, &hi->file_info_list, list) { + entry = fi_hlp->addr_list.next; + while (entry != &(fi_hlp->addr_list)) { + arm_addr = list_entry(entry, + struct arm_addr, + addr_list); + if (arm_addr->start == addr->start) { + DBGMSG("another host ownes " + "same addressrange"); + another_host = 1; + break; + } + entry = entry->next; + } + if (another_host) { + break; + } + } + } + } + if (another_host) { + DBGMSG("delete entry from list -> success"); + list_del(&addr->addr_list); + spin_unlock_irqrestore(&host_info_lock, flags); + vfree(addr->addr_space_buffer); + kfree(addr); + free_pending_request(req); /* immediate success or fail */ + return 0; + } + retval = + hpsb_unregister_addrspace(&raw1394_highlevel, fi->host, + addr->start); + if (!retval) { + printk(KERN_ERR "raw1394: arm_Unregister failed -> EINVAL\n"); + spin_unlock_irqrestore(&host_info_lock, flags); + return (-EINVAL); + } + DBGMSG("delete entry from list -> success"); + list_del(&addr->addr_list); + spin_unlock_irqrestore(&host_info_lock, flags); + vfree(addr->addr_space_buffer); + kfree(addr); + free_pending_request(req); /* immediate success or fail */ + return 0; +} + +/* Copy data from ARM buffer(s) to user buffer. */ +static int arm_get_buf(struct file_info *fi, struct pending_request *req) +{ + struct arm_addr *arm_addr = NULL; + unsigned long flags; + unsigned long offset; + + struct list_head *entry; + + DBGMSG("arm_get_buf " + "addr(Offset): %04X %08X length: %u", + (u32) ((req->req.address >> 32) & 0xFFFF), + (u32) (req->req.address & 0xFFFFFFFF), (u32) req->req.length); + + spin_lock_irqsave(&host_info_lock, flags); + entry = fi->addr_list.next; + while (entry != &(fi->addr_list)) { + arm_addr = list_entry(entry, struct arm_addr, addr_list); + if ((arm_addr->start <= req->req.address) && + (arm_addr->end > req->req.address)) { + if (req->req.address + req->req.length <= arm_addr->end) { + offset = req->req.address - arm_addr->start; + spin_unlock_irqrestore(&host_info_lock, flags); + + DBGMSG + ("arm_get_buf copy_to_user( %08X, %p, %u )", + (u32) req->req.recvb, + arm_addr->addr_space_buffer + offset, + (u32) req->req.length); + if (copy_to_user + (int2ptr(req->req.recvb), + arm_addr->addr_space_buffer + offset, + req->req.length)) + return (-EFAULT); + + /* We have to free the request, because we + * queue no response, and therefore nobody + * will free it. */ + free_pending_request(req); + return 0; + } else { + DBGMSG("arm_get_buf request exceeded mapping"); + spin_unlock_irqrestore(&host_info_lock, flags); + return (-EINVAL); + } + } + entry = entry->next; + } + spin_unlock_irqrestore(&host_info_lock, flags); + return (-EINVAL); +} + +/* Copy data from user buffer to ARM buffer(s). */ +static int arm_set_buf(struct file_info *fi, struct pending_request *req) +{ + struct arm_addr *arm_addr = NULL; + unsigned long flags; + unsigned long offset; + + struct list_head *entry; + + DBGMSG("arm_set_buf " + "addr(Offset): %04X %08X length: %u", + (u32) ((req->req.address >> 32) & 0xFFFF), + (u32) (req->req.address & 0xFFFFFFFF), (u32) req->req.length); + + spin_lock_irqsave(&host_info_lock, flags); + entry = fi->addr_list.next; + while (entry != &(fi->addr_list)) { + arm_addr = list_entry(entry, struct arm_addr, addr_list); + if ((arm_addr->start <= req->req.address) && + (arm_addr->end > req->req.address)) { + if (req->req.address + req->req.length <= arm_addr->end) { + offset = req->req.address - arm_addr->start; + spin_unlock_irqrestore(&host_info_lock, flags); + + DBGMSG + ("arm_set_buf copy_from_user( %p, %08X, %u )", + arm_addr->addr_space_buffer + offset, + (u32) req->req.sendb, + (u32) req->req.length); + if (copy_from_user + (arm_addr->addr_space_buffer + offset, + int2ptr(req->req.sendb), + req->req.length)) + return (-EFAULT); + + /* We have to free the request, because we + * queue no response, and therefore nobody + * will free it. */ + free_pending_request(req); + return 0; + } else { + DBGMSG("arm_set_buf request exceeded mapping"); + spin_unlock_irqrestore(&host_info_lock, flags); + return (-EINVAL); + } + } + entry = entry->next; + } + spin_unlock_irqrestore(&host_info_lock, flags); + return (-EINVAL); +} + +static int reset_notification(struct file_info *fi, struct pending_request *req) +{ + DBGMSG("reset_notification called - switch %s ", + (req->req.misc == RAW1394_NOTIFY_OFF) ? "OFF" : "ON"); + if ((req->req.misc == RAW1394_NOTIFY_OFF) || + (req->req.misc == RAW1394_NOTIFY_ON)) { + fi->notification = (u8) req->req.misc; + free_pending_request(req); /* we have to free the request, because we queue no response, and therefore nobody will free it */ + return 0; + } + /* error EINVAL (22) invalid argument */ + return (-EINVAL); +} + +static int write_phypacket(struct file_info *fi, struct pending_request *req) +{ + struct hpsb_packet *packet = NULL; + int retval = 0; + quadlet_t data; + unsigned long flags; + + data = be32_to_cpu((u32) req->req.sendb); + DBGMSG("write_phypacket called - quadlet 0x%8.8x ", data); + packet = hpsb_make_phypacket(fi->host, data); + if (!packet) + return -ENOMEM; + req->req.length = 0; + req->packet = packet; + hpsb_set_packet_complete_task(packet, + (void (*)(void *))queue_complete_cb, req); + spin_lock_irqsave(&fi->reqlists_lock, flags); + list_add_tail(&req->list, &fi->req_pending); + spin_unlock_irqrestore(&fi->reqlists_lock, flags); + packet->generation = req->req.generation; + retval = hpsb_send_packet(packet); + DBGMSG("write_phypacket send_packet called => retval: %d ", retval); + if (retval < 0) { + req->req.error = RAW1394_ERROR_SEND_ERROR; + req->req.length = 0; + queue_complete_req(req); + } + return 0; +} + +static int get_config_rom(struct file_info *fi, struct pending_request *req) +{ + int ret = 0; + quadlet_t *data = kmalloc(req->req.length, GFP_KERNEL); + int status; + + if (!data) + return -ENOMEM; + + status = + csr1212_read(fi->host->csr.rom, CSR1212_CONFIG_ROM_SPACE_OFFSET, + data, req->req.length); + if (copy_to_user(int2ptr(req->req.recvb), data, req->req.length)) + ret = -EFAULT; + if (copy_to_user + (int2ptr(req->req.tag), &fi->host->csr.rom->cache_head->len, + sizeof(fi->host->csr.rom->cache_head->len))) + ret = -EFAULT; + if (copy_to_user(int2ptr(req->req.address), &fi->host->csr.generation, + sizeof(fi->host->csr.generation))) + ret = -EFAULT; + if (copy_to_user(int2ptr(req->req.sendb), &status, sizeof(status))) + ret = -EFAULT; + kfree(data); + if (ret >= 0) { + free_pending_request(req); /* we have to free the request, because we queue no response, and therefore nobody will free it */ + } + return ret; +} + +static int update_config_rom(struct file_info *fi, struct pending_request *req) +{ + int ret = 0; + quadlet_t *data = kmalloc(req->req.length, GFP_KERNEL); + if (!data) + return -ENOMEM; + if (copy_from_user(data, int2ptr(req->req.sendb), req->req.length)) { + ret = -EFAULT; + } else { + int status = hpsb_update_config_rom(fi->host, + data, req->req.length, + (unsigned char)req->req. + misc); + if (copy_to_user + (int2ptr(req->req.recvb), &status, sizeof(status))) + ret = -ENOMEM; + } + kfree(data); + if (ret >= 0) { + free_pending_request(req); /* we have to free the request, because we queue no response, and therefore nobody will free it */ + fi->cfgrom_upd = 1; + } + return ret; +} + +static int modify_config_rom(struct file_info *fi, struct pending_request *req) +{ + struct csr1212_keyval *kv; + struct csr1212_csr_rom_cache *cache; + struct csr1212_dentry *dentry; + u32 dr; + int ret = 0; + + if (req->req.misc == ~0) { + if (req->req.length == 0) + return -EINVAL; + + /* Find an unused slot */ + for (dr = 0; + dr < RAW1394_MAX_USER_CSR_DIRS && fi->csr1212_dirs[dr]; + dr++) ; + + if (dr == RAW1394_MAX_USER_CSR_DIRS) + return -ENOMEM; + + fi->csr1212_dirs[dr] = + csr1212_new_directory(CSR1212_KV_ID_VENDOR); + if (!fi->csr1212_dirs[dr]) + return -ENOMEM; + } else { + dr = req->req.misc; + if (!fi->csr1212_dirs[dr]) + return -EINVAL; + + /* Delete old stuff */ + for (dentry = + fi->csr1212_dirs[dr]->value.directory.dentries_head; + dentry; dentry = dentry->next) { + csr1212_detach_keyval_from_directory(fi->host->csr.rom-> + root_kv, + dentry->kv); + } + + if (req->req.length == 0) { + csr1212_release_keyval(fi->csr1212_dirs[dr]); + fi->csr1212_dirs[dr] = NULL; + + hpsb_update_config_rom_image(fi->host); + free_pending_request(req); + return 0; + } + } + + cache = csr1212_rom_cache_malloc(0, req->req.length); + if (!cache) { + csr1212_release_keyval(fi->csr1212_dirs[dr]); + fi->csr1212_dirs[dr] = NULL; + return -ENOMEM; + } + + cache->filled_head = kmalloc(sizeof(*cache->filled_head), GFP_KERNEL); + if (!cache->filled_head) { + csr1212_release_keyval(fi->csr1212_dirs[dr]); + fi->csr1212_dirs[dr] = NULL; + CSR1212_FREE(cache); + return -ENOMEM; + } + cache->filled_tail = cache->filled_head; + + if (copy_from_user(cache->data, int2ptr(req->req.sendb), + req->req.length)) { + csr1212_release_keyval(fi->csr1212_dirs[dr]); + fi->csr1212_dirs[dr] = NULL; + ret = -EFAULT; + } else { + cache->len = req->req.length; + cache->filled_head->offset_start = 0; + cache->filled_head->offset_end = cache->size - 1; + + cache->layout_head = cache->layout_tail = fi->csr1212_dirs[dr]; + + ret = CSR1212_SUCCESS; + /* parse all the items */ + for (kv = cache->layout_head; ret == CSR1212_SUCCESS && kv; + kv = kv->next) { + ret = csr1212_parse_keyval(kv, cache); + } + + /* attach top level items to the root directory */ + for (dentry = + fi->csr1212_dirs[dr]->value.directory.dentries_head; + ret == CSR1212_SUCCESS && dentry; dentry = dentry->next) { + ret = + csr1212_attach_keyval_to_directory(fi->host->csr. + rom->root_kv, + dentry->kv); + } + + if (ret == CSR1212_SUCCESS) { + ret = hpsb_update_config_rom_image(fi->host); + + if (ret >= 0 && copy_to_user(int2ptr(req->req.recvb), + &dr, sizeof(dr))) { + ret = -ENOMEM; + } + } + } + kfree(cache->filled_head); + CSR1212_FREE(cache); + + if (ret >= 0) { + /* we have to free the request, because we queue no response, + * and therefore nobody will free it */ + free_pending_request(req); + return 0; + } else { + for (dentry = + fi->csr1212_dirs[dr]->value.directory.dentries_head; + dentry; dentry = dentry->next) { + csr1212_detach_keyval_from_directory(fi->host->csr.rom-> + root_kv, + dentry->kv); + } + csr1212_release_keyval(fi->csr1212_dirs[dr]); + fi->csr1212_dirs[dr] = NULL; + return ret; + } +} + +static int state_connected(struct file_info *fi, struct pending_request *req) +{ + int node = req->req.address >> 48; + + req->req.error = RAW1394_ERROR_NONE; + + switch (req->req.type) { + + case RAW1394_REQ_ECHO: + queue_complete_req(req); + return 0; + + case RAW1394_REQ_ARM_REGISTER: + return arm_register(fi, req); + + case RAW1394_REQ_ARM_UNREGISTER: + return arm_unregister(fi, req); + + case RAW1394_REQ_ARM_SET_BUF: + return arm_set_buf(fi, req); + + case RAW1394_REQ_ARM_GET_BUF: + return arm_get_buf(fi, req); + + case RAW1394_REQ_RESET_NOTIFY: + return reset_notification(fi, req); + + case RAW1394_REQ_ISO_SEND: + case RAW1394_REQ_ISO_LISTEN: + printk(KERN_DEBUG "raw1394: old iso ABI has been removed\n"); + req->req.error = RAW1394_ERROR_COMPAT; + req->req.misc = RAW1394_KERNELAPI_VERSION; + queue_complete_req(req); + return 0; + + case RAW1394_REQ_FCP_LISTEN: + handle_fcp_listen(fi, req); + return 0; + + case RAW1394_REQ_RESET_BUS: + if (req->req.misc == RAW1394_LONG_RESET) { + DBGMSG("busreset called (type: LONG)"); + hpsb_reset_bus(fi->host, LONG_RESET); + free_pending_request(req); /* we have to free the request, because we queue no response, and therefore nobody will free it */ + return 0; + } + if (req->req.misc == RAW1394_SHORT_RESET) { + DBGMSG("busreset called (type: SHORT)"); + hpsb_reset_bus(fi->host, SHORT_RESET); + free_pending_request(req); /* we have to free the request, because we queue no response, and therefore nobody will free it */ + return 0; + } + /* error EINVAL (22) invalid argument */ + return (-EINVAL); + case RAW1394_REQ_GET_ROM: + return get_config_rom(fi, req); + + case RAW1394_REQ_UPDATE_ROM: + return update_config_rom(fi, req); + + case RAW1394_REQ_MODIFY_ROM: + return modify_config_rom(fi, req); + } + + if (req->req.generation != get_hpsb_generation(fi->host)) { + req->req.error = RAW1394_ERROR_GENERATION; + req->req.generation = get_hpsb_generation(fi->host); + req->req.length = 0; + queue_complete_req(req); + return 0; + } + + switch (req->req.type) { + case RAW1394_REQ_PHYPACKET: + return write_phypacket(fi, req); + case RAW1394_REQ_ASYNC_SEND: + return handle_async_send(fi, req); + } + + if (req->req.length == 0) { + req->req.error = RAW1394_ERROR_INVALID_ARG; + queue_complete_req(req); + return 0; + } + + return handle_async_request(fi, req, node); +} + +static ssize_t raw1394_write(struct file *file, const 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 retval = -EBADFD; + +#ifdef CONFIG_COMPAT + if (count == sizeof(struct compat_raw1394_req) && + sizeof(struct compat_raw1394_req) != + sizeof(struct raw1394_request)) { + buffer = raw1394_compat_write(buffer); + if (IS_ERR(buffer)) + return PTR_ERR(buffer); + } else +#endif + 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; + } + + if (!mutex_trylock(&fi->state_mutex)) + return -EAGAIN; + + 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; + } + + mutex_unlock(&fi->state_mutex); + + if (retval < 0) { + free_pending_request(req); + } else { + BUG_ON(retval); + retval = count; + } + + return retval; +} + +/* rawiso operations */ + +/* check if any RAW1394_REQ_RAWISO_ACTIVITY event is already in the + * completion queue (reqlists_lock must be taken) */ +static inline int __rawiso_event_in_queue(struct file_info *fi) +{ + struct pending_request *req; + + list_for_each_entry(req, &fi->req_complete, list) + if (req->req.type == RAW1394_REQ_RAWISO_ACTIVITY) + return 1; + + return 0; +} + +/* put a RAWISO_ACTIVITY event in the queue, if one isn't there already */ +static void queue_rawiso_event(struct file_info *fi) +{ + unsigned long flags; + + spin_lock_irqsave(&fi->reqlists_lock, flags); + + /* only one ISO activity event may be in the queue */ + if (!__rawiso_event_in_queue(fi)) { + struct pending_request *req = + __alloc_pending_request(GFP_ATOMIC); + + if (req) { + req->file_info = fi; + req->req.type = RAW1394_REQ_RAWISO_ACTIVITY; + req->req.generation = get_hpsb_generation(fi->host); + __queue_complete_req(req); + } else { + /* on allocation failure, signal an overflow */ + if (fi->iso_handle) { + atomic_inc(&fi->iso_handle->overflows); + } + } + } + spin_unlock_irqrestore(&fi->reqlists_lock, flags); +} + +static void rawiso_activity_cb(struct hpsb_iso *iso) +{ + unsigned long flags; + struct host_info *hi; + struct file_info *fi; + + spin_lock_irqsave(&host_info_lock, flags); + hi = find_host_info(iso->host); + + if (hi != NULL) { + list_for_each_entry(fi, &hi->file_info_list, list) { + if (fi->iso_handle == iso) + queue_rawiso_event(fi); + } + } + + spin_unlock_irqrestore(&host_info_lock, flags); +} + +/* helper function - gather all the kernel iso status bits for returning to user-space */ +static void raw1394_iso_fill_status(struct hpsb_iso *iso, + struct raw1394_iso_status *stat) +{ + int overflows = atomic_read(&iso->overflows); + int skips = atomic_read(&iso->skips); + + stat->config.data_buf_size = iso->buf_size; + stat->config.buf_packets = iso->buf_packets; + stat->config.channel = iso->channel; + stat->config.speed = iso->speed; + stat->config.irq_interval = iso->irq_interval; + stat->n_packets = hpsb_iso_n_ready(iso); + stat->overflows = ((skips & 0xFFFF) << 16) | ((overflows & 0xFFFF)); + stat->xmit_cycle = iso->xmit_cycle; +} + +static int raw1394_iso_xmit_init(struct file_info *fi, void __user * uaddr) +{ + struct raw1394_iso_status stat; + + if (!fi->host) + return -EINVAL; + + if (copy_from_user(&stat, uaddr, sizeof(stat))) + return -EFAULT; + + fi->iso_handle = hpsb_iso_xmit_init(fi->host, + stat.config.data_buf_size, + stat.config.buf_packets, + stat.config.channel, + stat.config.speed, + stat.config.irq_interval, + rawiso_activity_cb); + if (!fi->iso_handle) + return -ENOMEM; + + fi->iso_state = RAW1394_ISO_XMIT; + + raw1394_iso_fill_status(fi->iso_handle, &stat); + if (copy_to_user(uaddr, &stat, sizeof(stat))) + return -EFAULT; + + /* queue an event to get things started */ + rawiso_activity_cb(fi->iso_handle); + + return 0; +} + +static int raw1394_iso_recv_init(struct file_info *fi, void __user * uaddr) +{ + struct raw1394_iso_status stat; + + if (!fi->host) + return -EINVAL; + + if (copy_from_user(&stat, uaddr, sizeof(stat))) + return -EFAULT; + + fi->iso_handle = hpsb_iso_recv_init(fi->host, + stat.config.data_buf_size, + stat.config.buf_packets, + stat.config.channel, + stat.config.dma_mode, + stat.config.irq_interval, + rawiso_activity_cb); + if (!fi->iso_handle) + return -ENOMEM; + + fi->iso_state = RAW1394_ISO_RECV; + + raw1394_iso_fill_status(fi->iso_handle, &stat); + if (copy_to_user(uaddr, &stat, sizeof(stat))) + return -EFAULT; + return 0; +} + +static int raw1394_iso_get_status(struct file_info *fi, void __user * uaddr) +{ + struct raw1394_iso_status stat; + struct hpsb_iso *iso = fi->iso_handle; + + raw1394_iso_fill_status(fi->iso_handle, &stat); + if (copy_to_user(uaddr, &stat, sizeof(stat))) + return -EFAULT; + + /* reset overflow counter */ + atomic_set(&iso->overflows, 0); + /* reset skip counter */ + atomic_set(&iso->skips, 0); + + return 0; +} + +/* copy N packet_infos out of the ringbuffer into user-supplied array */ +static int raw1394_iso_recv_packets(struct file_info *fi, void __user * uaddr) +{ + struct raw1394_iso_packets upackets; + unsigned int packet = fi->iso_handle->first_packet; + int i; + + if (copy_from_user(&upackets, uaddr, sizeof(upackets))) + return -EFAULT; + + if (upackets.n_packets > hpsb_iso_n_ready(fi->iso_handle)) + return -EINVAL; + + /* ensure user-supplied buffer is accessible and big enough */ + if (!access_ok(VERIFY_WRITE, upackets.infos, + upackets.n_packets * + sizeof(struct raw1394_iso_packet_info))) + return -EFAULT; + + /* copy the packet_infos out */ + for (i = 0; i < upackets.n_packets; i++) { + if (__copy_to_user(&upackets.infos[i], + &fi->iso_handle->infos[packet], + sizeof(struct raw1394_iso_packet_info))) + return -EFAULT; + + packet = (packet + 1) % fi->iso_handle->buf_packets; + } + + return 0; +} + +/* copy N packet_infos from user to ringbuffer, and queue them for transmission */ +static int raw1394_iso_send_packets(struct file_info *fi, void __user * uaddr) +{ + struct raw1394_iso_packets upackets; + int i, rv; + + if (copy_from_user(&upackets, uaddr, sizeof(upackets))) + return -EFAULT; + + if (upackets.n_packets >= fi->iso_handle->buf_packets) + return -EINVAL; + + if (upackets.n_packets >= hpsb_iso_n_ready(fi->iso_handle)) + return -EAGAIN; + + /* ensure user-supplied buffer is accessible and big enough */ + if (!access_ok(VERIFY_READ, upackets.infos, + upackets.n_packets * + sizeof(struct raw1394_iso_packet_info))) + return -EFAULT; + + /* copy the infos structs in and queue the packets */ + for (i = 0; i < upackets.n_packets; i++) { + struct raw1394_iso_packet_info info; + + if (__copy_from_user(&info, &upackets.infos[i], + sizeof(struct raw1394_iso_packet_info))) + return -EFAULT; + + rv = hpsb_iso_xmit_queue_packet(fi->iso_handle, info.offset, + info.len, info.tag, info.sy); + if (rv) + return rv; + } + + return 0; +} + +static void raw1394_iso_shutdown(struct file_info *fi) +{ + if (fi->iso_handle) + hpsb_iso_shutdown(fi->iso_handle); + + fi->iso_handle = NULL; + fi->iso_state = RAW1394_ISO_INACTIVE; +} + +static int raw1394_read_cycle_timer(struct file_info *fi, void __user * uaddr) +{ + struct raw1394_cycle_timer ct; + int err; + + err = hpsb_read_cycle_timer(fi->host, &ct.cycle_timer, &ct.local_time); + if (!err) + if (copy_to_user(uaddr, &ct, sizeof(ct))) + err = -EFAULT; + return err; +} + +/* mmap the rawiso xmit/recv buffer */ +static int raw1394_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct file_info *fi = file->private_data; + int ret; + + if (!mutex_trylock(&fi->state_mutex)) + return -EAGAIN; + + if (fi->iso_state == RAW1394_ISO_INACTIVE) + ret = -EINVAL; + else + ret = dma_region_mmap(&fi->iso_handle->data_buf, file, vma); + + mutex_unlock(&fi->state_mutex); + + return ret; +} + +static long raw1394_ioctl_inactive(struct file_info *fi, unsigned int cmd, + void __user *argp) +{ + switch (cmd) { + case RAW1394_IOC_ISO_XMIT_INIT: + return raw1394_iso_xmit_init(fi, argp); + case RAW1394_IOC_ISO_RECV_INIT: + return raw1394_iso_recv_init(fi, argp); + default: + return -EINVAL; + } +} + +static long raw1394_ioctl_recv(struct file_info *fi, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + + switch (cmd) { + case RAW1394_IOC_ISO_RECV_START:{ + int args[3]; + + if (copy_from_user(&args[0], argp, sizeof(args))) + return -EFAULT; + return hpsb_iso_recv_start(fi->iso_handle, + args[0], args[1], args[2]); + } + case RAW1394_IOC_ISO_XMIT_RECV_STOP: + hpsb_iso_stop(fi->iso_handle); + return 0; + case RAW1394_IOC_ISO_RECV_LISTEN_CHANNEL: + return hpsb_iso_recv_listen_channel(fi->iso_handle, arg); + case RAW1394_IOC_ISO_RECV_UNLISTEN_CHANNEL: + return hpsb_iso_recv_unlisten_channel(fi->iso_handle, arg); + case RAW1394_IOC_ISO_RECV_SET_CHANNEL_MASK:{ + u64 mask; + + if (copy_from_user(&mask, argp, sizeof(mask))) + return -EFAULT; + return hpsb_iso_recv_set_channel_mask(fi->iso_handle, + mask); + } + case RAW1394_IOC_ISO_GET_STATUS: + return raw1394_iso_get_status(fi, argp); + case RAW1394_IOC_ISO_RECV_PACKETS: + return raw1394_iso_recv_packets(fi, argp); + case RAW1394_IOC_ISO_RECV_RELEASE_PACKETS: + return hpsb_iso_recv_release_packets(fi->iso_handle, arg); + case RAW1394_IOC_ISO_RECV_FLUSH: + return hpsb_iso_recv_flush(fi->iso_handle); + case RAW1394_IOC_ISO_SHUTDOWN: + raw1394_iso_shutdown(fi); + return 0; + case RAW1394_IOC_ISO_QUEUE_ACTIVITY: + queue_rawiso_event(fi); + return 0; + default: + return -EINVAL; + } +} + +static long raw1394_ioctl_xmit(struct file_info *fi, unsigned int cmd, + void __user *argp) +{ + switch (cmd) { + case RAW1394_IOC_ISO_XMIT_START:{ + int args[2]; + + if (copy_from_user(&args[0], argp, sizeof(args))) + return -EFAULT; + return hpsb_iso_xmit_start(fi->iso_handle, + args[0], args[1]); + } + case RAW1394_IOC_ISO_XMIT_SYNC: + return hpsb_iso_xmit_sync(fi->iso_handle); + case RAW1394_IOC_ISO_XMIT_RECV_STOP: + hpsb_iso_stop(fi->iso_handle); + return 0; + case RAW1394_IOC_ISO_GET_STATUS: + return raw1394_iso_get_status(fi, argp); + case RAW1394_IOC_ISO_XMIT_PACKETS: + return raw1394_iso_send_packets(fi, argp); + case RAW1394_IOC_ISO_SHUTDOWN: + raw1394_iso_shutdown(fi); + return 0; + case RAW1394_IOC_ISO_QUEUE_ACTIVITY: + queue_rawiso_event(fi); + return 0; + default: + return -EINVAL; + } +} + +/* ioctl is only used for rawiso operations */ +static long raw1394_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct file_info *fi = file->private_data; + void __user *argp = (void __user *)arg; + long ret; + + /* state-independent commands */ + switch(cmd) { + case RAW1394_IOC_GET_CYCLE_TIMER: + return raw1394_read_cycle_timer(fi, argp); + default: + break; + } + + if (!mutex_trylock(&fi->state_mutex)) + return -EAGAIN; + + switch (fi->iso_state) { + case RAW1394_ISO_INACTIVE: + ret = raw1394_ioctl_inactive(fi, cmd, argp); + break; + case RAW1394_ISO_RECV: + ret = raw1394_ioctl_recv(fi, cmd, arg); + break; + case RAW1394_ISO_XMIT: + ret = raw1394_ioctl_xmit(fi, cmd, argp); + break; + default: + ret = -EINVAL; + break; + } + + mutex_unlock(&fi->state_mutex); + + return ret; +} + +#ifdef CONFIG_COMPAT +struct raw1394_iso_packets32 { + __u32 n_packets; + compat_uptr_t infos; +} __attribute__((packed)); + +struct raw1394_cycle_timer32 { + __u32 cycle_timer; + __u64 local_time; +} +#if defined(CONFIG_X86_64) || defined(CONFIG_IA64) +__attribute__((packed)) +#endif +; + +#define RAW1394_IOC_ISO_RECV_PACKETS32 \ + _IOW ('#', 0x25, struct raw1394_iso_packets32) +#define RAW1394_IOC_ISO_XMIT_PACKETS32 \ + _IOW ('#', 0x27, struct raw1394_iso_packets32) +#define RAW1394_IOC_GET_CYCLE_TIMER32 \ + _IOR ('#', 0x30, struct raw1394_cycle_timer32) + +static long raw1394_iso_xmit_recv_packets32(struct file *file, unsigned int cmd, + struct raw1394_iso_packets32 __user *arg) +{ + compat_uptr_t infos32; + void __user *infos; + long err = -EFAULT; + struct raw1394_iso_packets __user *dst = compat_alloc_user_space(sizeof(struct raw1394_iso_packets)); + + if (!copy_in_user(&dst->n_packets, &arg->n_packets, sizeof arg->n_packets) && + !copy_from_user(&infos32, &arg->infos, sizeof infos32)) { + infos = compat_ptr(infos32); + if (!copy_to_user(&dst->infos, &infos, sizeof infos)) + err = raw1394_ioctl(file, cmd, (unsigned long)dst); + } + return err; +} + +static long raw1394_read_cycle_timer32(struct file_info *fi, void __user * uaddr) +{ + struct raw1394_cycle_timer32 ct; + int err; + + err = hpsb_read_cycle_timer(fi->host, &ct.cycle_timer, &ct.local_time); + if (!err) + if (copy_to_user(uaddr, &ct, sizeof(ct))) + err = -EFAULT; + return err; +} + +static long raw1394_compat_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct file_info *fi = file->private_data; + void __user *argp = (void __user *)arg; + long err; + + switch (cmd) { + /* These requests have same format as long as 'int' has same size. */ + case RAW1394_IOC_ISO_RECV_INIT: + case RAW1394_IOC_ISO_RECV_START: + case RAW1394_IOC_ISO_RECV_LISTEN_CHANNEL: + case RAW1394_IOC_ISO_RECV_UNLISTEN_CHANNEL: + case RAW1394_IOC_ISO_RECV_SET_CHANNEL_MASK: + case RAW1394_IOC_ISO_RECV_RELEASE_PACKETS: + case RAW1394_IOC_ISO_RECV_FLUSH: + case RAW1394_IOC_ISO_XMIT_RECV_STOP: + case RAW1394_IOC_ISO_XMIT_INIT: + case RAW1394_IOC_ISO_XMIT_START: + case RAW1394_IOC_ISO_XMIT_SYNC: + case RAW1394_IOC_ISO_GET_STATUS: + case RAW1394_IOC_ISO_SHUTDOWN: + case RAW1394_IOC_ISO_QUEUE_ACTIVITY: + err = raw1394_ioctl(file, cmd, arg); + break; + /* These request have different format. */ + case RAW1394_IOC_ISO_RECV_PACKETS32: + err = raw1394_iso_xmit_recv_packets32(file, RAW1394_IOC_ISO_RECV_PACKETS, argp); + break; + case RAW1394_IOC_ISO_XMIT_PACKETS32: + err = raw1394_iso_xmit_recv_packets32(file, RAW1394_IOC_ISO_XMIT_PACKETS, argp); + break; + case RAW1394_IOC_GET_CYCLE_TIMER32: + err = raw1394_read_cycle_timer32(fi, argp); + break; + default: + err = -EINVAL; + break; + } + + return err; +} +#endif + +static unsigned int raw1394_poll(struct file *file, poll_table * pt) +{ + struct file_info *fi = file->private_data; + unsigned int mask = POLLOUT | POLLWRNORM; + unsigned long flags; + + poll_wait(file, &fi->wait_complete, pt); + + spin_lock_irqsave(&fi->reqlists_lock, flags); + if (!list_empty(&fi->req_complete)) { + mask |= POLLIN | POLLRDNORM; + } + spin_unlock_irqrestore(&fi->reqlists_lock, flags); + + return mask; +} + +static int raw1394_open(struct inode *inode, struct file *file) +{ + struct file_info *fi; + + fi = kzalloc(sizeof(*fi), GFP_KERNEL); + if (!fi) + return -ENOMEM; + + fi->notification = (u8) RAW1394_NOTIFY_ON; /* busreset notification */ + + INIT_LIST_HEAD(&fi->list); + mutex_init(&fi->state_mutex); + fi->state = opened; + INIT_LIST_HEAD(&fi->req_pending); + INIT_LIST_HEAD(&fi->req_complete); + spin_lock_init(&fi->reqlists_lock); + init_waitqueue_head(&fi->wait_complete); + INIT_LIST_HEAD(&fi->addr_list); + + file->private_data = fi; + + return 0; +} + +static int raw1394_release(struct inode *inode, struct file *file) +{ + struct file_info *fi = file->private_data; + struct list_head *lh; + struct pending_request *req; + int i, fail; + int retval = 0; + struct list_head *entry; + struct arm_addr *addr = NULL; + struct host_info *hi; + struct file_info *fi_hlp = NULL; + struct arm_addr *arm_addr = NULL; + int another_host; + int csr_mod = 0; + unsigned long flags; + + if (fi->iso_state != RAW1394_ISO_INACTIVE) + raw1394_iso_shutdown(fi); + + spin_lock_irqsave(&host_info_lock, flags); + + fail = 0; + /* set address-entries invalid */ + + while (!list_empty(&fi->addr_list)) { + another_host = 0; + lh = fi->addr_list.next; + addr = list_entry(lh, struct arm_addr, addr_list); + /* another host with valid address-entry containing + same addressrange? */ + list_for_each_entry(hi, &host_info_list, list) { + if (hi->host != fi->host) { + list_for_each_entry(fi_hlp, &hi->file_info_list, + list) { + entry = fi_hlp->addr_list.next; + while (entry != &(fi_hlp->addr_list)) { + arm_addr = list_entry(entry, struct + arm_addr, + addr_list); + if (arm_addr->start == + addr->start) { + DBGMSG + ("raw1394_release: " + "another host ownes " + "same addressrange"); + another_host = 1; + break; + } + entry = entry->next; + } + if (another_host) { + break; + } + } + } + } + if (!another_host) { + DBGMSG("raw1394_release: call hpsb_arm_unregister"); + retval = + hpsb_unregister_addrspace(&raw1394_highlevel, + fi->host, addr->start); + if (!retval) { + ++fail; + printk(KERN_ERR + "raw1394_release arm_Unregister failed\n"); + } + } + DBGMSG("raw1394_release: delete addr_entry from list"); + list_del(&addr->addr_list); + vfree(addr->addr_space_buffer); + kfree(addr); + } /* while */ + spin_unlock_irqrestore(&host_info_lock, flags); + if (fail > 0) { + printk(KERN_ERR "raw1394: during addr_list-release " + "error(s) occurred \n"); + } + + for (;;) { + /* This locked section guarantees that neither + * complete nor pending requests exist once i!=0 */ + spin_lock_irqsave(&fi->reqlists_lock, flags); + while ((req = __next_complete_req(fi))) + free_pending_request(req); + + i = list_empty(&fi->req_pending); + spin_unlock_irqrestore(&fi->reqlists_lock, flags); + + if (i) + break; + /* + * Sleep until more requests can be freed. + * + * NB: We call the macro wait_event() 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() won't evaluate the condition again after that. + */ + wait_event(fi->wait_complete, (req = next_complete_req(fi))); + free_pending_request(req); + } + + /* Remove any sub-trees left by user space programs */ + for (i = 0; i < RAW1394_MAX_USER_CSR_DIRS; i++) { + struct csr1212_dentry *dentry; + if (!fi->csr1212_dirs[i]) + continue; + for (dentry = + fi->csr1212_dirs[i]->value.directory.dentries_head; dentry; + dentry = dentry->next) { + csr1212_detach_keyval_from_directory(fi->host->csr.rom-> + root_kv, + dentry->kv); + } + csr1212_release_keyval(fi->csr1212_dirs[i]); + fi->csr1212_dirs[i] = NULL; + csr_mod = 1; + } + + if ((csr_mod || fi->cfgrom_upd) + && hpsb_update_config_rom_image(fi->host) < 0) + HPSB_ERR + ("Failed to generate Configuration ROM image for host %d", + fi->host->id); + + if (fi->state == connected) { + spin_lock_irqsave(&host_info_lock, flags); + list_del(&fi->list); + spin_unlock_irqrestore(&host_info_lock, flags); + + put_device(&fi->host->device); + } + + spin_lock_irqsave(&host_info_lock, flags); + if (fi->host) + module_put(fi->host->driver->owner); + spin_unlock_irqrestore(&host_info_lock, flags); + + kfree(fi); + + return 0; +} + +/*** HOTPLUG STUFF **********************************************************/ +/* + * Export information about protocols/devices supported by this driver. + */ +#ifdef MODULE +static struct ieee1394_device_id raw1394_id_table[] = { + { + .match_flags = IEEE1394_MATCH_SPECIFIER_ID | IEEE1394_MATCH_VERSION, + .specifier_id = AVC_UNIT_SPEC_ID_ENTRY & 0xffffff, + .version = AVC_SW_VERSION_ENTRY & 0xffffff}, + { + .match_flags = IEEE1394_MATCH_SPECIFIER_ID | IEEE1394_MATCH_VERSION, + .specifier_id = CAMERA_UNIT_SPEC_ID_ENTRY & 0xffffff, + .version = CAMERA_SW_VERSION_ENTRY & 0xffffff}, + { + .match_flags = IEEE1394_MATCH_SPECIFIER_ID | IEEE1394_MATCH_VERSION, + .specifier_id = CAMERA_UNIT_SPEC_ID_ENTRY & 0xffffff, + .version = (CAMERA_SW_VERSION_ENTRY + 1) & 0xffffff}, + { + .match_flags = IEEE1394_MATCH_SPECIFIER_ID | IEEE1394_MATCH_VERSION, + .specifier_id = CAMERA_UNIT_SPEC_ID_ENTRY & 0xffffff, + .version = (CAMERA_SW_VERSION_ENTRY + 2) & 0xffffff}, + {} +}; + +MODULE_DEVICE_TABLE(ieee1394, raw1394_id_table); +#endif /* MODULE */ + +static struct hpsb_protocol_driver raw1394_driver = { + .name = "raw1394", +}; + +/******************************************************************************/ + +static struct hpsb_highlevel raw1394_highlevel = { + .name = RAW1394_DEVICE_NAME, + .add_host = add_host, + .remove_host = remove_host, + .host_reset = host_reset, + .fcp_request = fcp_request, +}; + +static struct cdev raw1394_cdev; +static const struct file_operations raw1394_fops = { + .owner = THIS_MODULE, + .read = raw1394_read, + .write = raw1394_write, + .mmap = raw1394_mmap, + .unlocked_ioctl = raw1394_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = raw1394_compat_ioctl, +#endif + .poll = raw1394_poll, + .open = raw1394_open, + .release = raw1394_release, +}; + +static int __init init_raw1394(void) +{ + int ret = 0; + + hpsb_register_highlevel(&raw1394_highlevel); + + if (IS_ERR + (device_create(hpsb_protocol_class, NULL, + MKDEV(IEEE1394_MAJOR, + IEEE1394_MINOR_BLOCK_RAW1394 * 16), + NULL, RAW1394_DEVICE_NAME))) { + ret = -EFAULT; + goto out_unreg; + } + + cdev_init(&raw1394_cdev, &raw1394_fops); + raw1394_cdev.owner = THIS_MODULE; + ret = cdev_add(&raw1394_cdev, IEEE1394_RAW1394_DEV, 1); + if (ret) { + HPSB_ERR("raw1394 failed to register minor device block"); + goto out_dev; + } + + HPSB_INFO("raw1394: /dev/%s device initialized", RAW1394_DEVICE_NAME); + + ret = hpsb_register_protocol(&raw1394_driver); + if (ret) { + HPSB_ERR("raw1394: failed to register protocol"); + cdev_del(&raw1394_cdev); + goto out_dev; + } + + goto out; + + out_dev: + device_destroy(hpsb_protocol_class, + MKDEV(IEEE1394_MAJOR, + IEEE1394_MINOR_BLOCK_RAW1394 * 16)); + out_unreg: + hpsb_unregister_highlevel(&raw1394_highlevel); + out: + return ret; +} + +static void __exit cleanup_raw1394(void) +{ + device_destroy(hpsb_protocol_class, + MKDEV(IEEE1394_MAJOR, + IEEE1394_MINOR_BLOCK_RAW1394 * 16)); + cdev_del(&raw1394_cdev); + hpsb_unregister_highlevel(&raw1394_highlevel); + hpsb_unregister_protocol(&raw1394_driver); +} + +module_init(init_raw1394); +module_exit(cleanup_raw1394); +MODULE_LICENSE("GPL"); diff --git a/drivers/ieee1394/raw1394.h b/drivers/ieee1394/raw1394.h new file mode 100644 index 0000000..963ac20 --- /dev/null +++ b/drivers/ieee1394/raw1394.h @@ -0,0 +1,191 @@ +#ifndef IEEE1394_RAW1394_H +#define IEEE1394_RAW1394_H + +/* header for the raw1394 API that is exported to user-space */ + +#define RAW1394_KERNELAPI_VERSION 4 + +/* state: opened */ +#define RAW1394_REQ_INITIALIZE 1 + +/* state: initialized */ +#define RAW1394_REQ_LIST_CARDS 2 +#define RAW1394_REQ_SET_CARD 3 + +/* state: connected */ +#define RAW1394_REQ_ASYNC_READ 100 +#define RAW1394_REQ_ASYNC_WRITE 101 +#define RAW1394_REQ_LOCK 102 +#define RAW1394_REQ_LOCK64 103 +#define RAW1394_REQ_ISO_SEND 104 /* removed ABI, now a no-op */ +#define RAW1394_REQ_ASYNC_SEND 105 +#define RAW1394_REQ_ASYNC_STREAM 106 + +#define RAW1394_REQ_ISO_LISTEN 200 /* removed ABI, now a no-op */ +#define RAW1394_REQ_FCP_LISTEN 201 +#define RAW1394_REQ_RESET_BUS 202 +#define RAW1394_REQ_GET_ROM 203 +#define RAW1394_REQ_UPDATE_ROM 204 +#define RAW1394_REQ_ECHO 205 +#define RAW1394_REQ_MODIFY_ROM 206 + +#define RAW1394_REQ_ARM_REGISTER 300 +#define RAW1394_REQ_ARM_UNREGISTER 301 +#define RAW1394_REQ_ARM_SET_BUF 302 +#define RAW1394_REQ_ARM_GET_BUF 303 + +#define RAW1394_REQ_RESET_NOTIFY 400 + +#define RAW1394_REQ_PHYPACKET 500 + +/* kernel to user */ +#define RAW1394_REQ_BUS_RESET 10000 +#define RAW1394_REQ_ISO_RECEIVE 10001 +#define RAW1394_REQ_FCP_REQUEST 10002 +#define RAW1394_REQ_ARM 10003 +#define RAW1394_REQ_RAWISO_ACTIVITY 10004 + +/* error codes */ +#define RAW1394_ERROR_NONE 0 +#define RAW1394_ERROR_COMPAT (-1001) +#define RAW1394_ERROR_STATE_ORDER (-1002) +#define RAW1394_ERROR_GENERATION (-1003) +#define RAW1394_ERROR_INVALID_ARG (-1004) +#define RAW1394_ERROR_MEMFAULT (-1005) +#define RAW1394_ERROR_ALREADY (-1006) + +#define RAW1394_ERROR_EXCESSIVE (-1020) +#define RAW1394_ERROR_UNTIDY_LEN (-1021) + +#define RAW1394_ERROR_SEND_ERROR (-1100) +#define RAW1394_ERROR_ABORTED (-1101) +#define RAW1394_ERROR_TIMEOUT (-1102) + +/* arm_codes */ +#define ARM_READ 1 +#define ARM_WRITE 2 +#define ARM_LOCK 4 + +#define RAW1394_LONG_RESET 0 +#define RAW1394_SHORT_RESET 1 + +/* busresetnotify ... */ +#define RAW1394_NOTIFY_OFF 0 +#define RAW1394_NOTIFY_ON 1 + +#include <asm/types.h> + +struct raw1394_request { + __u32 type; + __s32 error; + __u32 misc; + + __u32 generation; + __u32 length; + + __u64 address; + + __u64 tag; + + __u64 sendb; + __u64 recvb; +}; + +struct raw1394_khost_list { + __u32 nodes; + __u8 name[32]; +}; + +typedef struct arm_request { + __u16 destination_nodeid; + __u16 source_nodeid; + __u64 destination_offset; + __u8 tlabel; + __u8 tcode; + __u8 extended_transaction_code; + __u32 generation; + __u16 buffer_length; + __u8 __user *buffer; +} *arm_request_t; + +typedef struct arm_response { + __s32 response_code; + __u16 buffer_length; + __u8 __user *buffer; +} *arm_response_t; + +typedef struct arm_request_response { + struct arm_request __user *request; + struct arm_response __user *response; +} *arm_request_response_t; + +/* rawiso API */ +#include "ieee1394-ioctl.h" + +/* per-packet metadata embedded in the ringbuffer */ +/* must be identical to hpsb_iso_packet_info in iso.h! */ +struct raw1394_iso_packet_info { + __u32 offset; + __u16 len; + __u16 cycle; /* recv only */ + __u8 channel; /* recv only */ + __u8 tag; + __u8 sy; +}; + +/* argument for RAW1394_ISO_RECV/XMIT_PACKETS ioctls */ +struct raw1394_iso_packets { + __u32 n_packets; + struct raw1394_iso_packet_info __user *infos; +}; + +struct raw1394_iso_config { + /* size of packet data buffer, in bytes (will be rounded up to PAGE_SIZE) */ + __u32 data_buf_size; + + /* # of packets to buffer */ + __u32 buf_packets; + + /* iso channel (set to -1 for multi-channel recv) */ + __s32 channel; + + /* xmit only - iso transmission speed */ + __u8 speed; + + /* The mode of the dma when receiving iso data. Must be supported by chip */ + __u8 dma_mode; + + /* max. latency of buffer, in packets (-1 if you don't care) */ + __s32 irq_interval; +}; + +/* argument to RAW1394_ISO_XMIT/RECV_INIT and RAW1394_ISO_GET_STATUS */ +struct raw1394_iso_status { + /* current settings */ + struct raw1394_iso_config config; + + /* number of packets waiting to be filled with data (ISO transmission) + or containing data received (ISO reception) */ + __u32 n_packets; + + /* approximate number of packets dropped due to overflow or + underflow of the packet buffer (a value of zero guarantees + that no packets have been dropped) */ + __u32 overflows; + + /* cycle number at which next packet will be transmitted; + -1 if not known */ + __s16 xmit_cycle; +}; + +/* argument to RAW1394_IOC_GET_CYCLE_TIMER ioctl */ +struct raw1394_cycle_timer { + /* contents of Isochronous Cycle Timer register, + as in OHCI 1.1 clause 5.13 (also with non-OHCI hosts) */ + __u32 cycle_timer; + + /* local time in microseconds since Epoch, + simultaneously read with cycle timer */ + __u64 local_time; +}; +#endif /* IEEE1394_RAW1394_H */ diff --git a/drivers/ieee1394/sbp2.c b/drivers/ieee1394/sbp2.c new file mode 100644 index 0000000..a01b43e --- /dev/null +++ b/drivers/ieee1394/sbp2.c @@ -0,0 +1,2141 @@ +/* + * sbp2.c - SBP-2 protocol driver for IEEE-1394 + * + * Copyright (C) 2000 James Goodwin, Filanet Corporation (www.filanet.com) + * jamesg@filanet.com (JSG) + * + * Copyright (C) 2003 Ben Collins <bcollins@debian.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * Brief Description: + * + * This driver implements the Serial Bus Protocol 2 (SBP-2) over IEEE-1394 + * under Linux. The SBP-2 driver is implemented as an IEEE-1394 high-level + * driver. It also registers as a SCSI lower-level driver in order to accept + * SCSI commands for transport using SBP-2. + * + * You may access any attached SBP-2 (usually storage devices) as regular + * SCSI devices. E.g. mount /dev/sda1, fdisk, mkfs, etc.. + * + * See http://www.t10.org/drafts.htm#sbp2 for the final draft of the SBP-2 + * specification and for where to purchase the official standard. + * + * TODO: + * - look into possible improvements of the SCSI error handlers + * - handle Unit_Characteristics.mgt_ORB_timeout and .ORB_size + * - handle Logical_Unit_Number.ordered + * - handle src == 1 in status blocks + * - reimplement the DMA mapping in absence of physical DMA so that + * bus_to_virt is no longer required + * - debug the handling of absent physical DMA + * - replace CONFIG_IEEE1394_SBP2_PHYS_DMA by automatic detection + * (this is easy but depends on the previous two TODO items) + * - make the parameter serialize_io configurable per device + * - move all requests to fetch agent registers into non-atomic context, + * replace all usages of sbp2util_node_write_no_wait by true transactions + * Grep for inline FIXME comments below. + */ + +#include <linux/blkdev.h> +#include <linux/compiler.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/gfp.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/stringify.h> +#include <linux/types.h> +#include <linux/wait.h> +#include <linux/workqueue.h> +#include <linux/scatterlist.h> + +#include <asm/byteorder.h> +#include <asm/errno.h> +#include <asm/param.h> +#include <asm/system.h> +#include <asm/types.h> + +#ifdef CONFIG_IEEE1394_SBP2_PHYS_DMA +#include <asm/io.h> /* for bus_to_virt */ +#endif + +#include <scsi/scsi.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_dbg.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_host.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 "nodemgr.h" +#include "sbp2.h" + +/* + * Module load parameter definitions + */ + +/* + * Change max_speed on module load if you have a bad IEEE-1394 + * controller that has trouble running 2KB packets at 400mb. + * + * NOTE: On certain OHCI parts I have seen short packets on async transmit + * (probably due to PCI latency/throughput issues with the part). You can + * bump down the speed if you are running into problems. + */ +static int sbp2_max_speed = IEEE1394_SPEED_MAX; +module_param_named(max_speed, sbp2_max_speed, int, 0644); +MODULE_PARM_DESC(max_speed, "Force max speed " + "(3 = 800Mb/s, 2 = 400Mb/s, 1 = 200Mb/s, 0 = 100Mb/s)"); + +/* + * Set serialize_io to 0 or N to use dynamically appended lists of command ORBs. + * This is and always has been buggy in multiple subtle ways. See above TODOs. + */ +static int sbp2_serialize_io = 1; +module_param_named(serialize_io, sbp2_serialize_io, bool, 0444); +MODULE_PARM_DESC(serialize_io, "Serialize requests coming from SCSI drivers " + "(default = Y, faster but buggy = N)"); + +/* + * Adjust max_sectors if you'd like to influence how many sectors each SCSI + * command can transfer at most. Please note that some older SBP-2 bridge + * chips are broken for transfers greater or equal to 128KB, therefore + * max_sectors used to be a safe 255 sectors for many years. We now have a + * default of 0 here which means that we let the SCSI stack choose a limit. + * + * The SBP2_WORKAROUND_128K_MAX_TRANS flag, if set either in the workarounds + * module parameter or in the sbp2_workarounds_table[], will override the + * value of max_sectors. We should use sbp2_workarounds_table[] to cover any + * bridge chip which becomes known to need the 255 sectors limit. + */ +static int sbp2_max_sectors; +module_param_named(max_sectors, sbp2_max_sectors, int, 0444); +MODULE_PARM_DESC(max_sectors, "Change max sectors per I/O supported " + "(default = 0 = use SCSI stack's default)"); + +/* + * Exclusive login to sbp2 device? In most cases, the sbp2 driver should + * do an exclusive login, as it's generally unsafe to have two hosts + * talking to a single sbp2 device at the same time (filesystem coherency, + * etc.). If you're running an sbp2 device that supports multiple logins, + * and you're either running read-only filesystems or some sort of special + * filesystem supporting multiple hosts, e.g. OpenGFS, Oracle Cluster + * File System, or Lustre, then set exclusive_login to zero. + * + * So far only bridges from Oxford Semiconductor are known to support + * concurrent logins. Depending on firmware, four or two concurrent logins + * are possible on OXFW911 and newer Oxsemi bridges. + */ +static int sbp2_exclusive_login = 1; +module_param_named(exclusive_login, sbp2_exclusive_login, bool, 0644); +MODULE_PARM_DESC(exclusive_login, "Exclusive login to sbp2 device " + "(default = Y, use N for concurrent initiators)"); + +/* + * If any of the following workarounds is required for your device to work, + * please submit the kernel messages logged by sbp2 to the linux1394-devel + * mailing list. + * + * - 128kB max transfer + * Limit transfer size. Necessary for some old bridges. + * + * - 36 byte inquiry + * When scsi_mod probes the device, let the inquiry command look like that + * from MS Windows. + * + * - skip mode page 8 + * Suppress sending of mode_sense for mode page 8 if the device pretends to + * support the SCSI Primary Block commands instead of Reduced Block Commands. + * + * - fix capacity + * Tell sd_mod to correct the last sector number reported by read_capacity. + * Avoids access beyond actual disk limits on devices with an off-by-one bug. + * Don't use this with devices which don't have this bug. + * + * - delay inquiry + * Wait extra SBP2_INQUIRY_DELAY seconds after login before SCSI inquiry. + * + * - power condition + * Set the power condition field in the START STOP UNIT commands sent by + * sd_mod on suspend, resume, and shutdown (if manage_start_stop is on). + * Some disks need this to spin down or to resume properly. + * + * - override internal blacklist + * Instead of adding to the built-in blacklist, use only the workarounds + * specified in the module load parameter. + * Useful if a blacklist entry interfered with a non-broken device. + */ +static int sbp2_default_workarounds; +module_param_named(workarounds, sbp2_default_workarounds, int, 0644); +MODULE_PARM_DESC(workarounds, "Work around device bugs (default = 0" + ", 128kB max transfer = " __stringify(SBP2_WORKAROUND_128K_MAX_TRANS) + ", 36 byte inquiry = " __stringify(SBP2_WORKAROUND_INQUIRY_36) + ", skip mode page 8 = " __stringify(SBP2_WORKAROUND_MODE_SENSE_8) + ", fix capacity = " __stringify(SBP2_WORKAROUND_FIX_CAPACITY) + ", delay inquiry = " __stringify(SBP2_WORKAROUND_DELAY_INQUIRY) + ", set power condition in start stop unit = " + __stringify(SBP2_WORKAROUND_POWER_CONDITION) + ", override internal blacklist = " __stringify(SBP2_WORKAROUND_OVERRIDE) + ", or a combination)"); + +/* + * This influences the format of the sysfs attribute + * /sys/bus/scsi/devices/.../ieee1394_id. + * + * The default format is like in older kernels: %016Lx:%d:%d + * It contains the target's EUI-64, a number given to the logical unit by + * the ieee1394 driver's nodemgr (starting at 0), and the LUN. + * + * The long format is: %016Lx:%06x:%04x + * It contains the target's EUI-64, the unit directory's directory_ID as per + * IEEE 1212 clause 7.7.19, and the LUN. This format comes closest to the + * format of SBP(-3) target port and logical unit identifier as per SAM (SCSI + * Architecture Model) rev.2 to 4 annex A. Therefore and because it is + * independent of the implementation of the ieee1394 nodemgr, the longer format + * is recommended for future use. + */ +static int sbp2_long_sysfs_ieee1394_id; +module_param_named(long_ieee1394_id, sbp2_long_sysfs_ieee1394_id, bool, 0644); +MODULE_PARM_DESC(long_ieee1394_id, "8+3+2 bytes format of ieee1394_id in sysfs " + "(default = backwards-compatible = N, SAM-conforming = Y)"); + + +#define SBP2_INFO(fmt, args...) HPSB_INFO("sbp2: "fmt, ## args) +#define SBP2_ERR(fmt, args...) HPSB_ERR("sbp2: "fmt, ## args) + +/* + * Globals + */ +static void sbp2scsi_complete_all_commands(struct sbp2_lu *, u32); +static void sbp2scsi_complete_command(struct sbp2_lu *, u32, struct scsi_cmnd *, + void (*)(struct scsi_cmnd *)); +static struct sbp2_lu *sbp2_alloc_device(struct unit_directory *); +static int sbp2_start_device(struct sbp2_lu *); +static void sbp2_remove_device(struct sbp2_lu *); +static int sbp2_login_device(struct sbp2_lu *); +static int sbp2_reconnect_device(struct sbp2_lu *); +static int sbp2_logout_device(struct sbp2_lu *); +static void sbp2_host_reset(struct hpsb_host *); +static int sbp2_handle_status_write(struct hpsb_host *, int, int, quadlet_t *, + u64, size_t, u16); +static int sbp2_agent_reset(struct sbp2_lu *, int); +static void sbp2_parse_unit_directory(struct sbp2_lu *, + struct unit_directory *); +static int sbp2_set_busy_timeout(struct sbp2_lu *); +static int sbp2_max_speed_and_size(struct sbp2_lu *); + + +static const u8 sbp2_speedto_max_payload[] = { 0x7, 0x8, 0x9, 0xA, 0xB, 0xC }; + +static DEFINE_RWLOCK(sbp2_hi_logical_units_lock); + +static struct hpsb_highlevel sbp2_highlevel = { + .name = SBP2_DEVICE_NAME, + .host_reset = sbp2_host_reset, +}; + +static struct hpsb_address_ops sbp2_ops = { + .write = sbp2_handle_status_write +}; + +#ifdef CONFIG_IEEE1394_SBP2_PHYS_DMA +static int sbp2_handle_physdma_write(struct hpsb_host *, int, int, quadlet_t *, + u64, size_t, u16); +static int sbp2_handle_physdma_read(struct hpsb_host *, int, quadlet_t *, u64, + size_t, u16); + +static struct hpsb_address_ops sbp2_physdma_ops = { + .read = sbp2_handle_physdma_read, + .write = sbp2_handle_physdma_write, +}; +#endif + + +/* + * Interface to driver core and IEEE 1394 core + */ +static struct ieee1394_device_id sbp2_id_table[] = { + { + .match_flags = IEEE1394_MATCH_SPECIFIER_ID | IEEE1394_MATCH_VERSION, + .specifier_id = SBP2_UNIT_SPEC_ID_ENTRY & 0xffffff, + .version = SBP2_SW_VERSION_ENTRY & 0xffffff}, + {} +}; +MODULE_DEVICE_TABLE(ieee1394, sbp2_id_table); + +static int sbp2_probe(struct device *); +static int sbp2_remove(struct device *); +static int sbp2_update(struct unit_directory *); + +static struct hpsb_protocol_driver sbp2_driver = { + .name = SBP2_DEVICE_NAME, + .id_table = sbp2_id_table, + .update = sbp2_update, + .driver = { + .probe = sbp2_probe, + .remove = sbp2_remove, + }, +}; + + +/* + * Interface to SCSI core + */ +static int sbp2scsi_queuecommand(struct scsi_cmnd *, + void (*)(struct scsi_cmnd *)); +static int sbp2scsi_abort(struct scsi_cmnd *); +static int sbp2scsi_reset(struct scsi_cmnd *); +static int sbp2scsi_slave_alloc(struct scsi_device *); +static int sbp2scsi_slave_configure(struct scsi_device *); +static void sbp2scsi_slave_destroy(struct scsi_device *); +static ssize_t sbp2_sysfs_ieee1394_id_show(struct device *, + struct device_attribute *, char *); + +static DEVICE_ATTR(ieee1394_id, S_IRUGO, sbp2_sysfs_ieee1394_id_show, NULL); + +static struct device_attribute *sbp2_sysfs_sdev_attrs[] = { + &dev_attr_ieee1394_id, + NULL +}; + +static struct scsi_host_template sbp2_shost_template = { + .module = THIS_MODULE, + .name = "SBP-2 IEEE-1394", + .proc_name = SBP2_DEVICE_NAME, + .queuecommand = sbp2scsi_queuecommand, + .eh_abort_handler = sbp2scsi_abort, + .eh_device_reset_handler = sbp2scsi_reset, + .slave_alloc = sbp2scsi_slave_alloc, + .slave_configure = sbp2scsi_slave_configure, + .slave_destroy = sbp2scsi_slave_destroy, + .this_id = -1, + .sg_tablesize = SG_ALL, + .use_clustering = ENABLE_CLUSTERING, + .cmd_per_lun = SBP2_MAX_CMDS, + .can_queue = SBP2_MAX_CMDS, + .sdev_attrs = sbp2_sysfs_sdev_attrs, +}; + +/* for match-all entries in sbp2_workarounds_table */ +#define SBP2_ROM_VALUE_WILDCARD 0x1000000 + +/* + * List of devices with known bugs. + * + * The firmware_revision field, masked with 0xffff00, is the best indicator + * for the type of bridge chip of a device. It yields a few false positives + * but this did not break correctly behaving devices so far. + */ +static const struct { + u32 firmware_revision; + u32 model_id; + unsigned workarounds; +} sbp2_workarounds_table[] = { + /* DViCO Momobay CX-1 with TSB42AA9 bridge */ { + .firmware_revision = 0x002800, + .model_id = 0x001010, + .workarounds = SBP2_WORKAROUND_INQUIRY_36 | + SBP2_WORKAROUND_MODE_SENSE_8 | + SBP2_WORKAROUND_POWER_CONDITION, + }, + /* DViCO Momobay FX-3A with TSB42AA9A bridge */ { + .firmware_revision = 0x002800, + .model_id = 0x000000, + .workarounds = SBP2_WORKAROUND_DELAY_INQUIRY | + SBP2_WORKAROUND_POWER_CONDITION, + }, + /* Initio bridges, actually only needed for some older ones */ { + .firmware_revision = 0x000200, + .model_id = SBP2_ROM_VALUE_WILDCARD, + .workarounds = SBP2_WORKAROUND_INQUIRY_36, + }, + /* PL-3507 bridge with Prolific firmware */ { + .firmware_revision = 0x012800, + .model_id = SBP2_ROM_VALUE_WILDCARD, + .workarounds = SBP2_WORKAROUND_POWER_CONDITION, + }, + /* Symbios bridge */ { + .firmware_revision = 0xa0b800, + .model_id = SBP2_ROM_VALUE_WILDCARD, + .workarounds = SBP2_WORKAROUND_128K_MAX_TRANS, + }, + /* Datafab MD2-FW2 with Symbios/LSILogic SYM13FW500 bridge */ { + .firmware_revision = 0x002600, + .model_id = SBP2_ROM_VALUE_WILDCARD, + .workarounds = SBP2_WORKAROUND_128K_MAX_TRANS, + }, + /* + * iPod 2nd generation: needs 128k max transfer size workaround + * iPod 3rd generation: needs fix capacity workaround + */ + { + .firmware_revision = 0x0a2700, + .model_id = 0x000000, + .workarounds = SBP2_WORKAROUND_128K_MAX_TRANS | + SBP2_WORKAROUND_FIX_CAPACITY, + }, + /* iPod 4th generation */ { + .firmware_revision = 0x0a2700, + .model_id = 0x000021, + .workarounds = SBP2_WORKAROUND_FIX_CAPACITY, + }, + /* iPod mini */ { + .firmware_revision = 0x0a2700, + .model_id = 0x000022, + .workarounds = SBP2_WORKAROUND_FIX_CAPACITY, + }, + /* iPod mini */ { + .firmware_revision = 0x0a2700, + .model_id = 0x000023, + .workarounds = SBP2_WORKAROUND_FIX_CAPACITY, + }, + /* iPod Photo */ { + .firmware_revision = 0x0a2700, + .model_id = 0x00007e, + .workarounds = SBP2_WORKAROUND_FIX_CAPACITY, + } +}; + +/************************************** + * General utility functions + **************************************/ + +#ifndef __BIG_ENDIAN +/* + * Converts a buffer from be32 to cpu byte ordering. Length is in bytes. + */ +static inline void sbp2util_be32_to_cpu_buffer(void *buffer, int length) +{ + u32 *temp = buffer; + + for (length = (length >> 2); length--; ) + temp[length] = be32_to_cpu(temp[length]); +} + +/* + * Converts a buffer from cpu to be32 byte ordering. Length is in bytes. + */ +static inline void sbp2util_cpu_to_be32_buffer(void *buffer, int length) +{ + u32 *temp = buffer; + + for (length = (length >> 2); length--; ) + temp[length] = cpu_to_be32(temp[length]); +} +#else /* BIG_ENDIAN */ +/* Why waste the cpu cycles? */ +#define sbp2util_be32_to_cpu_buffer(x,y) do {} while (0) +#define sbp2util_cpu_to_be32_buffer(x,y) do {} while (0) +#endif + +static DECLARE_WAIT_QUEUE_HEAD(sbp2_access_wq); + +/* + * Waits for completion of an SBP-2 access request. + * Returns nonzero if timed out or prematurely interrupted. + */ +static int sbp2util_access_timeout(struct sbp2_lu *lu, int timeout) +{ + long leftover; + + leftover = wait_event_interruptible_timeout( + sbp2_access_wq, lu->access_complete, timeout); + lu->access_complete = 0; + return leftover <= 0; +} + +static void sbp2_free_packet(void *packet) +{ + hpsb_free_tlabel(packet); + hpsb_free_packet(packet); +} + +/* + * This is much like hpsb_node_write(), except it ignores the response + * subaction and returns immediately. Can be used from atomic context. + */ +static int sbp2util_node_write_no_wait(struct node_entry *ne, u64 addr, + quadlet_t *buf, size_t len) +{ + struct hpsb_packet *packet; + + packet = hpsb_make_writepacket(ne->host, ne->nodeid, addr, buf, len); + if (!packet) + return -ENOMEM; + + hpsb_set_packet_complete_task(packet, sbp2_free_packet, packet); + hpsb_node_fill_packet(ne, packet); + if (hpsb_send_packet(packet) < 0) { + sbp2_free_packet(packet); + return -EIO; + } + return 0; +} + +static void sbp2util_notify_fetch_agent(struct sbp2_lu *lu, u64 offset, + quadlet_t *data, size_t len) +{ + /* There is a small window after a bus reset within which the node + * entry's generation is current but the reconnect wasn't completed. */ + if (unlikely(atomic_read(&lu->state) == SBP2LU_STATE_IN_RESET)) + return; + + if (hpsb_node_write(lu->ne, lu->command_block_agent_addr + offset, + data, len)) + SBP2_ERR("sbp2util_notify_fetch_agent failed."); + + /* Now accept new SCSI commands, unless a bus reset happended during + * hpsb_node_write. */ + if (likely(atomic_read(&lu->state) != SBP2LU_STATE_IN_RESET)) + scsi_unblock_requests(lu->shost); +} + +static void sbp2util_write_orb_pointer(struct work_struct *work) +{ + struct sbp2_lu *lu = container_of(work, struct sbp2_lu, protocol_work); + quadlet_t data[2]; + + data[0] = ORB_SET_NODE_ID(lu->hi->host->node_id); + data[1] = lu->last_orb_dma; + sbp2util_cpu_to_be32_buffer(data, 8); + sbp2util_notify_fetch_agent(lu, SBP2_ORB_POINTER_OFFSET, data, 8); +} + +static void sbp2util_write_doorbell(struct work_struct *work) +{ + struct sbp2_lu *lu = container_of(work, struct sbp2_lu, protocol_work); + + sbp2util_notify_fetch_agent(lu, SBP2_DOORBELL_OFFSET, NULL, 4); +} + +static int sbp2util_create_command_orb_pool(struct sbp2_lu *lu) +{ + struct sbp2_command_info *cmd; + struct device *dmadev = lu->hi->host->device.parent; + int i, orbs = sbp2_serialize_io ? 2 : SBP2_MAX_CMDS; + + for (i = 0; i < orbs; i++) { + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) + goto failed_alloc; + + cmd->command_orb_dma = + dma_map_single(dmadev, &cmd->command_orb, + sizeof(struct sbp2_command_orb), + DMA_TO_DEVICE); + if (dma_mapping_error(dmadev, cmd->command_orb_dma)) + goto failed_orb; + + cmd->sge_dma = + dma_map_single(dmadev, &cmd->scatter_gather_element, + sizeof(cmd->scatter_gather_element), + DMA_TO_DEVICE); + if (dma_mapping_error(dmadev, cmd->sge_dma)) + goto failed_sge; + + INIT_LIST_HEAD(&cmd->list); + list_add_tail(&cmd->list, &lu->cmd_orb_completed); + } + return 0; + +failed_sge: + dma_unmap_single(dmadev, cmd->command_orb_dma, + sizeof(struct sbp2_command_orb), DMA_TO_DEVICE); +failed_orb: + kfree(cmd); +failed_alloc: + return -ENOMEM; +} + +static void sbp2util_remove_command_orb_pool(struct sbp2_lu *lu, + struct hpsb_host *host) +{ + struct list_head *lh, *next; + struct sbp2_command_info *cmd; + unsigned long flags; + + spin_lock_irqsave(&lu->cmd_orb_lock, flags); + if (!list_empty(&lu->cmd_orb_completed)) + list_for_each_safe(lh, next, &lu->cmd_orb_completed) { + cmd = list_entry(lh, struct sbp2_command_info, list); + dma_unmap_single(host->device.parent, + cmd->command_orb_dma, + sizeof(struct sbp2_command_orb), + DMA_TO_DEVICE); + dma_unmap_single(host->device.parent, cmd->sge_dma, + sizeof(cmd->scatter_gather_element), + DMA_TO_DEVICE); + kfree(cmd); + } + spin_unlock_irqrestore(&lu->cmd_orb_lock, flags); + return; +} + +/* + * Finds the sbp2_command for a given outstanding command ORB. + * Only looks at the in-use list. + */ +static struct sbp2_command_info *sbp2util_find_command_for_orb( + struct sbp2_lu *lu, dma_addr_t orb) +{ + struct sbp2_command_info *cmd; + unsigned long flags; + + spin_lock_irqsave(&lu->cmd_orb_lock, flags); + if (!list_empty(&lu->cmd_orb_inuse)) + list_for_each_entry(cmd, &lu->cmd_orb_inuse, list) + if (cmd->command_orb_dma == orb) { + spin_unlock_irqrestore( + &lu->cmd_orb_lock, flags); + return cmd; + } + spin_unlock_irqrestore(&lu->cmd_orb_lock, flags); + return NULL; +} + +/* + * Finds the sbp2_command for a given outstanding SCpnt. + * Only looks at the in-use list. + * Must be called with lu->cmd_orb_lock held. + */ +static struct sbp2_command_info *sbp2util_find_command_for_SCpnt( + struct sbp2_lu *lu, void *SCpnt) +{ + struct sbp2_command_info *cmd; + + if (!list_empty(&lu->cmd_orb_inuse)) + list_for_each_entry(cmd, &lu->cmd_orb_inuse, list) + if (cmd->Current_SCpnt == SCpnt) + return cmd; + return NULL; +} + +static struct sbp2_command_info *sbp2util_allocate_command_orb( + struct sbp2_lu *lu, + struct scsi_cmnd *Current_SCpnt, + void (*Current_done)(struct scsi_cmnd *)) +{ + struct list_head *lh; + struct sbp2_command_info *cmd = NULL; + unsigned long flags; + + spin_lock_irqsave(&lu->cmd_orb_lock, flags); + if (!list_empty(&lu->cmd_orb_completed)) { + lh = lu->cmd_orb_completed.next; + list_del(lh); + cmd = list_entry(lh, struct sbp2_command_info, list); + cmd->Current_done = Current_done; + cmd->Current_SCpnt = Current_SCpnt; + list_add_tail(&cmd->list, &lu->cmd_orb_inuse); + } else + SBP2_ERR("%s: no orbs available", __func__); + spin_unlock_irqrestore(&lu->cmd_orb_lock, flags); + return cmd; +} + +/* + * Unmaps the DMAs of a command and moves the command to the completed ORB list. + * Must be called with lu->cmd_orb_lock held. + */ +static void sbp2util_mark_command_completed(struct sbp2_lu *lu, + struct sbp2_command_info *cmd) +{ + if (scsi_sg_count(cmd->Current_SCpnt)) + dma_unmap_sg(lu->ud->ne->host->device.parent, + scsi_sglist(cmd->Current_SCpnt), + scsi_sg_count(cmd->Current_SCpnt), + cmd->Current_SCpnt->sc_data_direction); + list_move_tail(&cmd->list, &lu->cmd_orb_completed); +} + +/* + * Is lu valid? Is the 1394 node still present? + */ +static inline int sbp2util_node_is_available(struct sbp2_lu *lu) +{ + return lu && lu->ne && !lu->ne->in_limbo; +} + +/********************************************* + * IEEE-1394 core driver stack related section + *********************************************/ + +static int sbp2_probe(struct device *dev) +{ + struct unit_directory *ud; + struct sbp2_lu *lu; + + ud = container_of(dev, struct unit_directory, device); + + /* Don't probe UD's that have the LUN flag. We'll probe the LUN(s) + * instead. */ + if (ud->flags & UNIT_DIRECTORY_HAS_LUN_DIRECTORY) + return -ENODEV; + + lu = sbp2_alloc_device(ud); + if (!lu) + return -ENOMEM; + + sbp2_parse_unit_directory(lu, ud); + return sbp2_start_device(lu); +} + +static int sbp2_remove(struct device *dev) +{ + struct unit_directory *ud; + struct sbp2_lu *lu; + struct scsi_device *sdev; + + ud = container_of(dev, struct unit_directory, device); + lu = ud->device.driver_data; + if (!lu) + return 0; + + if (lu->shost) { + /* Get rid of enqueued commands if there is no chance to + * send them. */ + if (!sbp2util_node_is_available(lu)) + sbp2scsi_complete_all_commands(lu, DID_NO_CONNECT); + /* scsi_remove_device() may trigger shutdown functions of SCSI + * highlevel drivers which would deadlock if blocked. */ + atomic_set(&lu->state, SBP2LU_STATE_IN_SHUTDOWN); + scsi_unblock_requests(lu->shost); + } + sdev = lu->sdev; + if (sdev) { + lu->sdev = NULL; + scsi_remove_device(sdev); + } + + sbp2_logout_device(lu); + sbp2_remove_device(lu); + + return 0; +} + +static int sbp2_update(struct unit_directory *ud) +{ + struct sbp2_lu *lu = ud->device.driver_data; + + if (sbp2_reconnect_device(lu) != 0) { + /* + * Reconnect failed. If another bus reset happened, + * let nodemgr proceed and call sbp2_update again later + * (or sbp2_remove if this node went away). + */ + if (!hpsb_node_entry_valid(lu->ne)) + return 0; + /* + * Or the target rejected the reconnect because we weren't + * fast enough. Try a regular login, but first log out + * just in case of any weirdness. + */ + sbp2_logout_device(lu); + + if (sbp2_login_device(lu) != 0) { + if (!hpsb_node_entry_valid(lu->ne)) + return 0; + + /* Maybe another initiator won the login. */ + SBP2_ERR("Failed to reconnect to sbp2 device!"); + return -EBUSY; + } + } + + sbp2_set_busy_timeout(lu); + sbp2_agent_reset(lu, 1); + sbp2_max_speed_and_size(lu); + + /* Complete any pending commands with busy (so they get retried) + * and remove them from our queue. */ + sbp2scsi_complete_all_commands(lu, DID_BUS_BUSY); + + /* Accept new commands unless there was another bus reset in the + * meantime. */ + if (hpsb_node_entry_valid(lu->ne)) { + atomic_set(&lu->state, SBP2LU_STATE_RUNNING); + scsi_unblock_requests(lu->shost); + } + return 0; +} + +static struct sbp2_lu *sbp2_alloc_device(struct unit_directory *ud) +{ + struct sbp2_fwhost_info *hi; + struct Scsi_Host *shost = NULL; + struct sbp2_lu *lu = NULL; + unsigned long flags; + + lu = kzalloc(sizeof(*lu), GFP_KERNEL); + if (!lu) { + SBP2_ERR("failed to create lu"); + goto failed_alloc; + } + + lu->ne = ud->ne; + lu->ud = ud; + lu->speed_code = IEEE1394_SPEED_100; + lu->max_payload_size = sbp2_speedto_max_payload[IEEE1394_SPEED_100]; + lu->status_fifo_addr = CSR1212_INVALID_ADDR_SPACE; + INIT_LIST_HEAD(&lu->cmd_orb_inuse); + INIT_LIST_HEAD(&lu->cmd_orb_completed); + INIT_LIST_HEAD(&lu->lu_list); + spin_lock_init(&lu->cmd_orb_lock); + atomic_set(&lu->state, SBP2LU_STATE_RUNNING); + INIT_WORK(&lu->protocol_work, NULL); + + ud->device.driver_data = lu; + + hi = hpsb_get_hostinfo(&sbp2_highlevel, ud->ne->host); + if (!hi) { + hi = hpsb_create_hostinfo(&sbp2_highlevel, ud->ne->host, + sizeof(*hi)); + if (!hi) { + SBP2_ERR("failed to allocate hostinfo"); + goto failed_alloc; + } + hi->host = ud->ne->host; + INIT_LIST_HEAD(&hi->logical_units); + +#ifdef CONFIG_IEEE1394_SBP2_PHYS_DMA + /* Handle data movement if physical dma is not + * enabled or not supported on host controller */ + if (!hpsb_register_addrspace(&sbp2_highlevel, ud->ne->host, + &sbp2_physdma_ops, + 0x0ULL, 0xfffffffcULL)) { + SBP2_ERR("failed to register lower 4GB address range"); + goto failed_alloc; + } +#endif + } + + if (dma_get_max_seg_size(hi->host->device.parent) > SBP2_MAX_SEG_SIZE) + BUG_ON(dma_set_max_seg_size(hi->host->device.parent, + SBP2_MAX_SEG_SIZE)); + + /* Prevent unloading of the 1394 host */ + if (!try_module_get(hi->host->driver->owner)) { + SBP2_ERR("failed to get a reference on 1394 host driver"); + goto failed_alloc; + } + + lu->hi = hi; + + write_lock_irqsave(&sbp2_hi_logical_units_lock, flags); + list_add_tail(&lu->lu_list, &hi->logical_units); + write_unlock_irqrestore(&sbp2_hi_logical_units_lock, flags); + + /* Register the status FIFO address range. We could use the same FIFO + * for targets at different nodes. However we need different FIFOs per + * target in order to support multi-unit devices. + * The FIFO is located out of the local host controller's physical range + * but, if possible, within the posted write area. Status writes will + * then be performed as unified transactions. This slightly reduces + * bandwidth usage, and some Prolific based devices seem to require it. + */ + lu->status_fifo_addr = hpsb_allocate_and_register_addrspace( + &sbp2_highlevel, ud->ne->host, &sbp2_ops, + sizeof(struct sbp2_status_block), sizeof(quadlet_t), + ud->ne->host->low_addr_space, CSR1212_ALL_SPACE_END); + if (lu->status_fifo_addr == CSR1212_INVALID_ADDR_SPACE) { + SBP2_ERR("failed to allocate status FIFO address range"); + goto failed_alloc; + } + + shost = scsi_host_alloc(&sbp2_shost_template, sizeof(unsigned long)); + if (!shost) { + SBP2_ERR("failed to register scsi host"); + goto failed_alloc; + } + + shost->hostdata[0] = (unsigned long)lu; + + if (!scsi_add_host(shost, &ud->device)) { + lu->shost = shost; + return lu; + } + + SBP2_ERR("failed to add scsi host"); + scsi_host_put(shost); + +failed_alloc: + sbp2_remove_device(lu); + return NULL; +} + +static void sbp2_host_reset(struct hpsb_host *host) +{ + struct sbp2_fwhost_info *hi; + struct sbp2_lu *lu; + unsigned long flags; + + hi = hpsb_get_hostinfo(&sbp2_highlevel, host); + if (!hi) + return; + + read_lock_irqsave(&sbp2_hi_logical_units_lock, flags); + + list_for_each_entry(lu, &hi->logical_units, lu_list) + if (atomic_cmpxchg(&lu->state, + SBP2LU_STATE_RUNNING, SBP2LU_STATE_IN_RESET) + == SBP2LU_STATE_RUNNING) + scsi_block_requests(lu->shost); + + read_unlock_irqrestore(&sbp2_hi_logical_units_lock, flags); +} + +static int sbp2_start_device(struct sbp2_lu *lu) +{ + struct sbp2_fwhost_info *hi = lu->hi; + int error; + + lu->login_response = dma_alloc_coherent(hi->host->device.parent, + sizeof(struct sbp2_login_response), + &lu->login_response_dma, GFP_KERNEL); + if (!lu->login_response) + goto alloc_fail; + + lu->query_logins_orb = dma_alloc_coherent(hi->host->device.parent, + sizeof(struct sbp2_query_logins_orb), + &lu->query_logins_orb_dma, GFP_KERNEL); + if (!lu->query_logins_orb) + goto alloc_fail; + + lu->query_logins_response = dma_alloc_coherent(hi->host->device.parent, + sizeof(struct sbp2_query_logins_response), + &lu->query_logins_response_dma, GFP_KERNEL); + if (!lu->query_logins_response) + goto alloc_fail; + + lu->reconnect_orb = dma_alloc_coherent(hi->host->device.parent, + sizeof(struct sbp2_reconnect_orb), + &lu->reconnect_orb_dma, GFP_KERNEL); + if (!lu->reconnect_orb) + goto alloc_fail; + + lu->logout_orb = dma_alloc_coherent(hi->host->device.parent, + sizeof(struct sbp2_logout_orb), + &lu->logout_orb_dma, GFP_KERNEL); + if (!lu->logout_orb) + goto alloc_fail; + + lu->login_orb = dma_alloc_coherent(hi->host->device.parent, + sizeof(struct sbp2_login_orb), + &lu->login_orb_dma, GFP_KERNEL); + if (!lu->login_orb) + goto alloc_fail; + + if (sbp2util_create_command_orb_pool(lu)) + goto alloc_fail; + + /* Wait a second before trying to log in. Previously logged in + * initiators need a chance to reconnect. */ + if (msleep_interruptible(1000)) { + sbp2_remove_device(lu); + return -EINTR; + } + + if (sbp2_login_device(lu)) { + sbp2_remove_device(lu); + return -EBUSY; + } + + sbp2_set_busy_timeout(lu); + sbp2_agent_reset(lu, 1); + sbp2_max_speed_and_size(lu); + + if (lu->workarounds & SBP2_WORKAROUND_DELAY_INQUIRY) + ssleep(SBP2_INQUIRY_DELAY); + + error = scsi_add_device(lu->shost, 0, lu->ud->id, 0); + if (error) { + SBP2_ERR("scsi_add_device failed"); + sbp2_logout_device(lu); + sbp2_remove_device(lu); + return error; + } + + return 0; + +alloc_fail: + SBP2_ERR("Could not allocate memory for lu"); + sbp2_remove_device(lu); + return -ENOMEM; +} + +static void sbp2_remove_device(struct sbp2_lu *lu) +{ + struct sbp2_fwhost_info *hi; + unsigned long flags; + + if (!lu) + return; + hi = lu->hi; + if (!hi) + goto no_hi; + + if (lu->shost) { + scsi_remove_host(lu->shost); + scsi_host_put(lu->shost); + } + flush_scheduled_work(); + sbp2util_remove_command_orb_pool(lu, hi->host); + + write_lock_irqsave(&sbp2_hi_logical_units_lock, flags); + list_del(&lu->lu_list); + write_unlock_irqrestore(&sbp2_hi_logical_units_lock, flags); + + if (lu->login_response) + dma_free_coherent(hi->host->device.parent, + sizeof(struct sbp2_login_response), + lu->login_response, + lu->login_response_dma); + if (lu->login_orb) + dma_free_coherent(hi->host->device.parent, + sizeof(struct sbp2_login_orb), + lu->login_orb, + lu->login_orb_dma); + if (lu->reconnect_orb) + dma_free_coherent(hi->host->device.parent, + sizeof(struct sbp2_reconnect_orb), + lu->reconnect_orb, + lu->reconnect_orb_dma); + if (lu->logout_orb) + dma_free_coherent(hi->host->device.parent, + sizeof(struct sbp2_logout_orb), + lu->logout_orb, + lu->logout_orb_dma); + if (lu->query_logins_orb) + dma_free_coherent(hi->host->device.parent, + sizeof(struct sbp2_query_logins_orb), + lu->query_logins_orb, + lu->query_logins_orb_dma); + if (lu->query_logins_response) + dma_free_coherent(hi->host->device.parent, + sizeof(struct sbp2_query_logins_response), + lu->query_logins_response, + lu->query_logins_response_dma); + + if (lu->status_fifo_addr != CSR1212_INVALID_ADDR_SPACE) + hpsb_unregister_addrspace(&sbp2_highlevel, hi->host, + lu->status_fifo_addr); + + lu->ud->device.driver_data = NULL; + + module_put(hi->host->driver->owner); +no_hi: + kfree(lu); +} + +#ifdef CONFIG_IEEE1394_SBP2_PHYS_DMA +/* + * Deal with write requests on adapters which do not support physical DMA or + * have it switched off. + */ +static int sbp2_handle_physdma_write(struct hpsb_host *host, int nodeid, + int destid, quadlet_t *data, u64 addr, + size_t length, u16 flags) +{ + memcpy(bus_to_virt((u32) addr), data, length); + return RCODE_COMPLETE; +} + +/* + * Deal with read requests on adapters which do not support physical DMA or + * have it switched off. + */ +static int sbp2_handle_physdma_read(struct hpsb_host *host, int nodeid, + quadlet_t *data, u64 addr, size_t length, + u16 flags) +{ + memcpy(data, bus_to_virt((u32) addr), length); + return RCODE_COMPLETE; +} +#endif + +/************************************** + * SBP-2 protocol related section + **************************************/ + +static int sbp2_query_logins(struct sbp2_lu *lu) +{ + struct sbp2_fwhost_info *hi = lu->hi; + quadlet_t data[2]; + int max_logins; + int active_logins; + + lu->query_logins_orb->reserved1 = 0x0; + lu->query_logins_orb->reserved2 = 0x0; + + lu->query_logins_orb->query_response_lo = lu->query_logins_response_dma; + lu->query_logins_orb->query_response_hi = + ORB_SET_NODE_ID(hi->host->node_id); + lu->query_logins_orb->lun_misc = + ORB_SET_FUNCTION(SBP2_QUERY_LOGINS_REQUEST); + lu->query_logins_orb->lun_misc |= ORB_SET_NOTIFY(1); + lu->query_logins_orb->lun_misc |= ORB_SET_LUN(lu->lun); + + lu->query_logins_orb->reserved_resp_length = + ORB_SET_QUERY_LOGINS_RESP_LENGTH( + sizeof(struct sbp2_query_logins_response)); + + lu->query_logins_orb->status_fifo_hi = + ORB_SET_STATUS_FIFO_HI(lu->status_fifo_addr, hi->host->node_id); + lu->query_logins_orb->status_fifo_lo = + ORB_SET_STATUS_FIFO_LO(lu->status_fifo_addr); + + sbp2util_cpu_to_be32_buffer(lu->query_logins_orb, + sizeof(struct sbp2_query_logins_orb)); + + memset(lu->query_logins_response, 0, + sizeof(struct sbp2_query_logins_response)); + + data[0] = ORB_SET_NODE_ID(hi->host->node_id); + data[1] = lu->query_logins_orb_dma; + sbp2util_cpu_to_be32_buffer(data, 8); + + hpsb_node_write(lu->ne, lu->management_agent_addr, data, 8); + + if (sbp2util_access_timeout(lu, 2*HZ)) { + SBP2_INFO("Error querying logins to SBP-2 device - timed out"); + return -EIO; + } + + if (lu->status_block.ORB_offset_lo != lu->query_logins_orb_dma) { + SBP2_INFO("Error querying logins to SBP-2 device - timed out"); + return -EIO; + } + + if (STATUS_TEST_RDS(lu->status_block.ORB_offset_hi_misc)) { + SBP2_INFO("Error querying logins to SBP-2 device - failed"); + return -EIO; + } + + sbp2util_cpu_to_be32_buffer(lu->query_logins_response, + sizeof(struct sbp2_query_logins_response)); + + max_logins = RESPONSE_GET_MAX_LOGINS( + lu->query_logins_response->length_max_logins); + SBP2_INFO("Maximum concurrent logins supported: %d", max_logins); + + active_logins = RESPONSE_GET_ACTIVE_LOGINS( + lu->query_logins_response->length_max_logins); + SBP2_INFO("Number of active logins: %d", active_logins); + + if (active_logins >= max_logins) { + return -EIO; + } + + return 0; +} + +static int sbp2_login_device(struct sbp2_lu *lu) +{ + struct sbp2_fwhost_info *hi = lu->hi; + quadlet_t data[2]; + + if (!lu->login_orb) + return -EIO; + + if (!sbp2_exclusive_login && sbp2_query_logins(lu)) { + SBP2_INFO("Device does not support any more concurrent logins"); + return -EIO; + } + + /* assume no password */ + lu->login_orb->password_hi = 0; + lu->login_orb->password_lo = 0; + + lu->login_orb->login_response_lo = lu->login_response_dma; + lu->login_orb->login_response_hi = ORB_SET_NODE_ID(hi->host->node_id); + lu->login_orb->lun_misc = ORB_SET_FUNCTION(SBP2_LOGIN_REQUEST); + + /* one second reconnect time */ + lu->login_orb->lun_misc |= ORB_SET_RECONNECT(0); + lu->login_orb->lun_misc |= ORB_SET_EXCLUSIVE(sbp2_exclusive_login); + lu->login_orb->lun_misc |= ORB_SET_NOTIFY(1); + lu->login_orb->lun_misc |= ORB_SET_LUN(lu->lun); + + lu->login_orb->passwd_resp_lengths = + ORB_SET_LOGIN_RESP_LENGTH(sizeof(struct sbp2_login_response)); + + lu->login_orb->status_fifo_hi = + ORB_SET_STATUS_FIFO_HI(lu->status_fifo_addr, hi->host->node_id); + lu->login_orb->status_fifo_lo = + ORB_SET_STATUS_FIFO_LO(lu->status_fifo_addr); + + sbp2util_cpu_to_be32_buffer(lu->login_orb, + sizeof(struct sbp2_login_orb)); + + memset(lu->login_response, 0, sizeof(struct sbp2_login_response)); + + data[0] = ORB_SET_NODE_ID(hi->host->node_id); + data[1] = lu->login_orb_dma; + sbp2util_cpu_to_be32_buffer(data, 8); + + hpsb_node_write(lu->ne, lu->management_agent_addr, data, 8); + + /* wait up to 20 seconds for login status */ + if (sbp2util_access_timeout(lu, 20*HZ)) { + SBP2_ERR("Error logging into SBP-2 device - timed out"); + return -EIO; + } + + /* make sure that the returned status matches the login ORB */ + if (lu->status_block.ORB_offset_lo != lu->login_orb_dma) { + SBP2_ERR("Error logging into SBP-2 device - timed out"); + return -EIO; + } + + if (STATUS_TEST_RDS(lu->status_block.ORB_offset_hi_misc)) { + SBP2_ERR("Error logging into SBP-2 device - failed"); + return -EIO; + } + + sbp2util_cpu_to_be32_buffer(lu->login_response, + sizeof(struct sbp2_login_response)); + lu->command_block_agent_addr = + ((u64)lu->login_response->command_block_agent_hi) << 32; + lu->command_block_agent_addr |= + ((u64)lu->login_response->command_block_agent_lo); + lu->command_block_agent_addr &= 0x0000ffffffffffffULL; + + SBP2_INFO("Logged into SBP-2 device"); + return 0; +} + +static int sbp2_logout_device(struct sbp2_lu *lu) +{ + struct sbp2_fwhost_info *hi = lu->hi; + quadlet_t data[2]; + int error; + + lu->logout_orb->reserved1 = 0x0; + lu->logout_orb->reserved2 = 0x0; + lu->logout_orb->reserved3 = 0x0; + lu->logout_orb->reserved4 = 0x0; + + lu->logout_orb->login_ID_misc = ORB_SET_FUNCTION(SBP2_LOGOUT_REQUEST); + lu->logout_orb->login_ID_misc |= + ORB_SET_LOGIN_ID(lu->login_response->length_login_ID); + lu->logout_orb->login_ID_misc |= ORB_SET_NOTIFY(1); + + lu->logout_orb->reserved5 = 0x0; + lu->logout_orb->status_fifo_hi = + ORB_SET_STATUS_FIFO_HI(lu->status_fifo_addr, hi->host->node_id); + lu->logout_orb->status_fifo_lo = + ORB_SET_STATUS_FIFO_LO(lu->status_fifo_addr); + + sbp2util_cpu_to_be32_buffer(lu->logout_orb, + sizeof(struct sbp2_logout_orb)); + + data[0] = ORB_SET_NODE_ID(hi->host->node_id); + data[1] = lu->logout_orb_dma; + sbp2util_cpu_to_be32_buffer(data, 8); + + error = hpsb_node_write(lu->ne, lu->management_agent_addr, data, 8); + if (error) + return error; + + /* wait up to 1 second for the device to complete logout */ + if (sbp2util_access_timeout(lu, HZ)) + return -EIO; + + SBP2_INFO("Logged out of SBP-2 device"); + return 0; +} + +static int sbp2_reconnect_device(struct sbp2_lu *lu) +{ + struct sbp2_fwhost_info *hi = lu->hi; + quadlet_t data[2]; + int error; + + lu->reconnect_orb->reserved1 = 0x0; + lu->reconnect_orb->reserved2 = 0x0; + lu->reconnect_orb->reserved3 = 0x0; + lu->reconnect_orb->reserved4 = 0x0; + + lu->reconnect_orb->login_ID_misc = + ORB_SET_FUNCTION(SBP2_RECONNECT_REQUEST); + lu->reconnect_orb->login_ID_misc |= + ORB_SET_LOGIN_ID(lu->login_response->length_login_ID); + lu->reconnect_orb->login_ID_misc |= ORB_SET_NOTIFY(1); + + lu->reconnect_orb->reserved5 = 0x0; + lu->reconnect_orb->status_fifo_hi = + ORB_SET_STATUS_FIFO_HI(lu->status_fifo_addr, hi->host->node_id); + lu->reconnect_orb->status_fifo_lo = + ORB_SET_STATUS_FIFO_LO(lu->status_fifo_addr); + + sbp2util_cpu_to_be32_buffer(lu->reconnect_orb, + sizeof(struct sbp2_reconnect_orb)); + + data[0] = ORB_SET_NODE_ID(hi->host->node_id); + data[1] = lu->reconnect_orb_dma; + sbp2util_cpu_to_be32_buffer(data, 8); + + error = hpsb_node_write(lu->ne, lu->management_agent_addr, data, 8); + if (error) + return error; + + /* wait up to 1 second for reconnect status */ + if (sbp2util_access_timeout(lu, HZ)) { + SBP2_ERR("Error reconnecting to SBP-2 device - timed out"); + return -EIO; + } + + /* make sure that the returned status matches the reconnect ORB */ + if (lu->status_block.ORB_offset_lo != lu->reconnect_orb_dma) { + SBP2_ERR("Error reconnecting to SBP-2 device - timed out"); + return -EIO; + } + + if (STATUS_TEST_RDS(lu->status_block.ORB_offset_hi_misc)) { + SBP2_ERR("Error reconnecting to SBP-2 device - failed"); + return -EIO; + } + + SBP2_INFO("Reconnected to SBP-2 device"); + return 0; +} + +/* + * Set the target node's Single Phase Retry limit. Affects the target's retry + * behaviour if our node is too busy to accept requests. + */ +static int sbp2_set_busy_timeout(struct sbp2_lu *lu) +{ + quadlet_t data; + + data = cpu_to_be32(SBP2_BUSY_TIMEOUT_VALUE); + if (hpsb_node_write(lu->ne, SBP2_BUSY_TIMEOUT_ADDRESS, &data, 4)) + SBP2_ERR("%s error", __func__); + return 0; +} + +static void sbp2_parse_unit_directory(struct sbp2_lu *lu, + struct unit_directory *ud) +{ + struct csr1212_keyval *kv; + struct csr1212_dentry *dentry; + u64 management_agent_addr; + u32 unit_characteristics, firmware_revision; + unsigned workarounds; + int i; + + management_agent_addr = 0; + unit_characteristics = 0; + firmware_revision = 0; + + csr1212_for_each_dir_entry(ud->ne->csr, kv, ud->ud_kv, dentry) { + switch (kv->key.id) { + case CSR1212_KV_ID_DEPENDENT_INFO: + if (kv->key.type == CSR1212_KV_TYPE_CSR_OFFSET) + management_agent_addr = + CSR1212_REGISTER_SPACE_BASE + + (kv->value.csr_offset << 2); + + else if (kv->key.type == CSR1212_KV_TYPE_IMMEDIATE) + lu->lun = ORB_SET_LUN(kv->value.immediate); + break; + + case SBP2_UNIT_CHARACTERISTICS_KEY: + /* FIXME: This is ignored so far. + * See SBP-2 clause 7.4.8. */ + unit_characteristics = kv->value.immediate; + break; + + case SBP2_FIRMWARE_REVISION_KEY: + firmware_revision = kv->value.immediate; + break; + + default: + /* FIXME: Check for SBP2_DEVICE_TYPE_AND_LUN_KEY. + * Its "ordered" bit has consequences for command ORB + * list handling. See SBP-2 clauses 4.6, 7.4.11, 10.2 */ + break; + } + } + + workarounds = sbp2_default_workarounds; + + if (!(workarounds & SBP2_WORKAROUND_OVERRIDE)) + for (i = 0; i < ARRAY_SIZE(sbp2_workarounds_table); i++) { + if (sbp2_workarounds_table[i].firmware_revision != + SBP2_ROM_VALUE_WILDCARD && + sbp2_workarounds_table[i].firmware_revision != + (firmware_revision & 0xffff00)) + continue; + if (sbp2_workarounds_table[i].model_id != + SBP2_ROM_VALUE_WILDCARD && + sbp2_workarounds_table[i].model_id != ud->model_id) + continue; + workarounds |= sbp2_workarounds_table[i].workarounds; + break; + } + + if (workarounds) + SBP2_INFO("Workarounds for node " NODE_BUS_FMT ": 0x%x " + "(firmware_revision 0x%06x, vendor_id 0x%06x," + " model_id 0x%06x)", + NODE_BUS_ARGS(ud->ne->host, ud->ne->nodeid), + workarounds, firmware_revision, + ud->vendor_id ? ud->vendor_id : ud->ne->vendor_id, + ud->model_id); + + /* We would need one SCSI host template for each target to adjust + * max_sectors on the fly, therefore warn only. */ + if (workarounds & SBP2_WORKAROUND_128K_MAX_TRANS && + (sbp2_max_sectors * 512) > (128 * 1024)) + SBP2_INFO("Node " NODE_BUS_FMT ": Bridge only supports 128KB " + "max transfer size. WARNING: Current max_sectors " + "setting is larger than 128KB (%d sectors)", + NODE_BUS_ARGS(ud->ne->host, ud->ne->nodeid), + sbp2_max_sectors); + + /* If this is a logical unit directory entry, process the parent + * to get the values. */ + if (ud->flags & UNIT_DIRECTORY_LUN_DIRECTORY) { + struct unit_directory *parent_ud = container_of( + ud->device.parent, struct unit_directory, device); + sbp2_parse_unit_directory(lu, parent_ud); + } else { + lu->management_agent_addr = management_agent_addr; + lu->workarounds = workarounds; + if (ud->flags & UNIT_DIRECTORY_HAS_LUN) + lu->lun = ORB_SET_LUN(ud->lun); + } +} + +#define SBP2_PAYLOAD_TO_BYTES(p) (1 << ((p) + 2)) + +/* + * This function is called in order to determine the max speed and packet + * size we can use in our ORBs. Note, that we (the driver and host) only + * initiate the transaction. The SBP-2 device actually transfers the data + * (by reading from the DMA area we tell it). This means that the SBP-2 + * device decides the actual maximum data it can transfer. We just tell it + * the speed that it needs to use, and the max_rec the host supports, and + * it takes care of the rest. + */ +static int sbp2_max_speed_and_size(struct sbp2_lu *lu) +{ + struct sbp2_fwhost_info *hi = lu->hi; + u8 payload; + + lu->speed_code = hi->host->speed[NODEID_TO_NODE(lu->ne->nodeid)]; + + if (lu->speed_code > sbp2_max_speed) { + lu->speed_code = sbp2_max_speed; + SBP2_INFO("Reducing speed to %s", + hpsb_speedto_str[sbp2_max_speed]); + } + + /* Payload size is the lesser of what our speed supports and what + * our host supports. */ + payload = min(sbp2_speedto_max_payload[lu->speed_code], + (u8) (hi->host->csr.max_rec - 1)); + + /* If physical DMA is off, work around limitation in ohci1394: + * packet size must not exceed PAGE_SIZE */ + if (lu->ne->host->low_addr_space < (1ULL << 32)) + while (SBP2_PAYLOAD_TO_BYTES(payload) + 24 > PAGE_SIZE && + payload) + payload--; + + SBP2_INFO("Node " NODE_BUS_FMT ": Max speed [%s] - Max payload [%u]", + NODE_BUS_ARGS(hi->host, lu->ne->nodeid), + hpsb_speedto_str[lu->speed_code], + SBP2_PAYLOAD_TO_BYTES(payload)); + + lu->max_payload_size = payload; + return 0; +} + +static int sbp2_agent_reset(struct sbp2_lu *lu, int wait) +{ + quadlet_t data; + u64 addr; + int retval; + unsigned long flags; + + /* flush lu->protocol_work */ + if (wait) + flush_scheduled_work(); + + data = ntohl(SBP2_AGENT_RESET_DATA); + addr = lu->command_block_agent_addr + SBP2_AGENT_RESET_OFFSET; + + if (wait) + retval = hpsb_node_write(lu->ne, addr, &data, 4); + else + retval = sbp2util_node_write_no_wait(lu->ne, addr, &data, 4); + + if (retval < 0) { + SBP2_ERR("hpsb_node_write failed.\n"); + return -EIO; + } + + /* make sure that the ORB_POINTER is written on next command */ + spin_lock_irqsave(&lu->cmd_orb_lock, flags); + lu->last_orb = NULL; + spin_unlock_irqrestore(&lu->cmd_orb_lock, flags); + + return 0; +} + +static int sbp2_prep_command_orb_sg(struct sbp2_command_orb *orb, + struct sbp2_fwhost_info *hi, + struct sbp2_command_info *cmd, + unsigned int sg_count, + struct scatterlist *sg, + u32 orb_direction, + enum dma_data_direction dma_dir) +{ + struct device *dmadev = hi->host->device.parent; + struct sbp2_unrestricted_page_table *pt; + int i, n; + + n = dma_map_sg(dmadev, sg, sg_count, dma_dir); + if (n == 0) + return -ENOMEM; + + orb->data_descriptor_hi = ORB_SET_NODE_ID(hi->host->node_id); + orb->misc |= ORB_SET_DIRECTION(orb_direction); + + /* special case if only one element (and less than 64KB in size) */ + if (n == 1) { + orb->misc |= ORB_SET_DATA_SIZE(sg_dma_len(sg)); + orb->data_descriptor_lo = sg_dma_address(sg); + } else { + pt = &cmd->scatter_gather_element[0]; + + dma_sync_single_for_cpu(dmadev, cmd->sge_dma, + sizeof(cmd->scatter_gather_element), + DMA_TO_DEVICE); + + for_each_sg(sg, sg, n, i) { + pt[i].high = cpu_to_be32(sg_dma_len(sg) << 16); + pt[i].low = cpu_to_be32(sg_dma_address(sg)); + } + + orb->misc |= ORB_SET_PAGE_TABLE_PRESENT(0x1) | + ORB_SET_DATA_SIZE(n); + orb->data_descriptor_lo = cmd->sge_dma; + + dma_sync_single_for_device(dmadev, cmd->sge_dma, + sizeof(cmd->scatter_gather_element), + DMA_TO_DEVICE); + } + return 0; +} + +static int sbp2_create_command_orb(struct sbp2_lu *lu, + struct sbp2_command_info *cmd, + struct scsi_cmnd *SCpnt) +{ + struct device *dmadev = lu->hi->host->device.parent; + struct sbp2_command_orb *orb = &cmd->command_orb; + unsigned int scsi_request_bufflen = scsi_bufflen(SCpnt); + enum dma_data_direction dma_dir = SCpnt->sc_data_direction; + u32 orb_direction; + int ret; + + dma_sync_single_for_cpu(dmadev, cmd->command_orb_dma, + sizeof(struct sbp2_command_orb), DMA_TO_DEVICE); + /* + * Set-up our command ORB. + * + * NOTE: We're doing unrestricted page tables (s/g), as this is + * best performance (at least with the devices I have). This means + * that data_size becomes the number of s/g elements, and + * page_size should be zero (for unrestricted). + */ + orb->next_ORB_hi = ORB_SET_NULL_PTR(1); + orb->next_ORB_lo = 0x0; + orb->misc = ORB_SET_MAX_PAYLOAD(lu->max_payload_size); + orb->misc |= ORB_SET_SPEED(lu->speed_code); + orb->misc |= ORB_SET_NOTIFY(1); + + if (dma_dir == DMA_NONE) + orb_direction = ORB_DIRECTION_NO_DATA_TRANSFER; + else if (dma_dir == DMA_TO_DEVICE && scsi_request_bufflen) + orb_direction = ORB_DIRECTION_WRITE_TO_MEDIA; + else if (dma_dir == DMA_FROM_DEVICE && scsi_request_bufflen) + orb_direction = ORB_DIRECTION_READ_FROM_MEDIA; + else { + SBP2_INFO("Falling back to DMA_NONE"); + orb_direction = ORB_DIRECTION_NO_DATA_TRANSFER; + } + + /* set up our page table stuff */ + if (orb_direction == ORB_DIRECTION_NO_DATA_TRANSFER) { + orb->data_descriptor_hi = 0x0; + orb->data_descriptor_lo = 0x0; + orb->misc |= ORB_SET_DIRECTION(1); + ret = 0; + } else { + ret = sbp2_prep_command_orb_sg(orb, lu->hi, cmd, + scsi_sg_count(SCpnt), + scsi_sglist(SCpnt), + orb_direction, dma_dir); + } + sbp2util_cpu_to_be32_buffer(orb, sizeof(*orb)); + + memset(orb->cdb, 0, sizeof(orb->cdb)); + memcpy(orb->cdb, SCpnt->cmnd, SCpnt->cmd_len); + + dma_sync_single_for_device(dmadev, cmd->command_orb_dma, + sizeof(struct sbp2_command_orb), DMA_TO_DEVICE); + return ret; +} + +static void sbp2_link_orb_command(struct sbp2_lu *lu, + struct sbp2_command_info *cmd) +{ + struct sbp2_fwhost_info *hi = lu->hi; + struct sbp2_command_orb *last_orb; + dma_addr_t last_orb_dma; + u64 addr = lu->command_block_agent_addr; + quadlet_t data[2]; + size_t length; + unsigned long flags; + + /* check to see if there are any previous orbs to use */ + spin_lock_irqsave(&lu->cmd_orb_lock, flags); + last_orb = lu->last_orb; + last_orb_dma = lu->last_orb_dma; + if (!last_orb) { + /* + * last_orb == NULL means: We know that the target's fetch agent + * is not active right now. + */ + addr += SBP2_ORB_POINTER_OFFSET; + data[0] = ORB_SET_NODE_ID(hi->host->node_id); + data[1] = cmd->command_orb_dma; + sbp2util_cpu_to_be32_buffer(data, 8); + length = 8; + } else { + /* + * last_orb != NULL means: We know that the target's fetch agent + * is (very probably) not dead or in reset state right now. + * We have an ORB already sent that we can append a new one to. + * The target's fetch agent may or may not have read this + * previous ORB yet. + */ + dma_sync_single_for_cpu(hi->host->device.parent, last_orb_dma, + sizeof(struct sbp2_command_orb), + DMA_TO_DEVICE); + last_orb->next_ORB_lo = cpu_to_be32(cmd->command_orb_dma); + wmb(); + /* Tells hardware that this pointer is valid */ + last_orb->next_ORB_hi = 0; + dma_sync_single_for_device(hi->host->device.parent, + last_orb_dma, + sizeof(struct sbp2_command_orb), + DMA_TO_DEVICE); + addr += SBP2_DOORBELL_OFFSET; + data[0] = 0; + length = 4; + } + lu->last_orb = &cmd->command_orb; + lu->last_orb_dma = cmd->command_orb_dma; + spin_unlock_irqrestore(&lu->cmd_orb_lock, flags); + + if (sbp2util_node_write_no_wait(lu->ne, addr, data, length)) { + /* + * sbp2util_node_write_no_wait failed. We certainly ran out + * of transaction labels, perhaps just because there were no + * context switches which gave khpsbpkt a chance to collect + * free tlabels. Try again in non-atomic context. If necessary, + * the workqueue job will sleep to guaranteedly get a tlabel. + * We do not accept new commands until the job is over. + */ + scsi_block_requests(lu->shost); + PREPARE_WORK(&lu->protocol_work, + last_orb ? sbp2util_write_doorbell: + sbp2util_write_orb_pointer); + schedule_work(&lu->protocol_work); + } +} + +static int sbp2_send_command(struct sbp2_lu *lu, struct scsi_cmnd *SCpnt, + void (*done)(struct scsi_cmnd *)) +{ + struct sbp2_command_info *cmd; + + cmd = sbp2util_allocate_command_orb(lu, SCpnt, done); + if (!cmd) + return -EIO; + + if (sbp2_create_command_orb(lu, cmd, SCpnt)) + return -ENOMEM; + + sbp2_link_orb_command(lu, cmd); + return 0; +} + +/* + * Translates SBP-2 status into SCSI sense data for check conditions + */ +static unsigned int sbp2_status_to_sense_data(unchar *sbp2_status, + unchar *sense_data) +{ + /* OK, it's pretty ugly... ;-) */ + sense_data[0] = 0x70; + sense_data[1] = 0x0; + sense_data[2] = sbp2_status[9]; + sense_data[3] = sbp2_status[12]; + sense_data[4] = sbp2_status[13]; + sense_data[5] = sbp2_status[14]; + sense_data[6] = sbp2_status[15]; + sense_data[7] = 10; + sense_data[8] = sbp2_status[16]; + sense_data[9] = sbp2_status[17]; + sense_data[10] = sbp2_status[18]; + sense_data[11] = sbp2_status[19]; + sense_data[12] = sbp2_status[10]; + sense_data[13] = sbp2_status[11]; + sense_data[14] = sbp2_status[20]; + sense_data[15] = sbp2_status[21]; + + return sbp2_status[8] & 0x3f; +} + +static int sbp2_handle_status_write(struct hpsb_host *host, int nodeid, + int destid, quadlet_t *data, u64 addr, + size_t length, u16 fl) +{ + struct sbp2_fwhost_info *hi; + struct sbp2_lu *lu = NULL, *lu_tmp; + struct scsi_cmnd *SCpnt = NULL; + struct sbp2_status_block *sb; + u32 scsi_status = SBP2_SCSI_STATUS_GOOD; + struct sbp2_command_info *cmd; + unsigned long flags; + + if (unlikely(length < 8 || length > sizeof(struct sbp2_status_block))) { + SBP2_ERR("Wrong size of status block"); + return RCODE_ADDRESS_ERROR; + } + if (unlikely(!host)) { + SBP2_ERR("host is NULL - this is bad!"); + return RCODE_ADDRESS_ERROR; + } + hi = hpsb_get_hostinfo(&sbp2_highlevel, host); + if (unlikely(!hi)) { + SBP2_ERR("host info is NULL - this is bad!"); + return RCODE_ADDRESS_ERROR; + } + + /* Find the unit which wrote the status. */ + read_lock_irqsave(&sbp2_hi_logical_units_lock, flags); + list_for_each_entry(lu_tmp, &hi->logical_units, lu_list) { + if (lu_tmp->ne->nodeid == nodeid && + lu_tmp->status_fifo_addr == addr) { + lu = lu_tmp; + break; + } + } + read_unlock_irqrestore(&sbp2_hi_logical_units_lock, flags); + + if (unlikely(!lu)) { + SBP2_ERR("lu is NULL - device is gone?"); + return RCODE_ADDRESS_ERROR; + } + + /* Put response into lu status fifo buffer. The first two bytes + * come in big endian bit order. Often the target writes only a + * truncated status block, minimally the first two quadlets. The rest + * is implied to be zeros. */ + sb = &lu->status_block; + memset(sb->command_set_dependent, 0, sizeof(sb->command_set_dependent)); + memcpy(sb, data, length); + sbp2util_be32_to_cpu_buffer(sb, 8); + + /* Ignore unsolicited status. Handle command ORB status. */ + if (unlikely(STATUS_GET_SRC(sb->ORB_offset_hi_misc) == 2)) + cmd = NULL; + else + cmd = sbp2util_find_command_for_orb(lu, sb->ORB_offset_lo); + if (cmd) { + /* Grab SCSI command pointers and check status. */ + /* + * FIXME: If the src field in the status is 1, the ORB DMA must + * not be reused until status for a subsequent ORB is received. + */ + SCpnt = cmd->Current_SCpnt; + spin_lock_irqsave(&lu->cmd_orb_lock, flags); + sbp2util_mark_command_completed(lu, cmd); + spin_unlock_irqrestore(&lu->cmd_orb_lock, flags); + + if (SCpnt) { + u32 h = sb->ORB_offset_hi_misc; + u32 r = STATUS_GET_RESP(h); + + if (r != RESP_STATUS_REQUEST_COMPLETE) { + SBP2_INFO("resp 0x%x, sbp_status 0x%x", + r, STATUS_GET_SBP_STATUS(h)); + scsi_status = + r == RESP_STATUS_TRANSPORT_FAILURE ? + SBP2_SCSI_STATUS_BUSY : + SBP2_SCSI_STATUS_COMMAND_TERMINATED; + } + + if (STATUS_GET_LEN(h) > 1) + scsi_status = sbp2_status_to_sense_data( + (unchar *)sb, SCpnt->sense_buffer); + + if (STATUS_TEST_DEAD(h)) + sbp2_agent_reset(lu, 0); + } + + /* Check here to see if there are no commands in-use. If there + * are none, we know that the fetch agent left the active state + * _and_ that we did not reactivate it yet. Therefore clear + * last_orb so that next time we write directly to the + * ORB_POINTER register. That way the fetch agent does not need + * to refetch the next_ORB. */ + spin_lock_irqsave(&lu->cmd_orb_lock, flags); + if (list_empty(&lu->cmd_orb_inuse)) + lu->last_orb = NULL; + spin_unlock_irqrestore(&lu->cmd_orb_lock, flags); + + } else { + /* It's probably status after a management request. */ + if ((sb->ORB_offset_lo == lu->reconnect_orb_dma) || + (sb->ORB_offset_lo == lu->login_orb_dma) || + (sb->ORB_offset_lo == lu->query_logins_orb_dma) || + (sb->ORB_offset_lo == lu->logout_orb_dma)) { + lu->access_complete = 1; + wake_up_interruptible(&sbp2_access_wq); + } + } + + if (SCpnt) + sbp2scsi_complete_command(lu, scsi_status, SCpnt, + cmd->Current_done); + return RCODE_COMPLETE; +} + +/************************************** + * SCSI interface related section + **************************************/ + +static int sbp2scsi_queuecommand(struct scsi_cmnd *SCpnt, + void (*done)(struct scsi_cmnd *)) +{ + struct sbp2_lu *lu = (struct sbp2_lu *)SCpnt->device->host->hostdata[0]; + struct sbp2_fwhost_info *hi; + int result = DID_NO_CONNECT << 16; + + if (unlikely(!sbp2util_node_is_available(lu))) + goto done; + + hi = lu->hi; + + if (unlikely(!hi)) { + SBP2_ERR("sbp2_fwhost_info is NULL - this is bad!"); + goto done; + } + + /* Multiple units are currently represented to the SCSI core as separate + * targets, not as one target with multiple LUs. Therefore return + * selection time-out to any IO directed at non-zero LUNs. */ + if (unlikely(SCpnt->device->lun)) + goto done; + + if (unlikely(!hpsb_node_entry_valid(lu->ne))) { + SBP2_ERR("Bus reset in progress - rejecting command"); + result = DID_BUS_BUSY << 16; + goto done; + } + + /* Bidirectional commands are not yet implemented, + * and unknown transfer direction not handled. */ + if (unlikely(SCpnt->sc_data_direction == DMA_BIDIRECTIONAL)) { + SBP2_ERR("Cannot handle DMA_BIDIRECTIONAL - rejecting command"); + result = DID_ERROR << 16; + goto done; + } + + if (sbp2_send_command(lu, SCpnt, done)) { + SBP2_ERR("Error sending SCSI command"); + sbp2scsi_complete_command(lu, + SBP2_SCSI_STATUS_SELECTION_TIMEOUT, + SCpnt, done); + } + return 0; + +done: + SCpnt->result = result; + done(SCpnt); + return 0; +} + +static void sbp2scsi_complete_all_commands(struct sbp2_lu *lu, u32 status) +{ + struct list_head *lh; + struct sbp2_command_info *cmd; + unsigned long flags; + + spin_lock_irqsave(&lu->cmd_orb_lock, flags); + while (!list_empty(&lu->cmd_orb_inuse)) { + lh = lu->cmd_orb_inuse.next; + cmd = list_entry(lh, struct sbp2_command_info, list); + sbp2util_mark_command_completed(lu, cmd); + if (cmd->Current_SCpnt) { + cmd->Current_SCpnt->result = status << 16; + cmd->Current_done(cmd->Current_SCpnt); + } + } + spin_unlock_irqrestore(&lu->cmd_orb_lock, flags); + + return; +} + +/* + * Complete a regular SCSI command. Can be called in atomic context. + */ +static void sbp2scsi_complete_command(struct sbp2_lu *lu, u32 scsi_status, + struct scsi_cmnd *SCpnt, + void (*done)(struct scsi_cmnd *)) +{ + if (!SCpnt) { + SBP2_ERR("SCpnt is NULL"); + return; + } + + switch (scsi_status) { + case SBP2_SCSI_STATUS_GOOD: + SCpnt->result = DID_OK << 16; + break; + + case SBP2_SCSI_STATUS_BUSY: + SBP2_ERR("SBP2_SCSI_STATUS_BUSY"); + SCpnt->result = DID_BUS_BUSY << 16; + break; + + case SBP2_SCSI_STATUS_CHECK_CONDITION: + SCpnt->result = CHECK_CONDITION << 1 | DID_OK << 16; + break; + + case SBP2_SCSI_STATUS_SELECTION_TIMEOUT: + SBP2_ERR("SBP2_SCSI_STATUS_SELECTION_TIMEOUT"); + SCpnt->result = DID_NO_CONNECT << 16; + scsi_print_command(SCpnt); + break; + + case SBP2_SCSI_STATUS_CONDITION_MET: + case SBP2_SCSI_STATUS_RESERVATION_CONFLICT: + case SBP2_SCSI_STATUS_COMMAND_TERMINATED: + SBP2_ERR("Bad SCSI status = %x", scsi_status); + SCpnt->result = DID_ERROR << 16; + scsi_print_command(SCpnt); + break; + + default: + SBP2_ERR("Unsupported SCSI status = %x", scsi_status); + SCpnt->result = DID_ERROR << 16; + } + + /* If a bus reset is in progress and there was an error, complete + * the command as busy so that it will get retried. */ + if (!hpsb_node_entry_valid(lu->ne) + && (scsi_status != SBP2_SCSI_STATUS_GOOD)) { + SBP2_ERR("Completing command with busy (bus reset)"); + SCpnt->result = DID_BUS_BUSY << 16; + } + + /* Tell the SCSI stack that we're done with this command. */ + done(SCpnt); +} + +static int sbp2scsi_slave_alloc(struct scsi_device *sdev) +{ + struct sbp2_lu *lu = (struct sbp2_lu *)sdev->host->hostdata[0]; + + if (sdev->lun != 0 || sdev->id != lu->ud->id || sdev->channel != 0) + return -ENODEV; + + lu->sdev = sdev; + sdev->allow_restart = 1; + + /* SBP-2 requires quadlet alignment of the data buffers. */ + blk_queue_update_dma_alignment(sdev->request_queue, 4 - 1); + + if (lu->workarounds & SBP2_WORKAROUND_INQUIRY_36) + sdev->inquiry_len = 36; + return 0; +} + +static int sbp2scsi_slave_configure(struct scsi_device *sdev) +{ + struct sbp2_lu *lu = (struct sbp2_lu *)sdev->host->hostdata[0]; + + sdev->use_10_for_rw = 1; + + if (sbp2_exclusive_login) + sdev->manage_start_stop = 1; + if (sdev->type == TYPE_ROM) + sdev->use_10_for_ms = 1; + if (sdev->type == TYPE_DISK && + lu->workarounds & SBP2_WORKAROUND_MODE_SENSE_8) + sdev->skip_ms_page_8 = 1; + if (lu->workarounds & SBP2_WORKAROUND_FIX_CAPACITY) + sdev->fix_capacity = 1; + if (lu->workarounds & SBP2_WORKAROUND_POWER_CONDITION) + sdev->start_stop_pwr_cond = 1; + if (lu->workarounds & SBP2_WORKAROUND_128K_MAX_TRANS) + blk_queue_max_sectors(sdev->request_queue, 128 * 1024 / 512); + + blk_queue_max_segment_size(sdev->request_queue, SBP2_MAX_SEG_SIZE); + return 0; +} + +static void sbp2scsi_slave_destroy(struct scsi_device *sdev) +{ + ((struct sbp2_lu *)sdev->host->hostdata[0])->sdev = NULL; + return; +} + +/* + * Called by scsi stack when something has really gone wrong. + * Usually called when a command has timed-out for some reason. + */ +static int sbp2scsi_abort(struct scsi_cmnd *SCpnt) +{ + struct sbp2_lu *lu = (struct sbp2_lu *)SCpnt->device->host->hostdata[0]; + struct sbp2_command_info *cmd; + unsigned long flags; + + SBP2_INFO("aborting sbp2 command"); + scsi_print_command(SCpnt); + + if (sbp2util_node_is_available(lu)) { + sbp2_agent_reset(lu, 1); + + /* Return a matching command structure to the free pool. */ + spin_lock_irqsave(&lu->cmd_orb_lock, flags); + cmd = sbp2util_find_command_for_SCpnt(lu, SCpnt); + if (cmd) { + sbp2util_mark_command_completed(lu, cmd); + if (cmd->Current_SCpnt) { + cmd->Current_SCpnt->result = DID_ABORT << 16; + cmd->Current_done(cmd->Current_SCpnt); + } + } + spin_unlock_irqrestore(&lu->cmd_orb_lock, flags); + + sbp2scsi_complete_all_commands(lu, DID_BUS_BUSY); + } + + return SUCCESS; +} + +/* + * Called by scsi stack when something has really gone wrong. + */ +static int sbp2scsi_reset(struct scsi_cmnd *SCpnt) +{ + struct sbp2_lu *lu = (struct sbp2_lu *)SCpnt->device->host->hostdata[0]; + + SBP2_INFO("reset requested"); + + if (sbp2util_node_is_available(lu)) { + SBP2_INFO("generating sbp2 fetch agent reset"); + sbp2_agent_reset(lu, 1); + } + + return SUCCESS; +} + +static ssize_t sbp2_sysfs_ieee1394_id_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct scsi_device *sdev; + struct sbp2_lu *lu; + + if (!(sdev = to_scsi_device(dev))) + return 0; + + if (!(lu = (struct sbp2_lu *)sdev->host->hostdata[0])) + return 0; + + if (sbp2_long_sysfs_ieee1394_id) + return sprintf(buf, "%016Lx:%06x:%04x\n", + (unsigned long long)lu->ne->guid, + lu->ud->directory_id, ORB_SET_LUN(lu->lun)); + else + return sprintf(buf, "%016Lx:%d:%d\n", + (unsigned long long)lu->ne->guid, + lu->ud->id, ORB_SET_LUN(lu->lun)); +} + +MODULE_AUTHOR("Ben Collins <bcollins@debian.org>"); +MODULE_DESCRIPTION("IEEE-1394 SBP-2 protocol driver"); +MODULE_SUPPORTED_DEVICE(SBP2_DEVICE_NAME); +MODULE_LICENSE("GPL"); + +static int sbp2_module_init(void) +{ + int ret; + + if (sbp2_serialize_io) { + sbp2_shost_template.can_queue = 1; + sbp2_shost_template.cmd_per_lun = 1; + } + + sbp2_shost_template.max_sectors = sbp2_max_sectors; + + hpsb_register_highlevel(&sbp2_highlevel); + ret = hpsb_register_protocol(&sbp2_driver); + if (ret) { + SBP2_ERR("Failed to register protocol"); + hpsb_unregister_highlevel(&sbp2_highlevel); + return ret; + } + return 0; +} + +static void __exit sbp2_module_exit(void) +{ + hpsb_unregister_protocol(&sbp2_driver); + hpsb_unregister_highlevel(&sbp2_highlevel); +} + +module_init(sbp2_module_init); +module_exit(sbp2_module_exit); diff --git a/drivers/ieee1394/sbp2.h b/drivers/ieee1394/sbp2.h new file mode 100644 index 0000000..c5036f1 --- /dev/null +++ b/drivers/ieee1394/sbp2.h @@ -0,0 +1,340 @@ +/* + * sbp2.h - Defines and prototypes for sbp2.c + * + * Copyright (C) 2000 James Goodwin, Filanet Corporation (www.filanet.com) + * jamesg@filanet.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef SBP2_H +#define SBP2_H + +#define SBP2_DEVICE_NAME "sbp2" + +/* + * SBP-2 specific definitions + */ + +#define ORB_DIRECTION_WRITE_TO_MEDIA 0x0 +#define ORB_DIRECTION_READ_FROM_MEDIA 0x1 +#define ORB_DIRECTION_NO_DATA_TRANSFER 0x2 + +#define ORB_SET_NULL_PTR(v) (((v) & 0x1) << 31) +#define ORB_SET_NOTIFY(v) (((v) & 0x1) << 31) +#define ORB_SET_RQ_FMT(v) (((v) & 0x3) << 29) +#define ORB_SET_NODE_ID(v) (((v) & 0xffff) << 16) +#define ORB_SET_STATUS_FIFO_HI(v, id) ((v) >> 32 | ORB_SET_NODE_ID(id)) +#define ORB_SET_STATUS_FIFO_LO(v) ((v) & 0xffffffff) +#define ORB_SET_DATA_SIZE(v) ((v) & 0xffff) +#define ORB_SET_PAGE_SIZE(v) (((v) & 0x7) << 16) +#define ORB_SET_PAGE_TABLE_PRESENT(v) (((v) & 0x1) << 19) +#define ORB_SET_MAX_PAYLOAD(v) (((v) & 0xf) << 20) +#define ORB_SET_SPEED(v) (((v) & 0x7) << 24) +#define ORB_SET_DIRECTION(v) (((v) & 0x1) << 27) + +struct sbp2_command_orb { + u32 next_ORB_hi; + u32 next_ORB_lo; + u32 data_descriptor_hi; + u32 data_descriptor_lo; + u32 misc; + u8 cdb[12]; +} __attribute__((packed)); + +#define SBP2_LOGIN_REQUEST 0x0 +#define SBP2_QUERY_LOGINS_REQUEST 0x1 +#define SBP2_RECONNECT_REQUEST 0x3 +#define SBP2_SET_PASSWORD_REQUEST 0x4 +#define SBP2_LOGOUT_REQUEST 0x7 +#define SBP2_ABORT_TASK_REQUEST 0xb +#define SBP2_ABORT_TASK_SET 0xc +#define SBP2_LOGICAL_UNIT_RESET 0xe +#define SBP2_TARGET_RESET_REQUEST 0xf + +#define ORB_SET_LUN(v) ((v) & 0xffff) +#define ORB_SET_FUNCTION(v) (((v) & 0xf) << 16) +#define ORB_SET_RECONNECT(v) (((v) & 0xf) << 20) +#define ORB_SET_EXCLUSIVE(v) ((v) ? 1 << 28 : 0) +#define ORB_SET_LOGIN_RESP_LENGTH(v) ((v) & 0xffff) +#define ORB_SET_PASSWD_LENGTH(v) (((v) & 0xffff) << 16) + +struct sbp2_login_orb { + u32 password_hi; + u32 password_lo; + u32 login_response_hi; + u32 login_response_lo; + u32 lun_misc; + u32 passwd_resp_lengths; + u32 status_fifo_hi; + u32 status_fifo_lo; +} __attribute__((packed)); + +#define RESPONSE_GET_LOGIN_ID(v) ((v) & 0xffff) +#define RESPONSE_GET_LENGTH(v) (((v) >> 16) & 0xffff) +#define RESPONSE_GET_RECONNECT_HOLD(v) ((v) & 0xffff) + +struct sbp2_login_response { + u32 length_login_ID; + u32 command_block_agent_hi; + u32 command_block_agent_lo; + u32 reconnect_hold; +} __attribute__((packed)); + +#define ORB_SET_LOGIN_ID(v) ((v) & 0xffff) +#define ORB_SET_QUERY_LOGINS_RESP_LENGTH(v) ((v) & 0xffff) + +struct sbp2_query_logins_orb { + u32 reserved1; + u32 reserved2; + u32 query_response_hi; + u32 query_response_lo; + u32 lun_misc; + u32 reserved_resp_length; + u32 status_fifo_hi; + u32 status_fifo_lo; +} __attribute__((packed)); + +#define RESPONSE_GET_MAX_LOGINS(v) ((v) & 0xffff) +#define RESPONSE_GET_ACTIVE_LOGINS(v) ((RESPONSE_GET_LENGTH((v)) - 4) / 12) + +struct sbp2_query_logins_response { + u32 length_max_logins; + u32 misc_IDs; + u32 initiator_misc_hi; + u32 initiator_misc_lo; +} __attribute__((packed)); + +struct sbp2_reconnect_orb { + u32 reserved1; + u32 reserved2; + u32 reserved3; + u32 reserved4; + u32 login_ID_misc; + u32 reserved5; + u32 status_fifo_hi; + u32 status_fifo_lo; +} __attribute__((packed)); + +struct sbp2_logout_orb { + u32 reserved1; + u32 reserved2; + u32 reserved3; + u32 reserved4; + u32 login_ID_misc; + u32 reserved5; + u32 status_fifo_hi; + u32 status_fifo_lo; +} __attribute__((packed)); + +struct sbp2_unrestricted_page_table { + __be32 high; + __be32 low; +}; + +#define RESP_STATUS_REQUEST_COMPLETE 0x0 +#define RESP_STATUS_TRANSPORT_FAILURE 0x1 +#define RESP_STATUS_ILLEGAL_REQUEST 0x2 +#define RESP_STATUS_VENDOR_DEPENDENT 0x3 + +#define SBP2_STATUS_NO_ADDITIONAL_INFO 0x0 +#define SBP2_STATUS_REQ_TYPE_NOT_SUPPORTED 0x1 +#define SBP2_STATUS_SPEED_NOT_SUPPORTED 0x2 +#define SBP2_STATUS_PAGE_SIZE_NOT_SUPPORTED 0x3 +#define SBP2_STATUS_ACCESS_DENIED 0x4 +#define SBP2_STATUS_LU_NOT_SUPPORTED 0x5 +#define SBP2_STATUS_MAX_PAYLOAD_TOO_SMALL 0x6 +#define SBP2_STATUS_RESOURCES_UNAVAILABLE 0x8 +#define SBP2_STATUS_FUNCTION_REJECTED 0x9 +#define SBP2_STATUS_LOGIN_ID_NOT_RECOGNIZED 0xa +#define SBP2_STATUS_DUMMY_ORB_COMPLETED 0xb +#define SBP2_STATUS_REQUEST_ABORTED 0xc +#define SBP2_STATUS_UNSPECIFIED_ERROR 0xff + +#define SFMT_CURRENT_ERROR 0x0 +#define SFMT_DEFERRED_ERROR 0x1 +#define SFMT_VENDOR_DEPENDENT_STATUS 0x3 + +#define STATUS_GET_SRC(v) (((v) >> 30) & 0x3) +#define STATUS_GET_RESP(v) (((v) >> 28) & 0x3) +#define STATUS_GET_LEN(v) (((v) >> 24) & 0x7) +#define STATUS_GET_SBP_STATUS(v) (((v) >> 16) & 0xff) +#define STATUS_GET_ORB_OFFSET_HI(v) ((v) & 0x0000ffff) +#define STATUS_TEST_DEAD(v) ((v) & 0x08000000) +/* test 'resp' | 'dead' | 'sbp2_status' */ +#define STATUS_TEST_RDS(v) ((v) & 0x38ff0000) + +struct sbp2_status_block { + u32 ORB_offset_hi_misc; + u32 ORB_offset_lo; + u8 command_set_dependent[24]; +} __attribute__((packed)); + + +/* + * SBP2 related configuration ROM definitions + */ + +#define SBP2_UNIT_DIRECTORY_OFFSET_KEY 0xd1 +#define SBP2_CSR_OFFSET_KEY 0x54 +#define SBP2_UNIT_SPEC_ID_KEY 0x12 +#define SBP2_UNIT_SW_VERSION_KEY 0x13 +#define SBP2_COMMAND_SET_SPEC_ID_KEY 0x38 +#define SBP2_COMMAND_SET_KEY 0x39 +#define SBP2_UNIT_CHARACTERISTICS_KEY 0x3a +#define SBP2_DEVICE_TYPE_AND_LUN_KEY 0x14 +#define SBP2_FIRMWARE_REVISION_KEY 0x3c + +#define SBP2_AGENT_STATE_OFFSET 0x00ULL +#define SBP2_AGENT_RESET_OFFSET 0x04ULL +#define SBP2_ORB_POINTER_OFFSET 0x08ULL +#define SBP2_DOORBELL_OFFSET 0x10ULL +#define SBP2_UNSOLICITED_STATUS_ENABLE_OFFSET 0x14ULL +#define SBP2_UNSOLICITED_STATUS_VALUE 0xf + +#define SBP2_BUSY_TIMEOUT_ADDRESS 0xfffff0000210ULL +/* biggest possible value for Single Phase Retry count is 0xf */ +#define SBP2_BUSY_TIMEOUT_VALUE 0xf + +#define SBP2_AGENT_RESET_DATA 0xf + +#define SBP2_UNIT_SPEC_ID_ENTRY 0x0000609e +#define SBP2_SW_VERSION_ENTRY 0x00010483 + +/* + * The default maximum s/g segment size of a FireWire controller is + * usually 0x10000, but SBP-2 only allows 0xffff. Since buffers have to + * be quadlet-aligned, we set the length limit to 0xffff & ~3. + */ +#define SBP2_MAX_SEG_SIZE 0xfffc + +/* + * There is no real limitation of the queue depth (i.e. length of the linked + * list of command ORBs) at the target. The chosen depth is merely an + * implementation detail of the sbp2 driver. + */ +#define SBP2_MAX_CMDS 8 + +#define SBP2_SCSI_STATUS_GOOD 0x0 +#define SBP2_SCSI_STATUS_CHECK_CONDITION 0x2 +#define SBP2_SCSI_STATUS_CONDITION_MET 0x4 +#define SBP2_SCSI_STATUS_BUSY 0x8 +#define SBP2_SCSI_STATUS_RESERVATION_CONFLICT 0x18 +#define SBP2_SCSI_STATUS_COMMAND_TERMINATED 0x22 +#define SBP2_SCSI_STATUS_SELECTION_TIMEOUT 0xff + + +/* + * Representations of commands and devices + */ + +/* Per SCSI command */ +struct sbp2_command_info { + struct list_head list; + struct sbp2_command_orb command_orb; + dma_addr_t command_orb_dma; + struct scsi_cmnd *Current_SCpnt; + void (*Current_done)(struct scsi_cmnd *); + + /* Also need s/g structure for each sbp2 command */ + struct sbp2_unrestricted_page_table + scatter_gather_element[SG_ALL] __attribute__((aligned(8))); + dma_addr_t sge_dma; +}; + +/* Per FireWire host */ +struct sbp2_fwhost_info { + struct hpsb_host *host; + struct list_head logical_units; +}; + +/* Per logical unit */ +struct sbp2_lu { + /* Operation request blocks */ + struct sbp2_command_orb *last_orb; + dma_addr_t last_orb_dma; + struct sbp2_login_orb *login_orb; + dma_addr_t login_orb_dma; + struct sbp2_login_response *login_response; + dma_addr_t login_response_dma; + struct sbp2_query_logins_orb *query_logins_orb; + dma_addr_t query_logins_orb_dma; + struct sbp2_query_logins_response *query_logins_response; + dma_addr_t query_logins_response_dma; + struct sbp2_reconnect_orb *reconnect_orb; + dma_addr_t reconnect_orb_dma; + struct sbp2_logout_orb *logout_orb; + dma_addr_t logout_orb_dma; + struct sbp2_status_block status_block; + + /* How to talk to the unit */ + u64 management_agent_addr; + u64 command_block_agent_addr; + u32 speed_code; + u32 max_payload_size; + u16 lun; + + /* Address for the unit to write status blocks to */ + u64 status_fifo_addr; + + /* Waitqueue flag for logins, reconnects, logouts, query logins */ + unsigned int access_complete:1; + + /* Pool of command ORBs for this logical unit */ + spinlock_t cmd_orb_lock; + struct list_head cmd_orb_inuse; + struct list_head cmd_orb_completed; + + /* Backlink to FireWire host; list of units attached to the host */ + struct sbp2_fwhost_info *hi; + struct list_head lu_list; + + /* IEEE 1394 core's device representations */ + struct node_entry *ne; + struct unit_directory *ud; + + /* SCSI core's device representations */ + struct scsi_device *sdev; + struct Scsi_Host *shost; + + /* Device specific workarounds/brokeness */ + unsigned workarounds; + + /* Connection state */ + atomic_t state; + + /* For deferred requests to the fetch agent */ + struct work_struct protocol_work; +}; + +/* For use in sbp2_lu.state */ +enum sbp2lu_state_types { + SBP2LU_STATE_RUNNING, /* all normal */ + SBP2LU_STATE_IN_RESET, /* between bus reset and reconnect */ + SBP2LU_STATE_IN_SHUTDOWN /* when sbp2_remove was called */ +}; + +/* For use in sbp2_lu.workarounds and in the corresponding + * module load parameter */ +#define SBP2_WORKAROUND_128K_MAX_TRANS 0x1 +#define SBP2_WORKAROUND_INQUIRY_36 0x2 +#define SBP2_WORKAROUND_MODE_SENSE_8 0x4 +#define SBP2_WORKAROUND_FIX_CAPACITY 0x8 +#define SBP2_WORKAROUND_DELAY_INQUIRY 0x10 +#define SBP2_INQUIRY_DELAY 12 +#define SBP2_WORKAROUND_POWER_CONDITION 0x20 +#define SBP2_WORKAROUND_OVERRIDE 0x100 + +#endif /* SBP2_H */ diff --git a/drivers/ieee1394/video1394.c b/drivers/ieee1394/video1394.c new file mode 100644 index 0000000..679a918 --- /dev/null +++ b/drivers/ieee1394/video1394.c @@ -0,0 +1,1531 @@ +/* + * video1394.c - video driver for OHCI 1394 boards + * Copyright (C)1999,2000 Sebastien Rougeaux <sebastien.rougeaux@anu.edu.au> + * Peter Schlaile <udbz@rz.uni-karlsruhe.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * NOTES: + * + * ioctl return codes: + * EFAULT is only for invalid address for the argp + * EINVAL for out of range values + * EBUSY when trying to use an already used resource + * ESRCH when trying to free/stop a not used resource + * EAGAIN for resource allocation failure that could perhaps succeed later + * ENOTTY for unsupported ioctl request + * + */ +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/delay.h> +#include <linux/bitops.h> +#include <linux/types.h> +#include <linux/vmalloc.h> +#include <linux/timex.h> +#include <linux/mm.h> +#include <linux/compat.h> +#include <linux/cdev.h> + +#include "dma.h" +#include "highlevel.h" +#include "hosts.h" +#include "ieee1394.h" +#include "ieee1394_core.h" +#include "ieee1394_hotplug.h" +#include "ieee1394_types.h" +#include "nodemgr.h" +#include "ohci1394.h" +#include "video1394.h" + +#define ISO_CHANNELS 64 + +struct it_dma_prg { + struct dma_cmd begin; + quadlet_t data[4]; + struct dma_cmd end; + quadlet_t pad[4]; /* FIXME: quick hack for memory alignment */ +}; + +struct dma_iso_ctx { + struct ti_ohci *ohci; + int type; /* OHCI_ISO_TRANSMIT or OHCI_ISO_RECEIVE */ + struct ohci1394_iso_tasklet iso_tasklet; + int channel; + int ctx; + int last_buffer; + int * next_buffer; /* For ISO Transmit of video packets + to write the correct SYT field + into the next block */ + unsigned int num_desc; + unsigned int buf_size; + unsigned int frame_size; + unsigned int packet_size; + unsigned int left_size; + unsigned int nb_cmd; + + struct dma_region dma; + + struct dma_prog_region *prg_reg; + + struct dma_cmd **ir_prg; + struct it_dma_prg **it_prg; + + unsigned int *buffer_status; + unsigned int *buffer_prg_assignment; + struct timeval *buffer_time; /* time when the buffer was received */ + unsigned int *last_used_cmd; /* For ISO Transmit with + variable sized packets only ! */ + int ctrlClear; + int ctrlSet; + int cmdPtr; + int ctxMatch; + wait_queue_head_t waitq; + spinlock_t lock; + unsigned int syt_offset; + int flags; + + struct list_head link; +}; + + +struct file_ctx { + struct ti_ohci *ohci; + struct list_head context_list; + struct dma_iso_ctx *current_ctx; +}; + +#ifdef CONFIG_IEEE1394_VERBOSEDEBUG +#define VIDEO1394_DEBUG +#endif + +#ifdef DBGMSG +#undef DBGMSG +#endif + +#ifdef VIDEO1394_DEBUG +#define DBGMSG(card, fmt, args...) \ +printk(KERN_INFO "video1394_%d: " fmt "\n" , card , ## args) +#else +#define DBGMSG(card, fmt, args...) do {} while (0) +#endif + +/* print general (card independent) information */ +#define PRINT_G(level, fmt, args...) \ +printk(level "video1394: " fmt "\n" , ## args) + +/* print card specific information */ +#define PRINT(level, card, fmt, args...) \ +printk(level "video1394_%d: " fmt "\n" , card , ## args) + +static void wakeup_dma_ir_ctx(unsigned long l); +static void wakeup_dma_it_ctx(unsigned long l); + +static struct hpsb_highlevel video1394_highlevel; + +static int free_dma_iso_ctx(struct dma_iso_ctx *d) +{ + int i; + + DBGMSG(d->ohci->host->id, "Freeing dma_iso_ctx %d", d->ctx); + + ohci1394_stop_context(d->ohci, d->ctrlClear, NULL); + if (d->iso_tasklet.link.next != NULL) + ohci1394_unregister_iso_tasklet(d->ohci, &d->iso_tasklet); + + dma_region_free(&d->dma); + + if (d->prg_reg) { + for (i = 0; i < d->num_desc; i++) + dma_prog_region_free(&d->prg_reg[i]); + kfree(d->prg_reg); + } + + kfree(d->ir_prg); + kfree(d->it_prg); + kfree(d->buffer_status); + kfree(d->buffer_prg_assignment); + kfree(d->buffer_time); + kfree(d->last_used_cmd); + kfree(d->next_buffer); + list_del(&d->link); + kfree(d); + + return 0; +} + +static struct dma_iso_ctx * +alloc_dma_iso_ctx(struct ti_ohci *ohci, int type, int num_desc, + int buf_size, int channel, unsigned int packet_size) +{ + struct dma_iso_ctx *d; + int i; + + d = kzalloc(sizeof(*d), GFP_KERNEL); + if (!d) { + PRINT(KERN_ERR, ohci->host->id, "Failed to allocate dma_iso_ctx"); + return NULL; + } + + d->ohci = ohci; + d->type = type; + d->channel = channel; + d->num_desc = num_desc; + d->frame_size = buf_size; + d->buf_size = PAGE_ALIGN(buf_size); + d->last_buffer = -1; + INIT_LIST_HEAD(&d->link); + init_waitqueue_head(&d->waitq); + + /* Init the regions for easy cleanup */ + dma_region_init(&d->dma); + + if (dma_region_alloc(&d->dma, (d->num_desc - 1) * d->buf_size, ohci->dev, + PCI_DMA_BIDIRECTIONAL)) { + PRINT(KERN_ERR, ohci->host->id, "Failed to allocate dma buffer"); + free_dma_iso_ctx(d); + return NULL; + } + + if (type == OHCI_ISO_RECEIVE) + ohci1394_init_iso_tasklet(&d->iso_tasklet, type, + wakeup_dma_ir_ctx, + (unsigned long) d); + else + ohci1394_init_iso_tasklet(&d->iso_tasklet, type, + wakeup_dma_it_ctx, + (unsigned long) d); + + if (ohci1394_register_iso_tasklet(ohci, &d->iso_tasklet) < 0) { + PRINT(KERN_ERR, ohci->host->id, "no free iso %s contexts", + type == OHCI_ISO_RECEIVE ? "receive" : "transmit"); + free_dma_iso_ctx(d); + return NULL; + } + d->ctx = d->iso_tasklet.context; + + d->prg_reg = kmalloc(d->num_desc * sizeof(*d->prg_reg), GFP_KERNEL); + if (!d->prg_reg) { + PRINT(KERN_ERR, ohci->host->id, "Failed to allocate ir prg regs"); + free_dma_iso_ctx(d); + return NULL; + } + /* Makes for easier cleanup */ + for (i = 0; i < d->num_desc; i++) + dma_prog_region_init(&d->prg_reg[i]); + + if (type == OHCI_ISO_RECEIVE) { + d->ctrlSet = OHCI1394_IsoRcvContextControlSet+32*d->ctx; + d->ctrlClear = OHCI1394_IsoRcvContextControlClear+32*d->ctx; + d->cmdPtr = OHCI1394_IsoRcvCommandPtr+32*d->ctx; + d->ctxMatch = OHCI1394_IsoRcvContextMatch+32*d->ctx; + + d->ir_prg = kzalloc(d->num_desc * sizeof(*d->ir_prg), + GFP_KERNEL); + + if (!d->ir_prg) { + PRINT(KERN_ERR, ohci->host->id, "Failed to allocate dma ir prg"); + free_dma_iso_ctx(d); + return NULL; + } + + d->nb_cmd = d->buf_size / PAGE_SIZE + 1; + d->left_size = (d->frame_size % PAGE_SIZE) ? + d->frame_size % PAGE_SIZE : PAGE_SIZE; + + for (i = 0;i < d->num_desc; i++) { + if (dma_prog_region_alloc(&d->prg_reg[i], d->nb_cmd * + sizeof(struct dma_cmd), ohci->dev)) { + PRINT(KERN_ERR, ohci->host->id, "Failed to allocate dma ir prg"); + free_dma_iso_ctx(d); + return NULL; + } + d->ir_prg[i] = (struct dma_cmd *)d->prg_reg[i].kvirt; + } + + } else { /* OHCI_ISO_TRANSMIT */ + d->ctrlSet = OHCI1394_IsoXmitContextControlSet+16*d->ctx; + d->ctrlClear = OHCI1394_IsoXmitContextControlClear+16*d->ctx; + d->cmdPtr = OHCI1394_IsoXmitCommandPtr+16*d->ctx; + + d->it_prg = kzalloc(d->num_desc * sizeof(*d->it_prg), + GFP_KERNEL); + + if (!d->it_prg) { + PRINT(KERN_ERR, ohci->host->id, + "Failed to allocate dma it prg"); + free_dma_iso_ctx(d); + return NULL; + } + + d->packet_size = packet_size; + + if (PAGE_SIZE % packet_size || packet_size>4096) { + PRINT(KERN_ERR, ohci->host->id, + "Packet size %d (page_size: %ld) " + "not yet supported\n", + packet_size, PAGE_SIZE); + free_dma_iso_ctx(d); + return NULL; + } + + d->nb_cmd = d->frame_size / d->packet_size; + if (d->frame_size % d->packet_size) { + d->nb_cmd++; + d->left_size = d->frame_size % d->packet_size; + } else + d->left_size = d->packet_size; + + for (i = 0; i < d->num_desc; i++) { + if (dma_prog_region_alloc(&d->prg_reg[i], d->nb_cmd * + sizeof(struct it_dma_prg), ohci->dev)) { + PRINT(KERN_ERR, ohci->host->id, "Failed to allocate dma it prg"); + free_dma_iso_ctx(d); + return NULL; + } + d->it_prg[i] = (struct it_dma_prg *)d->prg_reg[i].kvirt; + } + } + + d->buffer_status = + kzalloc(d->num_desc * sizeof(*d->buffer_status), GFP_KERNEL); + d->buffer_prg_assignment = + kzalloc(d->num_desc * sizeof(*d->buffer_prg_assignment), GFP_KERNEL); + d->buffer_time = + kzalloc(d->num_desc * sizeof(*d->buffer_time), GFP_KERNEL); + d->last_used_cmd = + kzalloc(d->num_desc * sizeof(*d->last_used_cmd), GFP_KERNEL); + d->next_buffer = + kzalloc(d->num_desc * sizeof(*d->next_buffer), GFP_KERNEL); + + if (!d->buffer_status || !d->buffer_prg_assignment || !d->buffer_time || + !d->last_used_cmd || !d->next_buffer) { + PRINT(KERN_ERR, ohci->host->id, + "Failed to allocate dma_iso_ctx member"); + free_dma_iso_ctx(d); + return NULL; + } + + spin_lock_init(&d->lock); + + DBGMSG(ohci->host->id, "Iso %s DMA: %d buffers " + "of size %d allocated for a frame size %d, each with %d prgs", + (type == OHCI_ISO_RECEIVE) ? "receive" : "transmit", + d->num_desc - 1, d->buf_size, d->frame_size, d->nb_cmd); + + return d; +} + +static void reset_ir_status(struct dma_iso_ctx *d, int n) +{ + int i; + d->ir_prg[n][0].status = cpu_to_le32(4); + d->ir_prg[n][1].status = cpu_to_le32(PAGE_SIZE-4); + for (i = 2; i < d->nb_cmd - 1; i++) + d->ir_prg[n][i].status = cpu_to_le32(PAGE_SIZE); + d->ir_prg[n][i].status = cpu_to_le32(d->left_size); +} + +static void reprogram_dma_ir_prg(struct dma_iso_ctx *d, int n, int buffer, int flags) +{ + struct dma_cmd *ir_prg = d->ir_prg[n]; + unsigned long buf = (unsigned long)d->dma.kvirt + buffer * d->buf_size; + int i; + + d->buffer_prg_assignment[n] = buffer; + + ir_prg[0].address = cpu_to_le32(dma_region_offset_to_bus(&d->dma, buf - + (unsigned long)d->dma.kvirt)); + ir_prg[1].address = cpu_to_le32(dma_region_offset_to_bus(&d->dma, + (buf + 4) - (unsigned long)d->dma.kvirt)); + + for (i=2;i<d->nb_cmd-1;i++) { + ir_prg[i].address = cpu_to_le32(dma_region_offset_to_bus(&d->dma, + (buf+(i-1)*PAGE_SIZE) - + (unsigned long)d->dma.kvirt)); + } + + ir_prg[i].control = cpu_to_le32(DMA_CTL_INPUT_MORE | DMA_CTL_UPDATE | + DMA_CTL_IRQ | DMA_CTL_BRANCH | d->left_size); + ir_prg[i].address = cpu_to_le32(dma_region_offset_to_bus(&d->dma, + (buf+(i-1)*PAGE_SIZE) - (unsigned long)d->dma.kvirt)); +} + +static void initialize_dma_ir_prg(struct dma_iso_ctx *d, int n, int flags) +{ + struct dma_cmd *ir_prg = d->ir_prg[n]; + struct dma_prog_region *ir_reg = &d->prg_reg[n]; + unsigned long buf = (unsigned long)d->dma.kvirt; + int i; + + /* the first descriptor will read only 4 bytes */ + ir_prg[0].control = cpu_to_le32(DMA_CTL_INPUT_MORE | DMA_CTL_UPDATE | + DMA_CTL_BRANCH | 4); + + /* set the sync flag */ + if (flags & VIDEO1394_SYNC_FRAMES) + ir_prg[0].control |= cpu_to_le32(DMA_CTL_WAIT); + + ir_prg[0].address = cpu_to_le32(dma_region_offset_to_bus(&d->dma, buf - + (unsigned long)d->dma.kvirt)); + ir_prg[0].branchAddress = cpu_to_le32((dma_prog_region_offset_to_bus(ir_reg, + 1 * sizeof(struct dma_cmd)) & 0xfffffff0) | 0x1); + + /* If there is *not* only one DMA page per frame (hence, d->nb_cmd==2) */ + if (d->nb_cmd > 2) { + /* The second descriptor will read PAGE_SIZE-4 bytes */ + ir_prg[1].control = cpu_to_le32(DMA_CTL_INPUT_MORE | DMA_CTL_UPDATE | + DMA_CTL_BRANCH | (PAGE_SIZE-4)); + ir_prg[1].address = cpu_to_le32(dma_region_offset_to_bus(&d->dma, (buf + 4) - + (unsigned long)d->dma.kvirt)); + ir_prg[1].branchAddress = cpu_to_le32((dma_prog_region_offset_to_bus(ir_reg, + 2 * sizeof(struct dma_cmd)) & 0xfffffff0) | 0x1); + + for (i = 2; i < d->nb_cmd - 1; i++) { + ir_prg[i].control = cpu_to_le32(DMA_CTL_INPUT_MORE | DMA_CTL_UPDATE | + DMA_CTL_BRANCH | PAGE_SIZE); + ir_prg[i].address = cpu_to_le32(dma_region_offset_to_bus(&d->dma, + (buf+(i-1)*PAGE_SIZE) - + (unsigned long)d->dma.kvirt)); + + ir_prg[i].branchAddress = + cpu_to_le32((dma_prog_region_offset_to_bus(ir_reg, + (i + 1) * sizeof(struct dma_cmd)) & 0xfffffff0) | 0x1); + } + + /* The last descriptor will generate an interrupt */ + ir_prg[i].control = cpu_to_le32(DMA_CTL_INPUT_MORE | DMA_CTL_UPDATE | + DMA_CTL_IRQ | DMA_CTL_BRANCH | d->left_size); + ir_prg[i].address = cpu_to_le32(dma_region_offset_to_bus(&d->dma, + (buf+(i-1)*PAGE_SIZE) - + (unsigned long)d->dma.kvirt)); + } else { + /* Only one DMA page is used. Read d->left_size immediately and */ + /* generate an interrupt as this is also the last page. */ + ir_prg[1].control = cpu_to_le32(DMA_CTL_INPUT_MORE | DMA_CTL_UPDATE | + DMA_CTL_IRQ | DMA_CTL_BRANCH | (d->left_size-4)); + ir_prg[1].address = cpu_to_le32(dma_region_offset_to_bus(&d->dma, + (buf + 4) - (unsigned long)d->dma.kvirt)); + } +} + +static void initialize_dma_ir_ctx(struct dma_iso_ctx *d, int tag, int flags) +{ + struct ti_ohci *ohci = (struct ti_ohci *)d->ohci; + int i; + + d->flags = flags; + + ohci1394_stop_context(ohci, d->ctrlClear, NULL); + + for (i=0;i<d->num_desc;i++) { + initialize_dma_ir_prg(d, i, flags); + reset_ir_status(d, i); + } + + /* reset the ctrl register */ + reg_write(ohci, d->ctrlClear, 0xf0000000); + + /* Set bufferFill */ + reg_write(ohci, d->ctrlSet, 0x80000000); + + /* Set isoch header */ + if (flags & VIDEO1394_INCLUDE_ISO_HEADERS) + reg_write(ohci, d->ctrlSet, 0x40000000); + + /* Set the context match register to match on all tags, + sync for sync tag, and listen to d->channel */ + reg_write(ohci, d->ctxMatch, 0xf0000000|((tag&0xf)<<8)|d->channel); + + /* Set up isoRecvIntMask to generate interrupts */ + reg_write(ohci, OHCI1394_IsoRecvIntMaskSet, 1<<d->ctx); +} + +/* find which context is listening to this channel */ +static struct dma_iso_ctx * +find_ctx(struct list_head *list, int type, int channel) +{ + struct dma_iso_ctx *ctx; + + list_for_each_entry(ctx, list, link) { + if (ctx->type == type && ctx->channel == channel) + return ctx; + } + + return NULL; +} + +static void wakeup_dma_ir_ctx(unsigned long l) +{ + struct dma_iso_ctx *d = (struct dma_iso_ctx *) l; + int i; + + spin_lock(&d->lock); + + for (i = 0; i < d->num_desc; i++) { + if (d->ir_prg[i][d->nb_cmd-1].status & cpu_to_le32(0xFFFF0000)) { + reset_ir_status(d, i); + d->buffer_status[d->buffer_prg_assignment[i]] = VIDEO1394_BUFFER_READY; + do_gettimeofday(&d->buffer_time[d->buffer_prg_assignment[i]]); + dma_region_sync_for_cpu(&d->dma, + d->buffer_prg_assignment[i] * d->buf_size, + d->buf_size); + } + } + + spin_unlock(&d->lock); + + if (waitqueue_active(&d->waitq)) + wake_up_interruptible(&d->waitq); +} + +static inline void put_timestamp(struct ti_ohci *ohci, struct dma_iso_ctx * d, + int n) +{ + unsigned char* buf = d->dma.kvirt + n * d->buf_size; + u32 cycleTimer; + u32 timeStamp; + + if (n == -1) { + return; + } + + cycleTimer = reg_read(ohci, OHCI1394_IsochronousCycleTimer); + + timeStamp = ((cycleTimer & 0x0fff) + d->syt_offset); /* 11059 = 450 us */ + timeStamp = (timeStamp % 3072 + ((timeStamp / 3072) << 12) + + (cycleTimer & 0xf000)) & 0xffff; + + buf[6] = timeStamp >> 8; + buf[7] = timeStamp & 0xff; + + /* if first packet is empty packet, then put timestamp into the next full one too */ + if ( (le32_to_cpu(d->it_prg[n][0].data[1]) >>16) == 0x008) { + buf += d->packet_size; + buf[6] = timeStamp >> 8; + buf[7] = timeStamp & 0xff; + } + + /* do the next buffer frame too in case of irq latency */ + n = d->next_buffer[n]; + if (n == -1) { + return; + } + buf = d->dma.kvirt + n * d->buf_size; + + timeStamp += (d->last_used_cmd[n] << 12) & 0xffff; + + buf[6] = timeStamp >> 8; + buf[7] = timeStamp & 0xff; + + /* if first packet is empty packet, then put timestamp into the next full one too */ + if ( (le32_to_cpu(d->it_prg[n][0].data[1]) >>16) == 0x008) { + buf += d->packet_size; + buf[6] = timeStamp >> 8; + buf[7] = timeStamp & 0xff; + } + +#if 0 + printk("curr: %d, next: %d, cycleTimer: %08x timeStamp: %08x\n", + curr, n, cycleTimer, timeStamp); +#endif +} + +static void wakeup_dma_it_ctx(unsigned long l) +{ + struct dma_iso_ctx *d = (struct dma_iso_ctx *) l; + struct ti_ohci *ohci = d->ohci; + int i; + + spin_lock(&d->lock); + + for (i = 0; i < d->num_desc; i++) { + if (d->it_prg[i][d->last_used_cmd[i]].end.status & + cpu_to_le32(0xFFFF0000)) { + int next = d->next_buffer[i]; + put_timestamp(ohci, d, next); + d->it_prg[i][d->last_used_cmd[i]].end.status = 0; + d->buffer_status[d->buffer_prg_assignment[i]] = VIDEO1394_BUFFER_READY; + } + } + + spin_unlock(&d->lock); + + if (waitqueue_active(&d->waitq)) + wake_up_interruptible(&d->waitq); +} + +static void reprogram_dma_it_prg(struct dma_iso_ctx *d, int n, int buffer) +{ + struct it_dma_prg *it_prg = d->it_prg[n]; + unsigned long buf = (unsigned long)d->dma.kvirt + buffer * d->buf_size; + int i; + + d->buffer_prg_assignment[n] = buffer; + for (i=0;i<d->nb_cmd;i++) { + it_prg[i].end.address = + cpu_to_le32(dma_region_offset_to_bus(&d->dma, + (buf+i*d->packet_size) - (unsigned long)d->dma.kvirt)); + } +} + +static void initialize_dma_it_prg(struct dma_iso_ctx *d, int n, int sync_tag) +{ + struct it_dma_prg *it_prg = d->it_prg[n]; + struct dma_prog_region *it_reg = &d->prg_reg[n]; + unsigned long buf = (unsigned long)d->dma.kvirt; + int i; + d->last_used_cmd[n] = d->nb_cmd - 1; + for (i=0;i<d->nb_cmd;i++) { + + it_prg[i].begin.control = cpu_to_le32(DMA_CTL_OUTPUT_MORE | + DMA_CTL_IMMEDIATE | 8) ; + it_prg[i].begin.address = 0; + + it_prg[i].begin.status = 0; + + it_prg[i].data[0] = cpu_to_le32( + (IEEE1394_SPEED_100 << 16) + | (/* tag */ 1 << 14) + | (d->channel << 8) + | (TCODE_ISO_DATA << 4)); + if (i==0) it_prg[i].data[0] |= cpu_to_le32(sync_tag); + it_prg[i].data[1] = cpu_to_le32(d->packet_size << 16); + it_prg[i].data[2] = 0; + it_prg[i].data[3] = 0; + + it_prg[i].end.control = cpu_to_le32(DMA_CTL_OUTPUT_LAST | + DMA_CTL_BRANCH); + it_prg[i].end.address = + cpu_to_le32(dma_region_offset_to_bus(&d->dma, (buf+i*d->packet_size) - + (unsigned long)d->dma.kvirt)); + + if (i<d->nb_cmd-1) { + it_prg[i].end.control |= cpu_to_le32(d->packet_size); + it_prg[i].begin.branchAddress = + cpu_to_le32((dma_prog_region_offset_to_bus(it_reg, (i + 1) * + sizeof(struct it_dma_prg)) & 0xfffffff0) | 0x3); + it_prg[i].end.branchAddress = + cpu_to_le32((dma_prog_region_offset_to_bus(it_reg, (i + 1) * + sizeof(struct it_dma_prg)) & 0xfffffff0) | 0x3); + } else { + /* the last prg generates an interrupt */ + it_prg[i].end.control |= cpu_to_le32(DMA_CTL_UPDATE | + DMA_CTL_IRQ | d->left_size); + /* the last prg doesn't branch */ + it_prg[i].begin.branchAddress = 0; + it_prg[i].end.branchAddress = 0; + } + it_prg[i].end.status = 0; + } +} + +static void initialize_dma_it_prg_var_packet_queue( + struct dma_iso_ctx *d, int n, unsigned int * packet_sizes, + struct ti_ohci *ohci) +{ + struct it_dma_prg *it_prg = d->it_prg[n]; + struct dma_prog_region *it_reg = &d->prg_reg[n]; + int i; + +#if 0 + if (n != -1) { + put_timestamp(ohci, d, n); + } +#endif + d->last_used_cmd[n] = d->nb_cmd - 1; + + for (i = 0; i < d->nb_cmd; i++) { + unsigned int size; + if (packet_sizes[i] > d->packet_size) { + size = d->packet_size; + } else { + size = packet_sizes[i]; + } + it_prg[i].data[1] = cpu_to_le32(size << 16); + it_prg[i].end.control = cpu_to_le32(DMA_CTL_OUTPUT_LAST | DMA_CTL_BRANCH); + + if (i < d->nb_cmd-1 && packet_sizes[i+1] != 0) { + it_prg[i].end.control |= cpu_to_le32(size); + it_prg[i].begin.branchAddress = + cpu_to_le32((dma_prog_region_offset_to_bus(it_reg, (i + 1) * + sizeof(struct it_dma_prg)) & 0xfffffff0) | 0x3); + it_prg[i].end.branchAddress = + cpu_to_le32((dma_prog_region_offset_to_bus(it_reg, (i + 1) * + sizeof(struct it_dma_prg)) & 0xfffffff0) | 0x3); + } else { + /* the last prg generates an interrupt */ + it_prg[i].end.control |= cpu_to_le32(DMA_CTL_UPDATE | + DMA_CTL_IRQ | size); + /* the last prg doesn't branch */ + it_prg[i].begin.branchAddress = 0; + it_prg[i].end.branchAddress = 0; + d->last_used_cmd[n] = i; + break; + } + } +} + +static void initialize_dma_it_ctx(struct dma_iso_ctx *d, int sync_tag, + unsigned int syt_offset, int flags) +{ + struct ti_ohci *ohci = (struct ti_ohci *)d->ohci; + int i; + + d->flags = flags; + d->syt_offset = (syt_offset == 0 ? 11000 : syt_offset); + + ohci1394_stop_context(ohci, d->ctrlClear, NULL); + + for (i=0;i<d->num_desc;i++) + initialize_dma_it_prg(d, i, sync_tag); + + /* Set up isoRecvIntMask to generate interrupts */ + reg_write(ohci, OHCI1394_IsoXmitIntMaskSet, 1<<d->ctx); +} + +static inline unsigned video1394_buffer_state(struct dma_iso_ctx *d, + unsigned int buffer) +{ + unsigned long flags; + unsigned int ret; + spin_lock_irqsave(&d->lock, flags); + ret = d->buffer_status[buffer]; + spin_unlock_irqrestore(&d->lock, flags); + return ret; +} + +static long video1394_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct file_ctx *ctx = (struct file_ctx *)file->private_data; + struct ti_ohci *ohci = ctx->ohci; + unsigned long flags; + void __user *argp = (void __user *)arg; + + switch(cmd) + { + case VIDEO1394_IOC_LISTEN_CHANNEL: + case VIDEO1394_IOC_TALK_CHANNEL: + { + struct video1394_mmap v; + u64 mask; + struct dma_iso_ctx *d; + int i; + + if (copy_from_user(&v, argp, sizeof(v))) + return -EFAULT; + + /* if channel < 0, find lowest available one */ + if (v.channel < 0) { + mask = (u64)0x1; + for (i=0; ; i++) { + if (i == ISO_CHANNELS) { + PRINT(KERN_ERR, ohci->host->id, + "No free channel found"); + return -EAGAIN; + } + if (!(ohci->ISO_channel_usage & mask)) { + v.channel = i; + PRINT(KERN_INFO, ohci->host->id, "Found free channel %d", i); + break; + } + mask = mask << 1; + } + } else if (v.channel >= ISO_CHANNELS) { + PRINT(KERN_ERR, ohci->host->id, + "Iso channel %d out of bounds", v.channel); + return -EINVAL; + } else { + mask = (u64)0x1<<v.channel; + } + DBGMSG(ohci->host->id, "mask: %08X%08X usage: %08X%08X\n", + (u32)(mask>>32),(u32)(mask&0xffffffff), + (u32)(ohci->ISO_channel_usage>>32), + (u32)(ohci->ISO_channel_usage&0xffffffff)); + if (ohci->ISO_channel_usage & mask) { + PRINT(KERN_ERR, ohci->host->id, + "Channel %d is already taken", v.channel); + return -EBUSY; + } + + if (v.buf_size == 0 || v.buf_size > VIDEO1394_MAX_SIZE) { + PRINT(KERN_ERR, ohci->host->id, + "Invalid %d length buffer requested",v.buf_size); + return -EINVAL; + } + + if (v.nb_buffers == 0 || v.nb_buffers > VIDEO1394_MAX_SIZE) { + PRINT(KERN_ERR, ohci->host->id, + "Invalid %d buffers requested",v.nb_buffers); + return -EINVAL; + } + + if (v.nb_buffers * v.buf_size > VIDEO1394_MAX_SIZE) { + PRINT(KERN_ERR, ohci->host->id, + "%d buffers of size %d bytes is too big", + v.nb_buffers, v.buf_size); + return -EINVAL; + } + + if (cmd == VIDEO1394_IOC_LISTEN_CHANNEL) { + d = alloc_dma_iso_ctx(ohci, OHCI_ISO_RECEIVE, + v.nb_buffers + 1, v.buf_size, + v.channel, 0); + + if (d == NULL) { + PRINT(KERN_ERR, ohci->host->id, + "Couldn't allocate ir context"); + return -EAGAIN; + } + initialize_dma_ir_ctx(d, v.sync_tag, v.flags); + + ctx->current_ctx = d; + + v.buf_size = d->buf_size; + list_add_tail(&d->link, &ctx->context_list); + + DBGMSG(ohci->host->id, + "iso context %d listen on channel %d", + d->ctx, v.channel); + } + else { + d = alloc_dma_iso_ctx(ohci, OHCI_ISO_TRANSMIT, + v.nb_buffers + 1, v.buf_size, + v.channel, v.packet_size); + + if (d == NULL) { + PRINT(KERN_ERR, ohci->host->id, + "Couldn't allocate it context"); + return -EAGAIN; + } + initialize_dma_it_ctx(d, v.sync_tag, + v.syt_offset, v.flags); + + ctx->current_ctx = d; + + v.buf_size = d->buf_size; + + list_add_tail(&d->link, &ctx->context_list); + + DBGMSG(ohci->host->id, + "Iso context %d talk on channel %d", d->ctx, + v.channel); + } + + if (copy_to_user(argp, &v, sizeof(v))) { + /* FIXME : free allocated dma resources */ + return -EFAULT; + } + + ohci->ISO_channel_usage |= mask; + + return 0; + } + case VIDEO1394_IOC_UNLISTEN_CHANNEL: + case VIDEO1394_IOC_UNTALK_CHANNEL: + { + int channel; + u64 mask; + struct dma_iso_ctx *d; + + if (copy_from_user(&channel, argp, sizeof(int))) + return -EFAULT; + + if (channel < 0 || channel >= ISO_CHANNELS) { + PRINT(KERN_ERR, ohci->host->id, + "Iso channel %d out of bound", channel); + return -EINVAL; + } + mask = (u64)0x1<<channel; + if (!(ohci->ISO_channel_usage & mask)) { + PRINT(KERN_ERR, ohci->host->id, + "Channel %d is not being used", channel); + return -ESRCH; + } + + /* Mark this channel as unused */ + ohci->ISO_channel_usage &= ~mask; + + if (cmd == VIDEO1394_IOC_UNLISTEN_CHANNEL) + d = find_ctx(&ctx->context_list, OHCI_ISO_RECEIVE, channel); + else + d = find_ctx(&ctx->context_list, OHCI_ISO_TRANSMIT, channel); + + if (d == NULL) return -ESRCH; + DBGMSG(ohci->host->id, "Iso context %d " + "stop talking on channel %d", d->ctx, channel); + free_dma_iso_ctx(d); + + return 0; + } + case VIDEO1394_IOC_LISTEN_QUEUE_BUFFER: + { + struct video1394_wait v; + struct dma_iso_ctx *d; + int next_prg; + + if (unlikely(copy_from_user(&v, argp, sizeof(v)))) + return -EFAULT; + + d = find_ctx(&ctx->context_list, OHCI_ISO_RECEIVE, v.channel); + if (unlikely(d == NULL)) + return -EFAULT; + + if (unlikely(v.buffer >= d->num_desc - 1)) { + PRINT(KERN_ERR, ohci->host->id, + "Buffer %d out of range",v.buffer); + return -EINVAL; + } + + spin_lock_irqsave(&d->lock,flags); + + if (unlikely(d->buffer_status[v.buffer]==VIDEO1394_BUFFER_QUEUED)) { + PRINT(KERN_ERR, ohci->host->id, + "Buffer %d is already used",v.buffer); + spin_unlock_irqrestore(&d->lock,flags); + return -EBUSY; + } + + d->buffer_status[v.buffer]=VIDEO1394_BUFFER_QUEUED; + + next_prg = (d->last_buffer + 1) % d->num_desc; + if (d->last_buffer>=0) + d->ir_prg[d->last_buffer][d->nb_cmd-1].branchAddress = + cpu_to_le32((dma_prog_region_offset_to_bus(&d->prg_reg[next_prg], 0) + & 0xfffffff0) | 0x1); + + d->last_buffer = next_prg; + reprogram_dma_ir_prg(d, d->last_buffer, v.buffer, d->flags); + + d->ir_prg[d->last_buffer][d->nb_cmd-1].branchAddress = 0; + + spin_unlock_irqrestore(&d->lock,flags); + + if (!(reg_read(ohci, d->ctrlSet) & 0x8000)) + { + DBGMSG(ohci->host->id, "Starting iso DMA ctx=%d",d->ctx); + + /* Tell the controller where the first program is */ + reg_write(ohci, d->cmdPtr, + dma_prog_region_offset_to_bus(&d->prg_reg[d->last_buffer], 0) | 0x1); + + /* Run IR context */ + reg_write(ohci, d->ctrlSet, 0x8000); + } + else { + /* Wake up dma context if necessary */ + if (!(reg_read(ohci, d->ctrlSet) & 0x400)) { + DBGMSG(ohci->host->id, + "Waking up iso dma ctx=%d", d->ctx); + reg_write(ohci, d->ctrlSet, 0x1000); + } + } + return 0; + + } + case VIDEO1394_IOC_LISTEN_WAIT_BUFFER: + case VIDEO1394_IOC_LISTEN_POLL_BUFFER: + { + struct video1394_wait v; + struct dma_iso_ctx *d; + int i = 0; + + if (unlikely(copy_from_user(&v, argp, sizeof(v)))) + return -EFAULT; + + d = find_ctx(&ctx->context_list, OHCI_ISO_RECEIVE, v.channel); + if (unlikely(d == NULL)) + return -EFAULT; + + if (unlikely(v.buffer > d->num_desc - 1)) { + PRINT(KERN_ERR, ohci->host->id, + "Buffer %d out of range",v.buffer); + return -EINVAL; + } + + /* + * I change the way it works so that it returns + * the last received frame. + */ + spin_lock_irqsave(&d->lock, flags); + switch(d->buffer_status[v.buffer]) { + case VIDEO1394_BUFFER_READY: + d->buffer_status[v.buffer]=VIDEO1394_BUFFER_FREE; + break; + case VIDEO1394_BUFFER_QUEUED: + if (cmd == VIDEO1394_IOC_LISTEN_POLL_BUFFER) { + /* for polling, return error code EINTR */ + spin_unlock_irqrestore(&d->lock, flags); + return -EINTR; + } + + spin_unlock_irqrestore(&d->lock, flags); + wait_event_interruptible(d->waitq, + video1394_buffer_state(d, v.buffer) == + VIDEO1394_BUFFER_READY); + if (signal_pending(current)) + return -EINTR; + spin_lock_irqsave(&d->lock, flags); + d->buffer_status[v.buffer]=VIDEO1394_BUFFER_FREE; + break; + default: + PRINT(KERN_ERR, ohci->host->id, + "Buffer %d is not queued",v.buffer); + spin_unlock_irqrestore(&d->lock, flags); + return -ESRCH; + } + + /* set time of buffer */ + v.filltime = d->buffer_time[v.buffer]; + + /* + * Look ahead to see how many more buffers have been received + */ + i=0; + while (d->buffer_status[(v.buffer+1)%(d->num_desc - 1)]== + VIDEO1394_BUFFER_READY) { + v.buffer=(v.buffer+1)%(d->num_desc - 1); + i++; + } + spin_unlock_irqrestore(&d->lock, flags); + + v.buffer=i; + if (unlikely(copy_to_user(argp, &v, sizeof(v)))) + return -EFAULT; + + return 0; + } + case VIDEO1394_IOC_TALK_QUEUE_BUFFER: + { + struct video1394_wait v; + unsigned int *psizes = NULL; + struct dma_iso_ctx *d; + int next_prg; + + if (copy_from_user(&v, argp, sizeof(v))) + return -EFAULT; + + d = find_ctx(&ctx->context_list, OHCI_ISO_TRANSMIT, v.channel); + if (d == NULL) return -EFAULT; + + if (v.buffer >= d->num_desc - 1) { + PRINT(KERN_ERR, ohci->host->id, + "Buffer %d out of range",v.buffer); + return -EINVAL; + } + + if (d->flags & VIDEO1394_VARIABLE_PACKET_SIZE) { + int buf_size = d->nb_cmd * sizeof(*psizes); + struct video1394_queue_variable __user *p = argp; + unsigned int __user *qv; + + if (get_user(qv, &p->packet_sizes)) + return -EFAULT; + + psizes = kmalloc(buf_size, GFP_KERNEL); + if (!psizes) + return -ENOMEM; + + if (copy_from_user(psizes, qv, buf_size)) { + kfree(psizes); + return -EFAULT; + } + } + + spin_lock_irqsave(&d->lock,flags); + + /* last_buffer is last_prg */ + next_prg = (d->last_buffer + 1) % d->num_desc; + if (d->buffer_status[v.buffer]!=VIDEO1394_BUFFER_FREE) { + PRINT(KERN_ERR, ohci->host->id, + "Buffer %d is already used",v.buffer); + spin_unlock_irqrestore(&d->lock,flags); + kfree(psizes); + return -EBUSY; + } + + if (d->flags & VIDEO1394_VARIABLE_PACKET_SIZE) { + initialize_dma_it_prg_var_packet_queue( + d, next_prg, psizes, ohci); + } + + d->buffer_status[v.buffer]=VIDEO1394_BUFFER_QUEUED; + + if (d->last_buffer >= 0) { + d->it_prg[d->last_buffer] + [ d->last_used_cmd[d->last_buffer] ].end.branchAddress = + cpu_to_le32((dma_prog_region_offset_to_bus(&d->prg_reg[next_prg], + 0) & 0xfffffff0) | 0x3); + + d->it_prg[d->last_buffer] + [ d->last_used_cmd[d->last_buffer] ].begin.branchAddress = + cpu_to_le32((dma_prog_region_offset_to_bus(&d->prg_reg[next_prg], + 0) & 0xfffffff0) | 0x3); + d->next_buffer[d->last_buffer] = (v.buffer + 1) % (d->num_desc - 1); + } + d->last_buffer = next_prg; + reprogram_dma_it_prg(d, d->last_buffer, v.buffer); + d->next_buffer[d->last_buffer] = -1; + + d->it_prg[d->last_buffer][d->last_used_cmd[d->last_buffer]].end.branchAddress = 0; + + spin_unlock_irqrestore(&d->lock,flags); + + if (!(reg_read(ohci, d->ctrlSet) & 0x8000)) + { + DBGMSG(ohci->host->id, "Starting iso transmit DMA ctx=%d", + d->ctx); + put_timestamp(ohci, d, d->last_buffer); + dma_region_sync_for_device(&d->dma, + v.buffer * d->buf_size, d->buf_size); + + /* Tell the controller where the first program is */ + reg_write(ohci, d->cmdPtr, + dma_prog_region_offset_to_bus(&d->prg_reg[next_prg], 0) | 0x3); + + /* Run IT context */ + reg_write(ohci, d->ctrlSet, 0x8000); + } + else { + /* Wake up dma context if necessary */ + if (!(reg_read(ohci, d->ctrlSet) & 0x400)) { + DBGMSG(ohci->host->id, + "Waking up iso transmit dma ctx=%d", + d->ctx); + put_timestamp(ohci, d, d->last_buffer); + dma_region_sync_for_device(&d->dma, + v.buffer * d->buf_size, d->buf_size); + + reg_write(ohci, d->ctrlSet, 0x1000); + } + } + + kfree(psizes); + return 0; + + } + case VIDEO1394_IOC_TALK_WAIT_BUFFER: + { + struct video1394_wait v; + struct dma_iso_ctx *d; + + if (copy_from_user(&v, argp, sizeof(v))) + return -EFAULT; + + d = find_ctx(&ctx->context_list, OHCI_ISO_TRANSMIT, v.channel); + if (d == NULL) return -EFAULT; + + if (v.buffer >= d->num_desc - 1) { + PRINT(KERN_ERR, ohci->host->id, + "Buffer %d out of range",v.buffer); + return -EINVAL; + } + + switch(d->buffer_status[v.buffer]) { + case VIDEO1394_BUFFER_READY: + d->buffer_status[v.buffer]=VIDEO1394_BUFFER_FREE; + return 0; + case VIDEO1394_BUFFER_QUEUED: + wait_event_interruptible(d->waitq, + (d->buffer_status[v.buffer] == VIDEO1394_BUFFER_READY)); + if (signal_pending(current)) + return -EINTR; + d->buffer_status[v.buffer]=VIDEO1394_BUFFER_FREE; + return 0; + default: + PRINT(KERN_ERR, ohci->host->id, + "Buffer %d is not queued",v.buffer); + return -ESRCH; + } + } + default: + return -ENOTTY; + } +} + +/* + * This maps the vmalloced and reserved buffer to user space. + * + * FIXME: + * - PAGE_READONLY should suffice!? + * - remap_pfn_range is kind of inefficient for page by page remapping. + * But e.g. pte_alloc() does not work in modules ... :-( + */ + +static int video1394_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct file_ctx *ctx = (struct file_ctx *)file->private_data; + + if (ctx->current_ctx == NULL) { + PRINT(KERN_ERR, ctx->ohci->host->id, + "Current iso context not set"); + return -EINVAL; + } + + return dma_region_mmap(&ctx->current_ctx->dma, file, vma); +} + +static unsigned int video1394_poll(struct file *file, poll_table *pt) +{ + struct file_ctx *ctx; + unsigned int mask = 0; + unsigned long flags; + struct dma_iso_ctx *d; + int i; + + ctx = file->private_data; + d = ctx->current_ctx; + if (d == NULL) { + PRINT(KERN_ERR, ctx->ohci->host->id, + "Current iso context not set"); + return POLLERR; + } + + poll_wait(file, &d->waitq, pt); + + spin_lock_irqsave(&d->lock, flags); + for (i = 0; i < d->num_desc; i++) { + if (d->buffer_status[i] == VIDEO1394_BUFFER_READY) { + mask |= POLLIN | POLLRDNORM; + break; + } + } + spin_unlock_irqrestore(&d->lock, flags); + + return mask; +} + +static int video1394_open(struct inode *inode, struct file *file) +{ + int i = ieee1394_file_to_instance(file); + struct ti_ohci *ohci; + struct file_ctx *ctx; + + ohci = hpsb_get_hostinfo_bykey(&video1394_highlevel, i); + if (ohci == NULL) + return -EIO; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + PRINT(KERN_ERR, ohci->host->id, "Cannot malloc file_ctx"); + return -ENOMEM; + } + + ctx->ohci = ohci; + INIT_LIST_HEAD(&ctx->context_list); + ctx->current_ctx = NULL; + file->private_data = ctx; + + return 0; +} + +static int video1394_release(struct inode *inode, struct file *file) +{ + struct file_ctx *ctx = (struct file_ctx *)file->private_data; + struct ti_ohci *ohci = ctx->ohci; + struct list_head *lh, *next; + u64 mask; + + list_for_each_safe(lh, next, &ctx->context_list) { + struct dma_iso_ctx *d; + d = list_entry(lh, struct dma_iso_ctx, link); + mask = (u64) 1 << d->channel; + + if (!(ohci->ISO_channel_usage & mask)) + PRINT(KERN_ERR, ohci->host->id, "On release: Channel %d " + "is not being used", d->channel); + else + ohci->ISO_channel_usage &= ~mask; + DBGMSG(ohci->host->id, "On release: Iso %s context " + "%d stop listening on channel %d", + d->type == OHCI_ISO_RECEIVE ? "receive" : "transmit", + d->ctx, d->channel); + free_dma_iso_ctx(d); + } + + kfree(ctx); + file->private_data = NULL; + + return 0; +} + +#ifdef CONFIG_COMPAT +static long video1394_compat_ioctl(struct file *f, unsigned cmd, unsigned long arg); +#endif + +static struct cdev video1394_cdev; +static const struct file_operations video1394_fops= +{ + .owner = THIS_MODULE, + .unlocked_ioctl = video1394_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = video1394_compat_ioctl, +#endif + .poll = video1394_poll, + .mmap = video1394_mmap, + .open = video1394_open, + .release = video1394_release +}; + +/*** HOTPLUG STUFF **********************************************************/ +/* + * Export information about protocols/devices supported by this driver. + */ +#ifdef MODULE +static struct ieee1394_device_id video1394_id_table[] = { + { + .match_flags = IEEE1394_MATCH_SPECIFIER_ID | IEEE1394_MATCH_VERSION, + .specifier_id = CAMERA_UNIT_SPEC_ID_ENTRY & 0xffffff, + .version = CAMERA_SW_VERSION_ENTRY & 0xffffff + }, + { + .match_flags = IEEE1394_MATCH_SPECIFIER_ID | IEEE1394_MATCH_VERSION, + .specifier_id = CAMERA_UNIT_SPEC_ID_ENTRY & 0xffffff, + .version = (CAMERA_SW_VERSION_ENTRY + 1) & 0xffffff + }, + { + .match_flags = IEEE1394_MATCH_SPECIFIER_ID | IEEE1394_MATCH_VERSION, + .specifier_id = CAMERA_UNIT_SPEC_ID_ENTRY & 0xffffff, + .version = (CAMERA_SW_VERSION_ENTRY + 2) & 0xffffff + }, + { } +}; + +MODULE_DEVICE_TABLE(ieee1394, video1394_id_table); +#endif /* MODULE */ + +static struct hpsb_protocol_driver video1394_driver = { + .name = VIDEO1394_DRIVER_NAME, +}; + + +static void video1394_add_host (struct hpsb_host *host) +{ + struct ti_ohci *ohci; + int minor; + + /* We only work with the OHCI-1394 driver */ + if (strcmp(host->driver->name, OHCI1394_DRIVER_NAME)) + return; + + ohci = (struct ti_ohci *)host->hostdata; + + if (!hpsb_create_hostinfo(&video1394_highlevel, host, 0)) { + PRINT(KERN_ERR, ohci->host->id, "Cannot allocate hostinfo"); + return; + } + + hpsb_set_hostinfo(&video1394_highlevel, host, ohci); + hpsb_set_hostinfo_key(&video1394_highlevel, host, ohci->host->id); + + minor = IEEE1394_MINOR_BLOCK_VIDEO1394 * 16 + ohci->host->id; + device_create(hpsb_protocol_class, NULL, MKDEV(IEEE1394_MAJOR, minor), + NULL, "%s-%d", VIDEO1394_DRIVER_NAME, ohci->host->id); +} + + +static void video1394_remove_host (struct hpsb_host *host) +{ + struct ti_ohci *ohci = hpsb_get_hostinfo(&video1394_highlevel, host); + + if (ohci) + device_destroy(hpsb_protocol_class, MKDEV(IEEE1394_MAJOR, + IEEE1394_MINOR_BLOCK_VIDEO1394 * 16 + ohci->host->id)); + return; +} + + +static struct hpsb_highlevel video1394_highlevel = { + .name = VIDEO1394_DRIVER_NAME, + .add_host = video1394_add_host, + .remove_host = video1394_remove_host, +}; + +MODULE_AUTHOR("Sebastien Rougeaux <sebastien.rougeaux@anu.edu.au>"); +MODULE_DESCRIPTION("driver for digital video on OHCI board"); +MODULE_SUPPORTED_DEVICE(VIDEO1394_DRIVER_NAME); +MODULE_LICENSE("GPL"); + +#ifdef CONFIG_COMPAT + +#define VIDEO1394_IOC32_LISTEN_QUEUE_BUFFER \ + _IOW ('#', 0x12, struct video1394_wait32) +#define VIDEO1394_IOC32_LISTEN_WAIT_BUFFER \ + _IOWR('#', 0x13, struct video1394_wait32) +#define VIDEO1394_IOC32_TALK_WAIT_BUFFER \ + _IOW ('#', 0x17, struct video1394_wait32) +#define VIDEO1394_IOC32_LISTEN_POLL_BUFFER \ + _IOWR('#', 0x18, struct video1394_wait32) + +struct video1394_wait32 { + u32 channel; + u32 buffer; + struct compat_timeval filltime; +}; + +static int video1394_wr_wait32(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct video1394_wait32 __user *argp = (void __user *)arg; + struct video1394_wait32 wait32; + struct video1394_wait wait; + mm_segment_t old_fs; + int ret; + + if (copy_from_user(&wait32, argp, sizeof(wait32))) + return -EFAULT; + + wait.channel = wait32.channel; + wait.buffer = wait32.buffer; + wait.filltime.tv_sec = (time_t)wait32.filltime.tv_sec; + wait.filltime.tv_usec = (suseconds_t)wait32.filltime.tv_usec; + + old_fs = get_fs(); + set_fs(KERNEL_DS); + if (cmd == VIDEO1394_IOC32_LISTEN_WAIT_BUFFER) + ret = video1394_ioctl(file, + VIDEO1394_IOC_LISTEN_WAIT_BUFFER, + (unsigned long) &wait); + else + ret = video1394_ioctl(file, + VIDEO1394_IOC_LISTEN_POLL_BUFFER, + (unsigned long) &wait); + set_fs(old_fs); + + if (!ret) { + wait32.channel = wait.channel; + wait32.buffer = wait.buffer; + wait32.filltime.tv_sec = (int)wait.filltime.tv_sec; + wait32.filltime.tv_usec = (int)wait.filltime.tv_usec; + + if (copy_to_user(argp, &wait32, sizeof(wait32))) + ret = -EFAULT; + } + + return ret; +} + +static int video1394_w_wait32(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct video1394_wait32 wait32; + struct video1394_wait wait; + mm_segment_t old_fs; + int ret; + + if (copy_from_user(&wait32, (void __user *)arg, sizeof(wait32))) + return -EFAULT; + + wait.channel = wait32.channel; + wait.buffer = wait32.buffer; + wait.filltime.tv_sec = (time_t)wait32.filltime.tv_sec; + wait.filltime.tv_usec = (suseconds_t)wait32.filltime.tv_usec; + + old_fs = get_fs(); + set_fs(KERNEL_DS); + if (cmd == VIDEO1394_IOC32_LISTEN_QUEUE_BUFFER) + ret = video1394_ioctl(file, + VIDEO1394_IOC_LISTEN_QUEUE_BUFFER, + (unsigned long) &wait); + else + ret = video1394_ioctl(file, + VIDEO1394_IOC_TALK_WAIT_BUFFER, + (unsigned long) &wait); + set_fs(old_fs); + + return ret; +} + +static int video1394_queue_buf32(struct file *file, unsigned int cmd, unsigned long arg) +{ + return -EFAULT; /* ??? was there before. */ + + return video1394_ioctl(file, + VIDEO1394_IOC_TALK_QUEUE_BUFFER, arg); +} + +static long video1394_compat_ioctl(struct file *f, unsigned cmd, unsigned long arg) +{ + switch (cmd) { + case VIDEO1394_IOC_LISTEN_CHANNEL: + case VIDEO1394_IOC_UNLISTEN_CHANNEL: + case VIDEO1394_IOC_TALK_CHANNEL: + case VIDEO1394_IOC_UNTALK_CHANNEL: + return video1394_ioctl(f, cmd, arg); + + case VIDEO1394_IOC32_LISTEN_QUEUE_BUFFER: + return video1394_w_wait32(f, cmd, arg); + case VIDEO1394_IOC32_LISTEN_WAIT_BUFFER: + return video1394_wr_wait32(f, cmd, arg); + case VIDEO1394_IOC_TALK_QUEUE_BUFFER: + return video1394_queue_buf32(f, cmd, arg); + case VIDEO1394_IOC32_TALK_WAIT_BUFFER: + return video1394_w_wait32(f, cmd, arg); + case VIDEO1394_IOC32_LISTEN_POLL_BUFFER: + return video1394_wr_wait32(f, cmd, arg); + default: + return -ENOIOCTLCMD; + } +} + +#endif /* CONFIG_COMPAT */ + +static void __exit video1394_exit_module (void) +{ + hpsb_unregister_protocol(&video1394_driver); + hpsb_unregister_highlevel(&video1394_highlevel); + cdev_del(&video1394_cdev); + PRINT_G(KERN_INFO, "Removed " VIDEO1394_DRIVER_NAME " module"); +} + +static int __init video1394_init_module (void) +{ + int ret; + + hpsb_init_highlevel(&video1394_highlevel); + + cdev_init(&video1394_cdev, &video1394_fops); + video1394_cdev.owner = THIS_MODULE; + ret = cdev_add(&video1394_cdev, IEEE1394_VIDEO1394_DEV, 16); + if (ret) { + PRINT_G(KERN_ERR, "video1394: unable to get minor device block"); + return ret; + } + + hpsb_register_highlevel(&video1394_highlevel); + + ret = hpsb_register_protocol(&video1394_driver); + if (ret) { + PRINT_G(KERN_ERR, "video1394: failed to register protocol"); + hpsb_unregister_highlevel(&video1394_highlevel); + cdev_del(&video1394_cdev); + return ret; + } + + PRINT_G(KERN_INFO, "Installed " VIDEO1394_DRIVER_NAME " module"); + return 0; +} + + +module_init(video1394_init_module); +module_exit(video1394_exit_module); diff --git a/drivers/ieee1394/video1394.h b/drivers/ieee1394/video1394.h new file mode 100644 index 0000000..9a89d9c --- /dev/null +++ b/drivers/ieee1394/video1394.h @@ -0,0 +1,67 @@ +/* + * video1394.h - driver for OHCI 1394 boards + * Copyright (C)1999,2000 Sebastien Rougeaux <sebastien.rougeaux@anu.edu.au> + * Peter Schlaile <udbz@rz.uni-karlsruhe.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _VIDEO_1394_H +#define _VIDEO_1394_H + +#include "ieee1394-ioctl.h" + +#define VIDEO1394_DRIVER_NAME "video1394" + +#define VIDEO1394_MAX_SIZE 0x4000000 + +enum { + VIDEO1394_BUFFER_FREE = 0, + VIDEO1394_BUFFER_QUEUED, + VIDEO1394_BUFFER_READY +}; + +#define VIDEO1394_SYNC_FRAMES 0x00000001 +#define VIDEO1394_INCLUDE_ISO_HEADERS 0x00000002 +#define VIDEO1394_VARIABLE_PACKET_SIZE 0x00000004 + +struct video1394_mmap { + int channel; /* -1 to find an open channel in LISTEN/TALK */ + unsigned int sync_tag; + unsigned int nb_buffers; + unsigned int buf_size; + unsigned int packet_size; /* For VARIABLE_PACKET_SIZE: + Maximum packet size */ + unsigned int fps; + unsigned int syt_offset; + unsigned int flags; +}; + +/* For TALK_QUEUE_BUFFER with VIDEO1394_VARIABLE_PACKET_SIZE use */ +struct video1394_queue_variable { + unsigned int channel; + unsigned int buffer; + unsigned int __user * packet_sizes; /* Buffer of size: + buf_size / packet_size */ +}; + +struct video1394_wait { + unsigned int channel; + unsigned int buffer; + struct timeval filltime; /* time of buffer full */ +}; + + +#endif |