Skip to content
Snippets Groups Projects
net.c 43.2 KiB
Newer Older
	struct fwnet_packet_task *ptask = data;
	static unsigned long j;
	static int last_rcode, errors_skipped;
	if (rcode == RCODE_COMPLETE) {
		fwnet_transmit_packet_done(ptask);
	} else {
		fwnet_transmit_packet_failed(ptask);

		if (printk_timed_ratelimit(&j,  1000) || rcode != last_rcode) {
			fw_error("fwnet_write_complete: "
				"failed: %x (skipped %d)\n", rcode, errors_skipped);

			errors_skipped = 0;
			last_rcode = rcode;
		} else
			errors_skipped++;
static int fwnet_send_packet(struct fwnet_packet_task *ptask)
{
	struct fwnet_device *dev;
Jay Fenlason's avatar
Jay Fenlason committed
	unsigned tx_len;
	struct rfc2734_header *bufhdr;
Jay Fenlason's avatar
Jay Fenlason committed
	unsigned long flags;
	dev = ptask->dev;
Jay Fenlason's avatar
Jay Fenlason committed
	tx_len = ptask->max_payload;
	switch (fwnet_get_hdr_lf(&ptask->hdr)) {
	case RFC2374_HDR_UNFRAG:
		bufhdr = (struct rfc2734_header *)
				skb_push(ptask->skb, RFC2374_UNFRAG_HDR_SIZE);
		put_unaligned_be32(ptask->hdr.w0, &bufhdr->w0);
	case RFC2374_HDR_FIRSTFRAG:
	case RFC2374_HDR_INTFRAG:
	case RFC2374_HDR_LASTFRAG:
		bufhdr = (struct rfc2734_header *)
				skb_push(ptask->skb, RFC2374_FRAG_HDR_SIZE);
		put_unaligned_be32(ptask->hdr.w0, &bufhdr->w0);
		put_unaligned_be32(ptask->hdr.w1, &bufhdr->w1);
Jay Fenlason's avatar
Jay Fenlason committed
		break;

	default:
		BUG();
	}
	if (ptask->dest_node == IEEE1394_ALL_NODES) {
		u8 *p;
Jay Fenlason's avatar
Jay Fenlason committed
		int generation;
		int node_id;
Jay Fenlason's avatar
Jay Fenlason committed

		/* ptask->generation may not have been set yet */
		generation = dev->card->generation;
Jay Fenlason's avatar
Jay Fenlason committed
		smp_rmb();
		node_id = dev->card->node_id;

		p = skb_push(ptask->skb, 8);
		put_unaligned_be32(node_id << 16 | IANA_SPECIFIER_ID >> 8, p);
		put_unaligned_be32((IANA_SPECIFIER_ID & 0xff) << 24
						| RFC2734_SW_VERSION, &p[4]);

		/* We should not transmit if broadcast_channel.valid == 0. */
		fw_send_request(dev->card, &ptask->transaction,
				TCODE_STREAM_DATA,
				fw_stream_packet_destination_id(3,
						IEEE1394_BROADCAST_CHANNEL, 0),
				generation, SCODE_100, 0ULL, ptask->skb->data,
				tx_len + 8, fwnet_write_complete, ptask);

		spin_lock_irqsave(&dev->lock, flags);

		/* If the AT tasklet already ran, we may be last user. */
		free = (ptask->outstanding_pkts == 0 && !ptask->enqueued);
			ptask->enqueued = true;
		else
		spin_unlock_irqrestore(&dev->lock, flags);


	fw_send_request(dev->card, &ptask->transaction,
			TCODE_WRITE_BLOCK_REQUEST, ptask->dest_node,
			ptask->generation, ptask->speed, ptask->fifo_addr,
			ptask->skb->data, tx_len, fwnet_write_complete, ptask);

	spin_lock_irqsave(&dev->lock, flags);

	/* If the AT tasklet already ran, we may be last user. */
	free = (ptask->outstanding_pkts == 0 && !ptask->enqueued);
		ptask->enqueued = true;
	else
	spin_unlock_irqrestore(&dev->lock, flags);

	dev->netdev->trans_start = jiffies;
 out:
	if (free)
		fwnet_free_ptask(ptask);
static int fwnet_broadcast_start(struct fwnet_device *dev)
{
Jay Fenlason's avatar
Jay Fenlason committed
	struct fw_iso_context *context;
	int retval;
	unsigned num_packets;
	unsigned max_receive;
	struct fw_iso_packet packet;
	unsigned long offset;
	unsigned u;

	if (dev->local_fifo == FWNET_NO_FIFO_ADDR) {
		/* outside OHCI posted write area? */
		static const struct fw_address_region region = {
			.start = 0xffff00000000ULL,
			.end   = CSR_REGISTER_BASE,
		};

		dev->handler.length = 4096;
		dev->handler.address_callback = fwnet_receive_packet;
		dev->handler.callback_data = dev;

		retval = fw_core_add_address_handler(&dev->handler, &region);
		if (retval < 0)
Jay Fenlason's avatar
Jay Fenlason committed
			goto failed_initial;

		dev->local_fifo = dev->handler.offset;
	max_receive = 1U << (dev->card->max_receive + 1);
	num_packets = (FWNET_ISO_PAGE_COUNT * PAGE_SIZE) / max_receive;

	if (!dev->broadcast_rcv_context) {
Jay Fenlason's avatar
Jay Fenlason committed
		void **ptrptr;

		context = fw_iso_context_create(dev->card,
		    FW_ISO_CONTEXT_RECEIVE, IEEE1394_BROADCAST_CHANNEL,
		    dev->card->link_speed, 8, fwnet_receive_broadcast, dev);
Jay Fenlason's avatar
Jay Fenlason committed
		if (IS_ERR(context)) {
			retval = PTR_ERR(context);
			goto failed_context_create;
		}

		retval = fw_iso_buffer_init(&dev->broadcast_rcv_buffer,
		    dev->card, FWNET_ISO_PAGE_COUNT, DMA_FROM_DEVICE);
		if (retval < 0)
Jay Fenlason's avatar
Jay Fenlason committed
			goto failed_buffer_init;

		ptrptr = kmalloc(sizeof(void *) * num_packets, GFP_KERNEL);
		if (!ptrptr) {
Jay Fenlason's avatar
Jay Fenlason committed
			retval = -ENOMEM;
			goto failed_ptrs_alloc;
		}

		dev->broadcast_rcv_buffer_ptrs = ptrptr;
		for (u = 0; u < FWNET_ISO_PAGE_COUNT; u++) {
Jay Fenlason's avatar
Jay Fenlason committed
			void *ptr;
			unsigned v;

			ptr = kmap(dev->broadcast_rcv_buffer.pages[u]);
			for (v = 0; v < num_packets / FWNET_ISO_PAGE_COUNT; v++)
				*ptrptr++ = (void *)
						((char *)ptr + v * max_receive);
		dev->broadcast_rcv_context = context;
	} else {
		context = dev->broadcast_rcv_context;
	}
Jay Fenlason's avatar
Jay Fenlason committed

	packet.payload_length = max_receive;
	packet.interrupt = 1;
	packet.skip = 0;
	packet.tag = 3;
	packet.sy = 0;
	packet.header_length = IEEE1394_GASP_HDR_SIZE;
Jay Fenlason's avatar
Jay Fenlason committed
	offset = 0;

	for (u = 0; u < num_packets; u++) {
		retval = fw_iso_context_queue(context, &packet,
				&dev->broadcast_rcv_buffer, offset);
		if (retval < 0)
Jay Fenlason's avatar
Jay Fenlason committed
			goto failed_rcv_queue;
Jay Fenlason's avatar
Jay Fenlason committed
		offset += max_receive;
	}
	dev->num_broadcast_rcv_ptrs = num_packets;
	dev->rcv_buffer_size = max_receive;
	dev->broadcast_rcv_next_ptr = 0U;
	retval = fw_iso_context_start(context, -1, 0,
			FW_ISO_CONTEXT_MATCH_ALL_TAGS); /* ??? sync */
	if (retval < 0)
Jay Fenlason's avatar
Jay Fenlason committed
		goto failed_rcv_queue;

	/* FIXME: adjust it according to the min. speed of all known peers? */
	dev->broadcast_xmt_max_payload = IEEE1394_MAX_PAYLOAD_S100
			- IEEE1394_GASP_HDR_SIZE - RFC2374_UNFRAG_HDR_SIZE;
	dev->broadcast_state = FWNET_BROADCAST_RUNNING;

Jay Fenlason's avatar
Jay Fenlason committed
	return 0;

 failed_rcv_queue:
	kfree(dev->broadcast_rcv_buffer_ptrs);
	dev->broadcast_rcv_buffer_ptrs = NULL;
Jay Fenlason's avatar
Jay Fenlason committed
 failed_ptrs_alloc:
	fw_iso_buffer_destroy(&dev->broadcast_rcv_buffer, dev->card);
Jay Fenlason's avatar
Jay Fenlason committed
 failed_buffer_init:
	fw_iso_context_destroy(context);
	dev->broadcast_rcv_context = NULL;
Jay Fenlason's avatar
Jay Fenlason committed
 failed_context_create:
	fw_core_remove_address_handler(&dev->handler);
Jay Fenlason's avatar
Jay Fenlason committed
 failed_initial:
	dev->local_fifo = FWNET_NO_FIFO_ADDR;

Jay Fenlason's avatar
Jay Fenlason committed
	return retval;
}

static void set_carrier_state(struct fwnet_device *dev)
{
	if (dev->peer_count > 1)
		netif_carrier_on(dev->netdev);
	else
		netif_carrier_off(dev->netdev);
}

/* ifup */
static int fwnet_open(struct net_device *net)
{
	struct fwnet_device *dev = netdev_priv(net);
	if (dev->broadcast_state == FWNET_BROADCAST_ERROR) {
		ret = fwnet_broadcast_start(dev);
Jay Fenlason's avatar
Jay Fenlason committed
		if (ret)
			return ret;
	}
	netif_start_queue(net);

	spin_lock_irq(&dev->lock);
	set_carrier_state(dev);
	spin_unlock_irq(&dev->lock);

/* ifdown */
static int fwnet_stop(struct net_device *net)
	netif_stop_queue(net);

	/* Deallocate iso context for use by other applications? */
static netdev_tx_t fwnet_tx(struct sk_buff *skb, struct net_device *net)
	struct fwnet_header hdr_buf;
	struct fwnet_device *dev = netdev_priv(net);
Jay Fenlason's avatar
Jay Fenlason committed
	__be16 proto;
	u16 dest_node;
	unsigned max_payload;
	u16 dg_size;
	u16 *datagram_label_ptr;
	struct fwnet_packet_task *ptask;
	struct fwnet_peer *peer;
	unsigned long flags;
	spin_lock_irqsave(&dev->lock, flags);

	/* Can this happen? */
	if (netif_queue_stopped(dev->netdev)) {
		spin_unlock_irqrestore(&dev->lock, flags);

		return NETDEV_TX_BUSY;
	}

	ptask = kmem_cache_alloc(fwnet_packet_task_cache, GFP_ATOMIC);
Jay Fenlason's avatar
Jay Fenlason committed
	if (ptask == NULL)
		goto fail;

	skb = skb_share_check(skb, GFP_ATOMIC);
	if (!skb)
		goto fail;

	/*
	 * Make a copy of the driver-specific header.
Jay Fenlason's avatar
Jay Fenlason committed
	 * We might need to rebuild the header on tx failure.
	 */
	memcpy(&hdr_buf, skb->data, sizeof(hdr_buf));
	skb_pull(skb, sizeof(hdr_buf));

	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, net->broadcast, FWNET_ALEN) == 0
Jay Fenlason's avatar
Jay Fenlason committed
	    || proto == htons(ETH_P_ARP)
	    || (proto == htons(ETH_P_IP)
		&& IN_MULTICAST(ntohl(ip_hdr(skb)->daddr)))) {
		max_payload        = dev->broadcast_xmt_max_payload;
		datagram_label_ptr = &dev->broadcast_xmt_datagramlabel;

		ptask->fifo_addr   = FWNET_NO_FIFO_ADDR;
		ptask->generation  = 0;
		ptask->dest_node   = IEEE1394_ALL_NODES;
		ptask->speed       = SCODE_100;
Jay Fenlason's avatar
Jay Fenlason committed
	} else {
		__be64 guid = get_unaligned((__be64 *)hdr_buf.h_dest);
Jay Fenlason's avatar
Jay Fenlason committed
		u8 generation;

		peer = fwnet_peer_find_by_guid(dev, be64_to_cpu(guid));
		if (!peer || peer->fifo == FWNET_NO_FIFO_ADDR)
		generation         = peer->generation;
		dest_node          = peer->node_id;
		max_payload        = peer->max_payload;
		datagram_label_ptr = &peer->datagram_label;
		ptask->fifo_addr   = peer->fifo;
		ptask->generation  = generation;
		ptask->dest_node   = dest_node;
		ptask->speed       = peer->speed;
Jay Fenlason's avatar
Jay Fenlason committed
	}

	/* If this is an ARP packet, convert it */
	if (proto == htons(ETH_P_ARP)) {
		struct arphdr *arp = (struct arphdr *)skb->data;
		unsigned char *arp_ptr = (unsigned char *)(arp + 1);
		struct rfc2734_arp *arp1394 = (struct rfc2734_arp *)skb->data;
		__be32 ipaddr;

		ipaddr = get_unaligned((__be32 *)(arp_ptr + FWNET_ALEN));

		arp1394->hw_addr_len    = RFC2734_HW_ADDR_LEN;
		arp1394->max_rec        = dev->card->max_receive;
		arp1394->sspd		= dev->card->link_speed;

		put_unaligned_be16(dev->local_fifo >> 32,
				   &arp1394->fifo_hi);
		put_unaligned_be32(dev->local_fifo & 0xffffffff,
				   &arp1394->fifo_lo);
		put_unaligned(ipaddr, &arp1394->sip);
Jay Fenlason's avatar
Jay Fenlason committed
	}

	ptask->hdr.w0 = 0;
	ptask->hdr.w1 = 0;
	ptask->skb = skb;
	ptask->dev = dev;

Jay Fenlason's avatar
Jay Fenlason committed
	/* Does it all fit in one packet? */
	if (dg_size <= max_payload) {
		fwnet_make_uf_hdr(&ptask->hdr, ntohs(proto));
Jay Fenlason's avatar
Jay Fenlason committed
		ptask->outstanding_pkts = 1;
		max_payload = dg_size + RFC2374_UNFRAG_HDR_SIZE;
Jay Fenlason's avatar
Jay Fenlason committed
	} else {
		u16 datagram_label;

		max_payload -= RFC2374_FRAG_OVERHEAD;
Jay Fenlason's avatar
Jay Fenlason committed
		datagram_label = (*datagram_label_ptr)++;
		fwnet_make_ff_hdr(&ptask->hdr, ntohs(proto), dg_size,
				  datagram_label);
Jay Fenlason's avatar
Jay Fenlason committed
		ptask->outstanding_pkts = DIV_ROUND_UP(dg_size, max_payload);
		max_payload += RFC2374_FRAG_HDR_SIZE;
	if (++dev->queued_datagrams == FWNET_MAX_QUEUED_DATAGRAMS)
		netif_stop_queue(dev->netdev);
	spin_unlock_irqrestore(&dev->lock, flags);

Jay Fenlason's avatar
Jay Fenlason committed
	ptask->max_payload = max_payload;
	ptask->enqueued    = 0;
	fwnet_send_packet(ptask);

Jay Fenlason's avatar
Jay Fenlason committed
	return NETDEV_TX_OK;

 fail:
	spin_unlock_irqrestore(&dev->lock, flags);

Jay Fenlason's avatar
Jay Fenlason committed
	if (ptask)
		kmem_cache_free(fwnet_packet_task_cache, ptask);
Jay Fenlason's avatar
Jay Fenlason committed

	if (skb != NULL)
		dev_kfree_skb(skb);

	net->stats.tx_dropped++;
	net->stats.tx_errors++;
Jay Fenlason's avatar
Jay Fenlason committed

	/*
	 * 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_OK;
}

static int fwnet_change_mtu(struct net_device *net, int new_mtu)
{
Jay Fenlason's avatar
Jay Fenlason committed
	if (new_mtu < 68)
		return -EINVAL;

	net->mtu = new_mtu;
static const struct ethtool_ops fwnet_ethtool_ops = {
	.get_link	= ethtool_op_get_link,
};

static const struct net_device_ops fwnet_netdev_ops = {
	.ndo_open       = fwnet_open,
	.ndo_stop	= fwnet_stop,
	.ndo_start_xmit = fwnet_tx,
	.ndo_change_mtu = fwnet_change_mtu,
static void fwnet_init_dev(struct net_device *net)
{
	net->header_ops		= &fwnet_header_ops;
	net->netdev_ops		= &fwnet_netdev_ops;
	net->watchdog_timeo	= 2 * HZ;
	net->flags		= IFF_BROADCAST | IFF_MULTICAST;
	net->features		= NETIF_F_HIGHDMA;
	net->addr_len		= FWNET_ALEN;
	net->hard_header_len	= FWNET_HLEN;
	net->type		= ARPHRD_IEEE1394;
	net->tx_queue_len	= FWNET_TX_QUEUE_LEN;
	net->ethtool_ops	= &fwnet_ethtool_ops;
/* caller must hold fwnet_device_mutex */
static struct fwnet_device *fwnet_dev_find(struct fw_card *card)
{
	struct fwnet_device *dev;

	list_for_each_entry(dev, &fwnet_device_list, dev_link)
		if (dev->card == card)
			return dev;

	return NULL;
}

static int fwnet_add_peer(struct fwnet_device *dev,
			  struct fw_unit *unit, struct fw_device *device)
{
	struct fwnet_peer *peer;

	peer = kmalloc(sizeof(*peer), GFP_KERNEL);
	if (!peer)
		return -ENOMEM;

	dev_set_drvdata(&unit->device, peer);

	peer->dev = dev;
	peer->guid = (u64)device->config_rom[3] << 32 | device->config_rom[4];
	peer->fifo = FWNET_NO_FIFO_ADDR;
	INIT_LIST_HEAD(&peer->pd_list);
	peer->pdg_size = 0;
	peer->datagram_label = 0;
	peer->speed = device->max_speed;
	peer->max_payload = fwnet_max_payload(device->max_rec, peer->speed);

	peer->generation = device->generation;
	smp_rmb();
	peer->node_id = device->node_id;

	spin_lock_irq(&dev->lock);
	list_add_tail(&peer->peer_link, &dev->peer_list);
	dev->peer_count++;
	set_carrier_state(dev);
static int fwnet_probe(struct device *_dev)
{
	struct fw_unit *unit = fw_unit(_dev);
	struct fw_device *device = fw_parent_device(unit);
	struct fw_card *card = device->card;
	struct net_device *net;
	bool allocated_netdev = false;
	struct fwnet_device *dev;
Jay Fenlason's avatar
Jay Fenlason committed
	unsigned max_mtu;
	mutex_lock(&fwnet_device_mutex);
	dev = fwnet_dev_find(card);
	if (dev) {
		net = dev->netdev;
		goto have_dev;
	net = alloc_netdev(sizeof(*dev), "firewire%d", fwnet_init_dev);
	if (net == NULL) {
	allocated_netdev = true;
	SET_NETDEV_DEV(net, card->device);
	dev = netdev_priv(net);
	spin_lock_init(&dev->lock);
	dev->broadcast_state = FWNET_BROADCAST_ERROR;
	dev->broadcast_rcv_context = NULL;
	dev->broadcast_xmt_max_payload = 0;
	dev->broadcast_xmt_datagramlabel = 0;
	dev->local_fifo = FWNET_NO_FIFO_ADDR;
	dev->queued_datagrams = 0;
	INIT_LIST_HEAD(&dev->peer_list);
	dev->card = card;
Jay Fenlason's avatar
Jay Fenlason committed

	/*
	 * Use the RFC 2734 default 1500 octets or the maximum payload
	 * as initial MTU
	 */
	max_mtu = (1 << (card->max_receive + 1))
		  - sizeof(struct rfc2734_header) - IEEE1394_GASP_HDR_SIZE;
	net->mtu = min(1500U, max_mtu);
Jay Fenlason's avatar
Jay Fenlason committed

	/* Set our hardware address while we're at it */
	put_unaligned_be64(card->guid, net->dev_addr);
	put_unaligned_be64(~0ULL, net->broadcast);
	ret = register_netdev(net);
	if (ret) {
		fw_error("Cannot register the driver\n");
	list_add_tail(&dev->dev_link, &fwnet_device_list);
	fw_notify("%s: IPv4 over FireWire on device %016llx\n",
		  net->name, (unsigned long long)card->guid);
 have_dev:
	ret = fwnet_add_peer(dev, unit, device);
	if (ret && allocated_netdev) {
		unregister_netdev(net);
		list_del(&dev->dev_link);
	}
Jay Fenlason's avatar
Jay Fenlason committed
 out:
	if (ret && allocated_netdev)
		free_netdev(net);

	mutex_unlock(&fwnet_device_mutex);

	return ret;
}

static void fwnet_remove_peer(struct fwnet_peer *peer, struct fwnet_device *dev)
{
	struct fwnet_partial_datagram *pd, *pd_next;

	spin_lock_irq(&dev->lock);
	list_del(&peer->peer_link);
	dev->peer_count--;
	set_carrier_state(dev);
	spin_unlock_irq(&dev->lock);

	list_for_each_entry_safe(pd, pd_next, &peer->pd_list, pd_link)
		fwnet_pd_delete(pd);

	kfree(peer);
static int fwnet_remove(struct device *_dev)
{
	struct fwnet_peer *peer = dev_get_drvdata(_dev);
	struct fwnet_device *dev = peer->dev;
	struct net_device *net;
	mutex_lock(&fwnet_device_mutex);
	fwnet_remove_peer(peer, dev);
	if (list_empty(&dev->peer_list)) {
		net = dev->netdev;
		unregister_netdev(net);

		if (dev->local_fifo != FWNET_NO_FIFO_ADDR)
			fw_core_remove_address_handler(&dev->handler);
		if (dev->broadcast_rcv_context) {
			fw_iso_context_stop(dev->broadcast_rcv_context);
			fw_iso_buffer_destroy(&dev->broadcast_rcv_buffer,
					      dev->card);
			fw_iso_context_destroy(dev->broadcast_rcv_context);
		for (i = 0; dev->queued_datagrams && i < 5; i++)
			ssleep(1);
		WARN_ON(dev->queued_datagrams);
		list_del(&dev->dev_link);

		free_netdev(net);
	mutex_unlock(&fwnet_device_mutex);

/*
 * FIXME abort partially sent fragmented datagrams,
 * discard partially received fragmented datagrams
 */
static void fwnet_update(struct fw_unit *unit)
{
	struct fw_device *device = fw_parent_device(unit);
	struct fwnet_peer *peer = dev_get_drvdata(&unit->device);
	generation = device->generation;

	spin_lock_irq(&peer->dev->lock);
	peer->node_id    = device->node_id;
	peer->generation = generation;
	spin_unlock_irq(&peer->dev->lock);
static const struct ieee1394_device_id fwnet_id_table[] = {
	{
		.match_flags  = IEEE1394_MATCH_SPECIFIER_ID |
				IEEE1394_MATCH_VERSION,
		.specifier_id = IANA_SPECIFIER_ID,
		.version      = RFC2734_SW_VERSION,
	},
	{ }
};

static struct fw_driver fwnet_driver = {
Jay Fenlason's avatar
Jay Fenlason committed
	.driver = {
		.owner  = THIS_MODULE,
		.name   = "net",
		.bus    = &fw_bus_type,
		.probe  = fwnet_probe,
		.remove = fwnet_remove,
	.update   = fwnet_update,
	.id_table = fwnet_id_table,
};

static const u32 rfc2374_unit_directory_data[] = {
	0x00040000,	/* directory_length		*/
	0x1200005e,	/* unit_specifier_id: IANA	*/
	0x81000003,	/* textual descriptor offset	*/
	0x13000001,	/* unit_sw_version: RFC 2734	*/
	0x81000005,	/* textual descriptor offset	*/
	0x00030000,	/* descriptor_length		*/
	0x00000000,	/* text				*/
	0x00000000,	/* minimal ASCII, en		*/
	0x49414e41,	/* I A N A			*/
	0x00030000,	/* descriptor_length		*/
	0x00000000,	/* text				*/
	0x00000000,	/* minimal ASCII, en		*/
	0x49507634,	/* I P v 4			*/
};

static struct fw_descriptor rfc2374_unit_directory = {
	.length = ARRAY_SIZE(rfc2374_unit_directory_data),
	.key    = (CSR_DIRECTORY | CSR_UNIT) << 24,
	.data   = rfc2374_unit_directory_data
static int __init fwnet_init(void)
{
	int err;

	err = fw_core_add_descriptor(&rfc2374_unit_directory);
	if (err)
		return err;
	fwnet_packet_task_cache = kmem_cache_create("packet_task",
			sizeof(struct fwnet_packet_task), 0, 0, NULL);
	if (!fwnet_packet_task_cache) {
		err = -ENOMEM;
		goto out;
	}

	err = driver_register(&fwnet_driver.driver);
	if (!err)
		return 0;

	kmem_cache_destroy(fwnet_packet_task_cache);
out:
	fw_core_remove_descriptor(&rfc2374_unit_directory);

	return err;
module_init(fwnet_init);
static void __exit fwnet_cleanup(void)
{
	driver_unregister(&fwnet_driver.driver);
	kmem_cache_destroy(fwnet_packet_task_cache);
	fw_core_remove_descriptor(&rfc2374_unit_directory);
module_exit(fwnet_cleanup);
MODULE_AUTHOR("Jay Fenlason <fenlason@redhat.com>");
MODULE_DESCRIPTION("IPv4 over IEEE1394 as per RFC 2734");
MODULE_LICENSE("GPL");
MODULE_DEVICE_TABLE(ieee1394, fwnet_id_table);