diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/firewire/Makefile | 2 | ||||
-rw-r--r-- | drivers/firewire/core-card.c | 4 | ||||
-rw-r--r-- | drivers/firewire/core-iso.c | 7 | ||||
-rw-r--r-- | drivers/firewire/core.h | 87 | ||||
-rw-r--r-- | drivers/firewire/fw-ipv4.c | 1819 |
5 files changed, 1832 insertions, 87 deletions
diff --git a/drivers/firewire/Makefile b/drivers/firewire/Makefile index bc3b9bf822b..31edf30c558 100644 --- a/drivers/firewire/Makefile +++ b/drivers/firewire/Makefile @@ -6,7 +6,9 @@ firewire-core-y += core-card.o core-cdev.o core-device.o \ core-iso.o core-topology.o core-transaction.o firewire-ohci-y += ohci.o firewire-sbp2-y += sbp2.o +firewire-ipv4-y += fw-ipv4.o obj-$(CONFIG_FIREWIRE) += firewire-core.o obj-$(CONFIG_FIREWIRE_OHCI) += firewire-ohci.o obj-$(CONFIG_FIREWIRE_SBP2) += firewire-sbp2.o +obj-$(CONFIG_FIREWIRE_IPV4) += firewire-ipv4.o diff --git a/drivers/firewire/core-card.c b/drivers/firewire/core-card.c index 4c1be64fddd..cdab32b2067 100644 --- a/drivers/firewire/core-card.c +++ b/drivers/firewire/core-card.c @@ -176,6 +176,7 @@ int fw_core_add_descriptor(struct fw_descriptor *desc) return 0; } +EXPORT_SYMBOL(fw_core_add_descriptor); void fw_core_remove_descriptor(struct fw_descriptor *desc) { @@ -189,6 +190,7 @@ void fw_core_remove_descriptor(struct fw_descriptor *desc) mutex_unlock(&card_mutex); } +EXPORT_SYMBOL(fw_core_remove_descriptor); static void allocate_broadcast_channel(struct fw_card *card, int generation) { @@ -427,6 +429,8 @@ void fw_card_initialize(struct fw_card *card, card->local_node = NULL; INIT_DELAYED_WORK(&card->work, fw_card_bm_work); + card->netdev = NULL; + INIT_LIST_HEAD(&card->ipv4_nodes); } EXPORT_SYMBOL(fw_card_initialize); diff --git a/drivers/firewire/core-iso.c b/drivers/firewire/core-iso.c index 28076c892d7..448ddd7d887 100644 --- a/drivers/firewire/core-iso.c +++ b/drivers/firewire/core-iso.c @@ -80,6 +80,7 @@ int fw_iso_buffer_init(struct fw_iso_buffer *buffer, struct fw_card *card, return -ENOMEM; } +EXPORT_SYMBOL(fw_iso_buffer_init); int fw_iso_buffer_map(struct fw_iso_buffer *buffer, struct vm_area_struct *vma) { @@ -114,6 +115,7 @@ void fw_iso_buffer_destroy(struct fw_iso_buffer *buffer, kfree(buffer->pages); buffer->pages = NULL; } +EXPORT_SYMBOL(fw_iso_buffer_destroy); struct fw_iso_context *fw_iso_context_create(struct fw_card *card, int type, int channel, int speed, size_t header_size, @@ -136,6 +138,7 @@ struct fw_iso_context *fw_iso_context_create(struct fw_card *card, return ctx; } +EXPORT_SYMBOL(fw_iso_context_create); void fw_iso_context_destroy(struct fw_iso_context *ctx) { @@ -143,12 +146,14 @@ void fw_iso_context_destroy(struct fw_iso_context *ctx) card->driver->free_iso_context(ctx); } +EXPORT_SYMBOL(fw_iso_context_destroy); int fw_iso_context_start(struct fw_iso_context *ctx, int cycle, int sync, int tags) { return ctx->card->driver->start_iso(ctx, cycle, sync, tags); } +EXPORT_SYMBOL(fw_iso_context_start); int fw_iso_context_queue(struct fw_iso_context *ctx, struct fw_iso_packet *packet, @@ -159,11 +164,13 @@ int fw_iso_context_queue(struct fw_iso_context *ctx, return card->driver->queue_iso(ctx, packet, buffer, payload); } +EXPORT_SYMBOL(fw_iso_context_queue); int fw_iso_context_stop(struct fw_iso_context *ctx) { return ctx->card->driver->stop_iso(ctx); } +EXPORT_SYMBOL(fw_iso_context_stop); /* * Isochronous bus resource management (channels, bandwidth), client side diff --git a/drivers/firewire/core.h b/drivers/firewire/core.h index 0a25a7b38a8..c3cfc647e5e 100644 --- a/drivers/firewire/core.h +++ b/drivers/firewire/core.h @@ -1,7 +1,6 @@ #ifndef _FIREWIRE_CORE_H #define _FIREWIRE_CORE_H -#include <linux/dma-mapping.h> #include <linux/fs.h> #include <linux/list.h> #include <linux/idr.h> @@ -97,17 +96,6 @@ int fw_core_initiate_bus_reset(struct fw_card *card, int short_reset); int fw_compute_block_crc(u32 *block); void fw_schedule_bm_work(struct fw_card *card, unsigned long delay); -struct fw_descriptor { - struct list_head link; - size_t length; - u32 immediate; - u32 key; - const u32 *data; -}; - -int fw_core_add_descriptor(struct fw_descriptor *desc); -void fw_core_remove_descriptor(struct fw_descriptor *desc); - /* -cdev */ @@ -130,77 +118,7 @@ void fw_node_event(struct fw_card *card, struct fw_node *node, int event); /* -iso */ -/* - * The iso packet format allows for an immediate header/payload part - * stored in 'header' immediately after the packet info plus an - * indirect payload part that is pointer to by the 'payload' field. - * Applications can use one or the other or both to implement simple - * low-bandwidth streaming (e.g. audio) or more advanced - * scatter-gather streaming (e.g. assembling video frame automatically). - */ -struct fw_iso_packet { - u16 payload_length; /* Length of indirect payload. */ - u32 interrupt:1; /* Generate interrupt on this packet */ - u32 skip:1; /* Set to not send packet at all. */ - u32 tag:2; - u32 sy:4; - u32 header_length:8; /* Length of immediate header. */ - u32 header[0]; -}; - -#define FW_ISO_CONTEXT_TRANSMIT 0 -#define FW_ISO_CONTEXT_RECEIVE 1 - -#define FW_ISO_CONTEXT_MATCH_TAG0 1 -#define FW_ISO_CONTEXT_MATCH_TAG1 2 -#define FW_ISO_CONTEXT_MATCH_TAG2 4 -#define FW_ISO_CONTEXT_MATCH_TAG3 8 -#define FW_ISO_CONTEXT_MATCH_ALL_TAGS 15 - -/* - * An iso buffer is just a set of pages mapped for DMA in the - * specified direction. Since the pages are to be used for DMA, they - * are not mapped into the kernel virtual address space. We store the - * DMA address in the page private. The helper function - * fw_iso_buffer_map() will map the pages into a given vma. - */ -struct fw_iso_buffer { - enum dma_data_direction direction; - struct page **pages; - int page_count; -}; - -typedef void (*fw_iso_callback_t)(struct fw_iso_context *context, - u32 cycle, size_t header_length, - void *header, void *data); - -struct fw_iso_context { - struct fw_card *card; - int type; - int channel; - int speed; - size_t header_size; - fw_iso_callback_t callback; - void *callback_data; -}; - -int fw_iso_buffer_init(struct fw_iso_buffer *buffer, struct fw_card *card, - int page_count, enum dma_data_direction direction); int fw_iso_buffer_map(struct fw_iso_buffer *buffer, struct vm_area_struct *vma); -void fw_iso_buffer_destroy(struct fw_iso_buffer *buffer, struct fw_card *card); - -struct fw_iso_context *fw_iso_context_create(struct fw_card *card, - int type, int channel, int speed, size_t header_size, - fw_iso_callback_t callback, void *callback_data); -int fw_iso_context_queue(struct fw_iso_context *ctx, - struct fw_iso_packet *packet, - struct fw_iso_buffer *buffer, - unsigned long payload); -int fw_iso_context_start(struct fw_iso_context *ctx, - int cycle, int sync, int tags); -int fw_iso_context_stop(struct fw_iso_context *ctx); -void fw_iso_context_destroy(struct fw_iso_context *ctx); - void fw_iso_resource_manage(struct fw_card *card, int generation, u64 channels_mask, int *channel, int *bandwidth, bool allocate); @@ -285,9 +203,4 @@ void fw_flush_transactions(struct fw_card *card); void fw_send_phy_config(struct fw_card *card, int node_id, int generation, int gap_count); -static inline int fw_stream_packet_destination_id(int tag, int channel, int sy) -{ - return tag << 14 | channel << 8 | sy; -} - #endif /* _FIREWIRE_CORE_H */ diff --git a/drivers/firewire/fw-ipv4.c b/drivers/firewire/fw-ipv4.c new file mode 100644 index 00000000000..4de6dbb95f0 --- /dev/null +++ b/drivers/firewire/fw-ipv4.c @@ -0,0 +1,1819 @@ +/* + * IPv4 over IEEE 1394, per RFC 2734 + * + * Copyright (C) 2009 Jay Fenlason <fenlason@redhat.com> + * + * based on eth1394 by Ben Collins et al + */ + +#include <linux/device.h> +#include <linux/ethtool.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/highmem.h> +#include <linux/in.h> +#include <linux/ip.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/netdevice.h> +#include <linux/skbuff.h> + +#include <asm/unaligned.h> +#include <net/arp.h> + +/* Things to potentially make runtime cofigurable */ +/* must be at least as large as our maximum receive size */ +#define FIFO_SIZE 4096 +/* Network timeout in glibbles */ +#define IPV4_TIMEOUT 100000 + +/* Runitme configurable paramaters */ +static int ipv4_mpd = 25; +static int ipv4_max_xmt = 0; +/* 16k for receiving arp and broadcast packets. Enough? */ +static int ipv4_iso_page_count = 4; + +MODULE_AUTHOR("Jay Fenlason (fenlason@redhat.com)"); +MODULE_DESCRIPTION("Firewire IPv4 Driver (IPv4-over-IEEE1394 as per RFC 2734)"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(ieee1394, ipv4_id_table); +module_param_named(max_partial_datagrams, ipv4_mpd, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(max_partial_datagrams, "Maximum number of received" + " incomplete fragmented datagrams (default = 25)."); + +/* Max xmt is useful for forcing fragmentation, which makes testing easier. */ +module_param_named(max_transmit, ipv4_max_xmt, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(max_transmit, "Maximum datagram size to transmit" + " (larger datagrams will be fragmented) (default = 0 (use hardware defaults)."); + +/* iso page count controls how many pages will be used for receiving broadcast packets. */ +module_param_named(iso_pages, ipv4_iso_page_count, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(iso_pages, "Number of pages to use for receiving broadcast packets" + " (default = 4)."); + +/* uncomment this line to do debugging */ +#define fw_debug(s, args...) printk(KERN_DEBUG KBUILD_MODNAME ": " s, ## args) + +/* comment out these lines to do debugging. */ +/* #undef fw_debug */ +/* #define fw_debug(s...) */ +/* #define print_hex_dump(l...) */ + +/* 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 IPV4_ALEN (8) +/* This must equal sizeof(struct ipv4_ether_hdr) */ +#define IPV4_HLEN (10) + +/* FIXME: what's a good size for this? */ +#define INVALID_FIFO_ADDR (u64)~0ULL + +/* Things specified by standards */ +#define BROADCAST_CHANNEL 31 + +#define S100_BUFFER_SIZE 512 +#define MAX_BUFFER_SIZE 4096 + +#define IPV4_GASP_SPECIFIER_ID 0x00005EU +#define IPV4_GASP_VERSION 0x00000001U + +#define IPV4_GASP_OVERHEAD (2 * sizeof(u32)) /* for GASP header */ + +#define IPV4_UNFRAG_HDR_SIZE sizeof(u32) +#define IPV4_FRAG_HDR_SIZE (2 * sizeof(u32)) +#define IPV4_FRAG_OVERHEAD sizeof(u32) + +#define ALL_NODES (0xffc0 | 0x003f) + +#define IPV4_HDR_UNFRAG 0 /* unfragmented */ +#define IPV4_HDR_FIRSTFRAG 1 /* first fragment */ +#define IPV4_HDR_LASTFRAG 2 /* last fragment */ +#define IPV4_HDR_INTFRAG 3 /* interior fragment */ + +/* Our arp packet (ARPHRD_IEEE1394) */ +/* FIXME: note that this is probably bogus on weird-endian machines */ +struct ipv4_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 */ +} __attribute__((packed)); + +struct ipv4_ether_hdr { + unsigned char h_dest[IPV4_ALEN]; /* destination address */ + unsigned short h_proto; /* packet type ID field */ +} __attribute__((packed)); + +static inline struct ipv4_ether_hdr *ipv4_ether_hdr(const struct sk_buff *skb) +{ + return (struct ipv4_ether_hdr *)skb_mac_header(skb); +} + +enum ipv4_tx_type { + IPV4_UNKNOWN = 0, + IPV4_GASP = 1, + IPV4_WRREQ = 2, +}; + +enum ipv4_broadcast_state { + IPV4_BROADCAST_ERROR, + IPV4_BROADCAST_RUNNING, + IPV4_BROADCAST_STOPPED, +}; + +#define ipv4_get_hdr_lf(h) (((h)->w0&0xC0000000)>>30) +#define ipv4_get_hdr_ether_type(h) (((h)->w0&0x0000FFFF) ) +#define ipv4_get_hdr_dg_size(h) (((h)->w0&0x0FFF0000)>>16) +#define ipv4_get_hdr_fg_off(h) (((h)->w0&0x00000FFF) ) +#define ipv4_get_hdr_dgl(h) (((h)->w1&0xFFFF0000)>>16) + +#define ipv4_set_hdr_lf(lf) (( lf)<<30) +#define ipv4_set_hdr_ether_type(et) (( et) ) +#define ipv4_set_hdr_dg_size(dgs) ((dgs)<<16) +#define ipv4_set_hdr_fg_off(fgo) ((fgo) ) + +#define ipv4_set_hdr_dgl(dgl) ((dgl)<<16) + +struct ipv4_hdr { + u32 w0; + u32 w1; +}; + +static inline void ipv4_make_uf_hdr( struct ipv4_hdr *hdr, unsigned ether_type) { + hdr->w0 = ipv4_set_hdr_lf(IPV4_HDR_UNFRAG) + |ipv4_set_hdr_ether_type(ether_type); + fw_debug ( "Setting unfragmented header %p to %x\n", hdr, hdr->w0 ); +} + +static inline void ipv4_make_ff_hdr ( struct ipv4_hdr *hdr, unsigned ether_type, unsigned dg_size, unsigned dgl ) { + hdr->w0 = ipv4_set_hdr_lf(IPV4_HDR_FIRSTFRAG) + |ipv4_set_hdr_dg_size(dg_size) + |ipv4_set_hdr_ether_type(ether_type); + hdr->w1 = ipv4_set_hdr_dgl(dgl); + fw_debug ( "Setting fragmented header %p to first_frag %x,%x (et %x, dgs %x, dgl %x)\n", hdr, hdr->w0, hdr->w1, + ether_type, dg_size, dgl ); +} + +static inline void ipv4_make_sf_hdr ( struct ipv4_hdr *hdr, unsigned lf, unsigned dg_size, unsigned fg_off, unsigned dgl) { + hdr->w0 = ipv4_set_hdr_lf(lf) + |ipv4_set_hdr_dg_size(dg_size) + |ipv4_set_hdr_fg_off(fg_off); + hdr->w1 = ipv4_set_hdr_dgl(dgl); + fw_debug ( "Setting fragmented header %p to %x,%x (lf %x, dgs %x, fo %x dgl %x)\n", + hdr, hdr->w0, hdr->w1, + lf, dg_size, fg_off, dgl ); +} + +/* 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 */ + +/* This list keeps track of what parts of the datagram have been filled in */ +struct ipv4_fragment_info { + struct list_head fragment_info; + u16 offset; + u16 len; +}; + +struct ipv4_partial_datagram { + struct list_head pdg_list; + struct list_head fragment_info; + struct sk_buff *skb; + /* FIXME Why not use skb->data? */ + char *pbuf; + u16 datagram_label; + u16 ether_type; + u16 datagram_size; +}; + +/* + * We keep one of these for each IPv4 capable device attached to a fw_card. + * The list of them is stored in the fw_card structure rather than in the + * ipv4_priv because the remote IPv4 nodes may be probed before the card is, + * so we need a place to store them before the ipv4_priv structure is + * allocated. + */ +struct ipv4_node { + struct list_head ipv4_nodes; + /* guid of the remote node */ + u64 guid; + /* FIFO address to transmit datagrams to, or INVALID_FIFO_ADDR */ + u64 fifo; + + spinlock_t pdg_lock; /* partial datagram lock */ + /* List of partial datagrams received from this node */ + struct list_head pdg_list; + /* Number of entries in pdg_list at the moment */ + unsigned pdg_size; + + /* max payload to transmit to this remote node */ + /* This already includes the IPV4_FRAG_HDR_SIZE overhead */ + u16 max_payload; + /* outgoing datagram label */ + u16 datagram_label; + /* Current node_id of the remote node */ + u16 nodeid; + /* current generation of the remote node */ + u8 generation; + /* max speed that this node can receive at */ + u8 xmt_speed; +}; + +struct ipv4_priv { + spinlock_t lock; + + enum ipv4_broadcast_state broadcast_state; + struct fw_iso_context *broadcast_rcv_context; + struct fw_iso_buffer broadcast_rcv_buffer; + void **broadcast_rcv_buffer_ptrs; + unsigned broadcast_rcv_next_ptr; + unsigned num_broadcast_rcv_ptrs; + unsigned rcv_buffer_size; + /* + * This value is the maximum unfragmented datagram size that can be + * sent by the hardware. It already has the GASP overhead and the + * unfragmented datagram header overhead calculated into it. + */ + unsigned broadcast_xmt_max_payload; + u16 broadcast_xmt_datagramlabel; + + /* + * The csr address that remote nodes must send datagrams to for us to + * receive them. + */ + struct fw_address_handler handler; + u64 local_fifo; + + /* Wake up to xmt */ + /* struct work_struct wake;*/ + /* List of packets to be sent */ + struct list_head packet_list; + /* + * List of packets that were broadcasted. When we get an ISO interrupt + * one of them has been sent + */ + struct list_head broadcasted_list; + /* List of packets that have been sent but not yet acked */ + struct list_head sent_list; + + struct fw_card *card; +}; + +/* This is our task struct. It's used for the packet complete callback. */ +struct ipv4_packet_task { + /* + * ptask can actually be on priv->packet_list, priv->broadcasted_list, + * or priv->sent_list depending on its current state. + */ + struct list_head packet_list; + struct fw_transaction transaction; + struct ipv4_hdr hdr; + struct sk_buff *skb; + struct ipv4_priv *priv; + enum ipv4_tx_type tx_type; + int outstanding_pkts; + unsigned max_payload; + u64 fifo_addr; + u16 dest_node; + u8 generation; + u8 speed; +}; + +static struct kmem_cache *ipv4_packet_task_cache; + +static const char ipv4_driver_name[] = "firewire-ipv4"; + +static const struct ieee1394_device_id ipv4_id_table[] = { + { + .match_flags = IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .specifier_id = IPV4_GASP_SPECIFIER_ID, + .version = IPV4_GASP_VERSION, + }, + { } +}; + +static u32 ipv4_unit_directory_data[] = { + 0x00040000, /* unit directory */ + 0x12000000 | IPV4_GASP_SPECIFIER_ID, /* specifier ID */ + 0x81000003, /* text descriptor */ + 0x13000000 | IPV4_GASP_VERSION, /* version */ + 0x81000005, /* text descriptor */ + + 0x00030000, /* Three quadlets */ + 0x00000000, /* Text */ + 0x00000000, /* Language 0 */ + 0x49414e41, /* I A N A */ + 0x00030000, /* Three quadlets */ + 0x00000000, /* Text */ + 0x00000000, /* Language 0 */ + 0x49507634, /* I P v 4 */ +}; + +static struct fw_descriptor ipv4_unit_directory = { + .length = ARRAY_SIZE(ipv4_unit_directory_data), + .key = 0xd1000000, + .data = ipv4_unit_directory_data +}; + +static int ipv4_send_packet(struct ipv4_packet_task *ptask ); + +/* ------------------------------------------------------------------ */ +/****************************************** + * 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 ipv4_header ( struct sk_buff *skb, struct net_device *dev, + unsigned short type, const void *daddr, + const void *saddr, unsigned len) { + struct ipv4_ether_hdr *eth; + + eth = (struct ipv4_ether_hdr *)skb_push(skb, sizeof(*eth)); + 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 ipv4_rebuild_header(struct sk_buff *skb) +{ + struct ipv4_ether_hdr *eth; + + eth = (struct ipv4_ether_hdr *)skb->data; + if (eth->h_proto == htons(ETH_P_IP)) + return arp_find((unsigned char *)ð->h_dest, skb); + + fw_notify ( "%s: unable to resolve type %04x addresses\n", + skb->dev->name,ntohs(eth->h_proto) ); + return 0; +} + +static int ipv4_header_cache(const struct neighbour *neigh, struct hh_cache *hh) { + unsigned short type = hh->hh_type; + struct net_device *dev; + struct ipv4_ether_hdr *eth; + + if (type == htons(ETH_P_802_3)) + return -1; + dev = neigh->dev; + eth = (struct ipv4_ether_hdr *)((u8 *)hh->hh_data + 16 - sizeof(*eth)); + eth->h_proto = type; + memcpy(eth->h_dest, neigh->ha, dev->addr_len); + + hh->hh_len = IPV4_HLEN; + return 0; +} + +/* Called by Address Resolution module to notify changes in address. */ +static void ipv4_header_cache_update(struct hh_cache *hh, const struct net_device *dev, const unsigned char * haddr ) { + memcpy((u8 *)hh->hh_data + 16 - IPV4_HLEN, haddr, dev->addr_len); +} + +static int ipv4_header_parse(const struct sk_buff *skb, unsigned char *haddr) { + memcpy(haddr, skb->dev->dev_addr, IPV4_ALEN); + return IPV4_ALEN; +} + +static const struct header_ops ipv4_header_ops = { + .create = ipv4_header, + .rebuild = ipv4_rebuild_header, + .cache = ipv4_header_cache, + .cache_update = ipv4_header_cache_update, + .parse = ipv4_header_parse, +}; + +/* ------------------------------------------------------------------ */ + +/* FIXME: is this correct for all cases? */ +static bool ipv4_frag_overlap(struct ipv4_partial_datagram *pd, unsigned offset, unsigned len) +{ + struct ipv4_fragment_info *fi; + unsigned end = offset + len; + + list_for_each_entry(fi, &pd->fragment_info, fragment_info) { + if (offset < fi->offset + fi->len && end > fi->offset) { + fw_debug ( "frag_overlap pd %p fi %p (%x@%x) with %x@%x\n", pd, fi, fi->len, fi->offset, len, offset ); + return true; + } + } + fw_debug ( "frag_overlap %p does not overlap with %x@%x\n", pd, len, offset ); + return false; +} + +/* Assumes that new fragment does not overlap any existing fragments */ +static struct ipv4_fragment_info *ipv4_frag_new ( struct ipv4_partial_datagram *pd, unsigned offset, unsigned len ) { + struct ipv4_fragment_info *fi, *fi2, *new; + struct list_head *list; + + fw_debug ( "frag_new pd %p %x@%x\n", pd, len, offset ); + list = &pd->fragment_info; + list_for_each_entry(fi, &pd->fragment_info, fragment_info) { + if (fi->offset + fi->len == offset) { + /* The new fragment can be tacked on to the end */ + /* Did the new fragment plug a hole? */ + fi2 = list_entry(fi->fragment_info.next, struct ipv4_fragment_info, fragment_info); + if (fi->offset + fi->len == fi2->offset) { + fw_debug ( "pd %p: hole filling %p (%x@%x) and %p(%x@%x): now %x@%x\n", pd, fi, fi->len, fi->offset, + fi2, fi2->len, fi2->offset, fi->len + len + fi2->len, fi->offset ); + /* glue fragments together */ + fi->len += len + fi2->len; + list_del(&fi2->fragment_info); + kfree(fi2); + } else { + fw_debug ( "pd %p: extending %p from %x@%x to %x@%x\n", pd, fi, fi->len, fi->offset, fi->len+len, fi->offset ); + fi->len += len; + } + return fi; + } + if (offset + len == fi->offset) { + /* The new fragment can be tacked on to the beginning */ + /* Did the new fragment plug a hole? */ + fi2 = list_entry(fi->fragment_info.prev, struct ipv4_fragment_info, fragment_info); + if (fi2->offset + fi2->len == fi->offset) { + /* glue fragments together */ + fw_debug ( "pd %p: extending %p and merging with %p from %x@%x to %x@%x\n", + pd, fi2, fi, fi2->len, fi2->offset, fi2->len + fi->len + len, fi2->offset ); + fi2->len += fi->len + len; + list_del(&fi->fragment_info); + kfree(fi); + return fi2; + } + fw_debug ( "pd %p: extending %p from %x@%x to %x@%x\n", pd, fi, fi->len, fi->offset, offset, fi->len + len ); + fi->offset = offset; + fi->len += len; + return fi; + } + if (offset > fi->offset + fi->len) { + list = &fi->fragment_info; + break; + } + if (offset + len < fi->offset) { + list = fi->fragment_info.prev; + break; + } + } + + new = kmalloc(sizeof(*new), GFP_ATOMIC); + if (!new) { + fw_error ( "out of memory in fragment handling!\n" ); + return NULL; + } + + new->offset = offset; + new->len = len; + list_add(&new->fragment_info, list); + fw_debug ( "pd %p: new frag %p %x@%x\n", pd, new, new->len, new->offset ); + list_for_each_entry( fi, &pd->fragment_info, fragment_info ) + fw_debug ( "fi %p %x@%x\n", fi, fi->len, fi->offset ); + return new; +} + +/* ------------------------------------------------------------------ */ + +static struct ipv4_partial_datagram *ipv4_pd_new(struct net_device *netdev, + struct ipv4_node *node, u16 datagram_label, unsigned dg_size, u32 *frag_buf, + unsigned frag_off, unsigned frag_len) { + struct ipv4_partial_datagram *new; + struct ipv4_fragment_info *fi; + + new = kmalloc(sizeof(*new), GFP_ATOMIC); + if (!new) + goto fail; + INIT_LIST_HEAD(&new->fragment_info); + fi = ipv4_frag_new ( new, frag_off, frag_len); + if ( fi == NULL ) + goto fail_w_new; + new->datagram_label = datagram_label; + new->datagram_size = dg_size; + new->skb = dev_alloc_skb(dg_size + netdev->hard_header_len + 15); + if ( new->skb == NULL ) + goto fail_w_fi; + skb_reserve(new->skb, (netdev->hard_header_len + 15) & ~15); + new->pbuf = skb_put(new->skb, dg_size); + memcpy(new->pbuf + frag_off, frag_buf, frag_len); + list_add_tail(&new->pdg_list, &node->pdg_list); + fw_debug ( "pd_new: new pd %p { dgl %u, dg_size %u, skb %p, pbuf %p } on node %p\n", + new, new->datagram_label, new->datagram_size, new->skb, new->pbuf, node ); + return new; + +fail_w_fi: + kfree(fi); +fail_w_new: + kfree(new); +fail: + fw_error("ipv4_pd_new: no memory\n"); + return NULL; +} + +static struct ipv4_partial_datagram *ipv4_pd_find(struct ipv4_node *node, u16 datagram_label) { + struct ipv4_partial_datagram *pd; + + list_for_each_entry(pd, &node->pdg_list, pdg_list) { + if ( pd->datagram_label == datagram_label ) { + fw_debug ( "pd_find(node %p, label %u): pd %p\n", node, datagram_label, pd ); + return pd; + } + } + fw_debug ( "pd_find(node %p, label %u) no entry\n", node, datagram_label ); + return NULL; +} + + +static void ipv4_pd_delete ( struct ipv4_partial_datagram *old ) { + struct ipv4_fragment_info *fi, *n; + + fw_debug ( "pd_delete %p\n", old ); + list_for_each_entry_safe(fi, n, &old->fragment_info, fragment_info) { + fw_debug ( "Freeing fi %p\n", fi ); + kfree(fi); + } + list_del(&old->pdg_list); + dev_kfree_skb_any(old->skb); + kfree(old); +} + +static bool ipv4_pd_update ( struct ipv4_node *node, struct ipv4_partial_datagram *pd, + u32 *frag_buf, unsigned frag_off, unsigned frag_len) { + fw_debug ( "pd_update node %p, pd %p, frag_buf %p, %x@%x\n", node, pd, frag_buf, frag_len, frag_off ); + if ( ipv4_frag_new ( pd, frag_off, frag_len ) == NULL) + return false; + 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_tail(&pd->pdg_list, &node->pdg_list); + fw_debug ( "New pd list:\n" ); + list_for_each_entry ( pd, &node->pdg_list, pdg_list ) { + fw_debug ( "pd %p\n", pd ); + } + return true; +} + +static bool ipv4_pd_is_complete ( struct ipv4_partial_datagram *pd ) { + struct ipv4_fragment_info *fi; + bool ret; + + fi = list_entry(pd->fragment_info.next, struct ipv4_fragment_info, fragment_info); + + ret = (fi->len == pd->datagram_size); + fw_debug ( "pd_is_complete (pd %p, dgs %x): fi %p (%x@%x) %s\n", pd, pd->datagram_size, fi, fi->len, fi->offset, ret ? "yes" : "no" ); + return ret; +} + +/* ------------------------------------------------------------------ */ + +static int ipv4_node_new ( struct fw_card *card, struct fw_device *device ) { + struct ipv4_node *node; + + node = kmalloc ( sizeof(*node), GFP_KERNEL ); + if ( ! node ) { + fw_error ( "allocate new node failed\n" ); + return -ENOMEM; + } + node->guid = (u64)device->config_rom[3] << 32 | device->config_rom[4]; + node->fifo = INVALID_FIFO_ADDR; + INIT_LIST_HEAD(&node->pdg_list); + spin_lock_init(&node->pdg_lock); + node->pdg_size = 0; + node->generation = device->generation; + rmb(); + node->nodeid = device->node_id; + /* FIXME what should it really be? */ + node->max_payload = S100_BUFFER_SIZE - IPV4_UNFRAG_HDR_SIZE; + node->datagram_label = 0U; + node->xmt_speed = device->max_speed; + list_add_tail ( &node->ipv4_nodes, &card->ipv4_nodes ); + fw_debug ( "node_new: %p { guid %016llx, generation %u, nodeid %x, max_payload %x, xmt_speed %x } added\n", + node, (unsigned long long)node->guid, node->generation, node->nodeid, node->max_payload, node->xmt_speed ); + return 0; +} + +static struct ipv4_node *ipv4_node_find_by_guid(struct ipv4_priv *priv, u64 guid) { + struct ipv4_node *node; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + list_for_each_entry(node, &priv->card->ipv4_nodes, ipv4_nodes) + if (node->guid == guid) { + /* FIXME: lock the node first? */ + spin_unlock_irqrestore ( &priv->lock, flags ); + fw_debug ( "node_find_by_guid (%016llx) found %p\n", (unsigned long long)guid, node ); + return node; + } + + spin_unlock_irqrestore ( &priv->lock, flags ); + fw_debug ( "node_find_by_guid (%016llx) not found\n", (unsigned long long)guid ); + return NULL; +} + +static struct ipv4_node *ipv4_node_find_by_nodeid(struct ipv4_priv *priv, u16 nodeid) { + struct ipv4_node *node; + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + list_for_each_entry(node, &priv->card->ipv4_nodes, ipv4_nodes) + if (node->nodeid == nodeid) { + /* FIXME: lock the node first? */ + spin_unlock_irqrestore ( &priv->lock, flags ); + fw_debug ( "node_find_by_nodeid (%x) found %p\n", nodeid, node ); + return node; + } + fw_debug ( "node_find_by_nodeid (%x) not found\n", nodeid ); + spin_unlock_irqrestore ( &priv->lock, flags ); + return NULL; +} + +/* This is only complicated because we can't assume priv exists */ +static void ipv4_node_delete ( struct fw_card *card, struct fw_device *device ) { + struct net_device *netdev; + struct ipv4_priv *priv; + struct ipv4_node *node; + u64 guid; + unsigned long flags; + struct ipv4_partial_datagram *pd, *pd_next; + + guid = (u64)device->config_rom[3] << 32 | device->config_rom[4]; + netdev = card->netdev; + if ( netdev ) + priv = netdev_priv ( netdev ); + else + priv = NULL; + if ( priv ) + spin_lock_irqsave ( &priv->lock, flags ); + list_for_each_entry( node, &card->ipv4_nodes, ipv4_nodes ) { + if ( node->guid == guid ) { + list_del ( &node->ipv4_nodes ); + list_for_each_entry_safe( pd, pd_next, &node->pdg_list, pdg_list ) + ipv4_pd_delete ( pd ); + break; + } + } + if ( priv ) + spin_unlock_irqrestore ( &priv->lock, flags ); +} + +/* ------------------------------------------------------------------ */ + + +static int ipv4_finish_incoming_packet ( struct net_device *netdev, + struct sk_buff *skb, u16 source_node_id, bool is_broadcast, u16 ether_type ) { + struct ipv4_priv *priv; + static u64 broadcast_hw = ~0ULL; + int status; + u64 guid; + + fw_debug ( "ipv4_finish_incoming_packet(%p, %p, %x, %s, %x\n", + netdev, skb, source_node_id, is_broadcast ? "true" : "false", ether_type ); + priv = netdev_priv(netdev); + /* Write metadata, and then pass to the receive level */ + skb->dev = netdev; + 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, as well as arp + * conversion if needed. ARP conversion is easier in this + * direction, since we are using ethernet as our backend. + */ + /* + * 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 == ETH_P_ARP) { + struct ipv4_arp *arp1394; + struct arphdr *arp; + unsigned char *arp_ptr; + u64 fifo_addr; + u8 max_rec; + u8 sspd; + u16 max_payload; + struct ipv4_node *node; + static const u16 ipv4_speed_to_max_payload[] = { + /* S100, S200, S400, S800, S1600, S3200 */ + 512, 1024, 2048, 4096, 4096, 4096 + }; + + /* fw_debug ( "ARP packet\n" ); */ + arp1394 = (struct ipv4_arp *)skb->data; + arp = (struct arphdr *)skb->data; + arp_ptr = (unsigned char *)(arp + 1); + fifo_addr = (u64)ntohs(arp1394->fifo_hi) << 32 | + ntohl(arp1394->fifo_lo); + max_rec = priv->card->max_receive; + if ( arp1394->max_rec < max_rec ) + max_rec = arp1394->max_rec; + sspd = arp1394->sspd; + /* + * Sanity check. MacOSX seems to be sending us 131 in this + * field (atleast on my Panther G5). Not sure why. + */ + if (sspd > 5 ) { + fw_notify ( "sspd %x out of range\n", sspd ); + sspd = 0; + } + + max_payload = min(ipv4_speed_to_max_payload[sspd], + (u16)(1 << (max_rec + 1))) - IPV4_UNFRAG_HDR_SIZE; + + guid = be64_to_cpu(get_unaligned(&arp1394->s_uniq_id)); + node = ipv4_node_find_by_guid(priv, guid); + if (!node) { + fw_notify ( "No node for ARP packet from %llx\n", guid ); + goto failed_proto; + } + if ( node->nodeid != source_node_id || node->generation != priv->card->generation ) { + fw_notify ( "Internal error: node->nodeid (%x) != soucre_node_id (%x) or node->generation (%x) != priv->card->generation(%x)\n", + node->nodeid, source_node_id, node->generation, priv->card->generation ); + node->nodeid = source_node_id; + node->generation = priv->card->generation; + } + + /* FIXME: for debugging */ + if ( sspd > SCODE_400 ) + sspd = SCODE_400; + /* Update our speed/payload/fifo_offset table */ + /* + * FIXME: this does not handle cases where two high-speed endpoints must use a slower speed because of + * a lower speed hub between them. We need to look at the actual topology map here. + */ + fw_debug ( "Setting node %p fifo %llx (was %llx), max_payload %x (was %x), speed %x (was %x)\n", + node, fifo_addr, node->fifo, max_payload, node->max_payload, sspd, node->xmt_speed ); + node->fifo = fifo_addr; + node->max_payload = max_payload; + /* + * Only allow speeds to go down from their initial value. + * Otherwise a local node that can only do S400 or slower may + * be told to transmit at S800 to a faster remote node. + */ + if ( node->xmt_speed > sspd ) + node->xmt_speed = sspd; |