Back to home page

OSCL-LXR

 
 

    


0001 /*
0002  * This file is subject to the terms and conditions of the GNU General Public
0003  * License.  See the file "COPYING" in the main directory of this archive
0004  * for more details.
0005  *
0006  * (C) Copyright 2020 Hewlett Packard Enterprise Development LP
0007  * Copyright (C) 1999-2009 Silicon Graphics, Inc. All rights reserved.
0008  */
0009 
0010 /*
0011  * Cross Partition Network Interface (XPNET) support
0012  *
0013  *  XPNET provides a virtual network layered on top of the Cross
0014  *  Partition communication layer.
0015  *
0016  *  XPNET provides direct point-to-point and broadcast-like support
0017  *  for an ethernet-like device.  The ethernet broadcast medium is
0018  *  replaced with a point-to-point message structure which passes
0019  *  pointers to a DMA-capable block that a remote partition should
0020  *  retrieve and pass to the upper level networking layer.
0021  *
0022  */
0023 
0024 #include <linux/slab.h>
0025 #include <linux/module.h>
0026 #include <linux/netdevice.h>
0027 #include <linux/etherdevice.h>
0028 #include "xp.h"
0029 
0030 /*
0031  * The message payload transferred by XPC.
0032  *
0033  * buf_pa is the physical address where the DMA should pull from.
0034  *
0035  * NOTE: for performance reasons, buf_pa should _ALWAYS_ begin on a
0036  * cacheline boundary.  To accomplish this, we record the number of
0037  * bytes from the beginning of the first cacheline to the first useful
0038  * byte of the skb (leadin_ignore) and the number of bytes from the
0039  * last useful byte of the skb to the end of the last cacheline
0040  * (tailout_ignore).
0041  *
0042  * size is the number of bytes to transfer which includes the skb->len
0043  * (useful bytes of the senders skb) plus the leadin and tailout
0044  */
0045 struct xpnet_message {
0046     u16 version;        /* Version for this message */
0047     u16 embedded_bytes; /* #of bytes embedded in XPC message */
0048     u32 magic;      /* Special number indicating this is xpnet */
0049     unsigned long buf_pa;   /* phys address of buffer to retrieve */
0050     u32 size;       /* #of bytes in buffer */
0051     u8 leadin_ignore;   /* #of bytes to ignore at the beginning */
0052     u8 tailout_ignore;  /* #of bytes to ignore at the end */
0053     unsigned char data; /* body of small packets */
0054 };
0055 
0056 /*
0057  * Determine the size of our message, the cacheline aligned size,
0058  * and then the number of message will request from XPC.
0059  *
0060  * XPC expects each message to exist in an individual cacheline.
0061  */
0062 #define XPNET_MSG_SIZE      XPC_MSG_PAYLOAD_MAX_SIZE
0063 #define XPNET_MSG_DATA_MAX  \
0064         (XPNET_MSG_SIZE - offsetof(struct xpnet_message, data))
0065 #define XPNET_MSG_NENTRIES  (PAGE_SIZE / XPC_MSG_MAX_SIZE)
0066 
0067 #define XPNET_MAX_KTHREADS  (XPNET_MSG_NENTRIES + 1)
0068 #define XPNET_MAX_IDLE_KTHREADS (XPNET_MSG_NENTRIES + 1)
0069 
0070 /*
0071  * Version number of XPNET implementation. XPNET can always talk to versions
0072  * with same major #, and never talk to versions with a different version.
0073  */
0074 #define _XPNET_VERSION(_major, _minor)  (((_major) << 4) | (_minor))
0075 #define XPNET_VERSION_MAJOR(_v)     ((_v) >> 4)
0076 #define XPNET_VERSION_MINOR(_v)     ((_v) & 0xf)
0077 
0078 #define XPNET_VERSION _XPNET_VERSION(1, 0)  /* version 1.0 */
0079 #define XPNET_VERSION_EMBED _XPNET_VERSION(1, 1)    /* version 1.1 */
0080 #define XPNET_MAGIC 0x88786984  /* "XNET" */
0081 
0082 #define XPNET_VALID_MSG(_m)                          \
0083    ((XPNET_VERSION_MAJOR(_m->version) == XPNET_VERSION_MAJOR(XPNET_VERSION)) \
0084     && (msg->magic == XPNET_MAGIC))
0085 
0086 #define XPNET_DEVICE_NAME       "xp0"
0087 
0088 /*
0089  * When messages are queued with xpc_send_notify, a kmalloc'd buffer
0090  * of the following type is passed as a notification cookie.  When the
0091  * notification function is called, we use the cookie to decide
0092  * whether all outstanding message sends have completed.  The skb can
0093  * then be released.
0094  */
0095 struct xpnet_pending_msg {
0096     struct sk_buff *skb;
0097     atomic_t use_count;
0098 };
0099 
0100 static struct net_device *xpnet_device;
0101 
0102 /*
0103  * When we are notified of other partitions activating, we add them to
0104  * our bitmask of partitions to which we broadcast.
0105  */
0106 static unsigned long *xpnet_broadcast_partitions;
0107 /* protect above */
0108 static DEFINE_SPINLOCK(xpnet_broadcast_lock);
0109 
0110 /*
0111  * Since the Block Transfer Engine (BTE) is being used for the transfer
0112  * and it relies upon cache-line size transfers, we need to reserve at
0113  * least one cache-line for head and tail alignment.  The BTE is
0114  * limited to 8MB transfers.
0115  *
0116  * Testing has shown that changing MTU to greater than 64KB has no effect
0117  * on TCP as the two sides negotiate a Max Segment Size that is limited
0118  * to 64K.  Other protocols May use packets greater than this, but for
0119  * now, the default is 64KB.
0120  */
0121 #define XPNET_MAX_MTU (0x800000UL - L1_CACHE_BYTES)
0122 /* 68 comes from min TCP+IP+MAC header */
0123 #define XPNET_MIN_MTU 68
0124 /* 32KB has been determined to be the ideal */
0125 #define XPNET_DEF_MTU (0x8000UL)
0126 
0127 /*
0128  * The partid is encapsulated in the MAC address beginning in the following
0129  * octet and it consists of two octets.
0130  */
0131 #define XPNET_PARTID_OCTET  2
0132 
0133 /* Define the XPNET debug device structures to be used with dev_dbg() et al */
0134 
0135 static struct device_driver xpnet_dbg_name = {
0136     .name = "xpnet"
0137 };
0138 
0139 static struct device xpnet_dbg_subname = {
0140     .init_name = "",    /* set to "" */
0141     .driver = &xpnet_dbg_name
0142 };
0143 
0144 static struct device *xpnet = &xpnet_dbg_subname;
0145 
0146 /*
0147  * Packet was recevied by XPC and forwarded to us.
0148  */
0149 static void
0150 xpnet_receive(short partid, int channel, struct xpnet_message *msg)
0151 {
0152     struct sk_buff *skb;
0153     void *dst;
0154     enum xp_retval ret;
0155 
0156     if (!XPNET_VALID_MSG(msg)) {
0157         /*
0158          * Packet with a different XPC version.  Ignore.
0159          */
0160         xpc_received(partid, channel, (void *)msg);
0161 
0162         xpnet_device->stats.rx_errors++;
0163 
0164         return;
0165     }
0166     dev_dbg(xpnet, "received 0x%lx, %d, %d, %d\n", msg->buf_pa, msg->size,
0167         msg->leadin_ignore, msg->tailout_ignore);
0168 
0169     /* reserve an extra cache line */
0170     skb = dev_alloc_skb(msg->size + L1_CACHE_BYTES);
0171     if (!skb) {
0172         dev_err(xpnet, "failed on dev_alloc_skb(%d)\n",
0173             msg->size + L1_CACHE_BYTES);
0174 
0175         xpc_received(partid, channel, (void *)msg);
0176 
0177         xpnet_device->stats.rx_errors++;
0178 
0179         return;
0180     }
0181 
0182     /*
0183      * The allocated skb has some reserved space.
0184      * In order to use xp_remote_memcpy(), we need to get the
0185      * skb->data pointer moved forward.
0186      */
0187     skb_reserve(skb, (L1_CACHE_BYTES - ((u64)skb->data &
0188                         (L1_CACHE_BYTES - 1)) +
0189               msg->leadin_ignore));
0190 
0191     /*
0192      * Update the tail pointer to indicate data actually
0193      * transferred.
0194      */
0195     skb_put(skb, (msg->size - msg->leadin_ignore - msg->tailout_ignore));
0196 
0197     /*
0198      * Move the data over from the other side.
0199      */
0200     if ((XPNET_VERSION_MINOR(msg->version) == 1) &&
0201         (msg->embedded_bytes != 0)) {
0202         dev_dbg(xpnet, "copying embedded message. memcpy(0x%p, 0x%p, "
0203             "%lu)\n", skb->data, &msg->data,
0204             (size_t)msg->embedded_bytes);
0205 
0206         skb_copy_to_linear_data(skb, &msg->data,
0207                     (size_t)msg->embedded_bytes);
0208     } else {
0209         dst = (void *)((u64)skb->data & ~(L1_CACHE_BYTES - 1));
0210         dev_dbg(xpnet, "transferring buffer to the skb->data area;\n\t"
0211             "xp_remote_memcpy(0x%p, 0x%p, %u)\n", dst,
0212                       (void *)msg->buf_pa, msg->size);
0213 
0214         ret = xp_remote_memcpy(xp_pa(dst), msg->buf_pa, msg->size);
0215         if (ret != xpSuccess) {
0216             /*
0217              * !!! Need better way of cleaning skb.  Currently skb
0218              * !!! appears in_use and we can't just call
0219              * !!! dev_kfree_skb.
0220              */
0221             dev_err(xpnet, "xp_remote_memcpy(0x%p, 0x%p, 0x%x) "
0222                 "returned error=0x%x\n", dst,
0223                 (void *)msg->buf_pa, msg->size, ret);
0224 
0225             xpc_received(partid, channel, (void *)msg);
0226 
0227             xpnet_device->stats.rx_errors++;
0228 
0229             return;
0230         }
0231     }
0232 
0233     dev_dbg(xpnet, "<skb->head=0x%p skb->data=0x%p skb->tail=0x%p "
0234         "skb->end=0x%p skb->len=%d\n", (void *)skb->head,
0235         (void *)skb->data, skb_tail_pointer(skb), skb_end_pointer(skb),
0236         skb->len);
0237 
0238     skb->protocol = eth_type_trans(skb, xpnet_device);
0239     skb->ip_summed = CHECKSUM_UNNECESSARY;
0240 
0241     dev_dbg(xpnet, "passing skb to network layer\n"
0242         "\tskb->head=0x%p skb->data=0x%p skb->tail=0x%p "
0243         "skb->end=0x%p skb->len=%d\n",
0244         (void *)skb->head, (void *)skb->data, skb_tail_pointer(skb),
0245         skb_end_pointer(skb), skb->len);
0246 
0247     xpnet_device->stats.rx_packets++;
0248     xpnet_device->stats.rx_bytes += skb->len + ETH_HLEN;
0249 
0250     netif_rx(skb);
0251     xpc_received(partid, channel, (void *)msg);
0252 }
0253 
0254 /*
0255  * This is the handler which XPC calls during any sort of change in
0256  * state or message reception on a connection.
0257  */
0258 static void
0259 xpnet_connection_activity(enum xp_retval reason, short partid, int channel,
0260               void *data, void *key)
0261 {
0262     DBUG_ON(partid < 0 || partid >= xp_max_npartitions);
0263     DBUG_ON(channel != XPC_NET_CHANNEL);
0264 
0265     switch (reason) {
0266     case xpMsgReceived: /* message received */
0267         DBUG_ON(data == NULL);
0268 
0269         xpnet_receive(partid, channel, (struct xpnet_message *)data);
0270         break;
0271 
0272     case xpConnected:   /* connection completed to a partition */
0273         spin_lock_bh(&xpnet_broadcast_lock);
0274         __set_bit(partid, xpnet_broadcast_partitions);
0275         spin_unlock_bh(&xpnet_broadcast_lock);
0276 
0277         netif_carrier_on(xpnet_device);
0278 
0279         dev_dbg(xpnet, "%s connected to partition %d\n",
0280             xpnet_device->name, partid);
0281         break;
0282 
0283     default:
0284         spin_lock_bh(&xpnet_broadcast_lock);
0285         __clear_bit(partid, xpnet_broadcast_partitions);
0286         spin_unlock_bh(&xpnet_broadcast_lock);
0287 
0288         if (bitmap_empty(xpnet_broadcast_partitions,
0289                  xp_max_npartitions)) {
0290             netif_carrier_off(xpnet_device);
0291         }
0292 
0293         dev_dbg(xpnet, "%s disconnected from partition %d\n",
0294             xpnet_device->name, partid);
0295         break;
0296     }
0297 }
0298 
0299 static int
0300 xpnet_dev_open(struct net_device *dev)
0301 {
0302     enum xp_retval ret;
0303 
0304     dev_dbg(xpnet, "calling xpc_connect(%d, 0x%p, NULL, %ld, %ld, %ld, "
0305         "%ld)\n", XPC_NET_CHANNEL, xpnet_connection_activity,
0306         (unsigned long)XPNET_MSG_SIZE,
0307         (unsigned long)XPNET_MSG_NENTRIES,
0308         (unsigned long)XPNET_MAX_KTHREADS,
0309         (unsigned long)XPNET_MAX_IDLE_KTHREADS);
0310 
0311     ret = xpc_connect(XPC_NET_CHANNEL, xpnet_connection_activity, NULL,
0312               XPNET_MSG_SIZE, XPNET_MSG_NENTRIES,
0313               XPNET_MAX_KTHREADS, XPNET_MAX_IDLE_KTHREADS);
0314     if (ret != xpSuccess) {
0315         dev_err(xpnet, "ifconfig up of %s failed on XPC connect, "
0316             "ret=%d\n", dev->name, ret);
0317 
0318         return -ENOMEM;
0319     }
0320 
0321     dev_dbg(xpnet, "ifconfig up of %s; XPC connected\n", dev->name);
0322 
0323     return 0;
0324 }
0325 
0326 static int
0327 xpnet_dev_stop(struct net_device *dev)
0328 {
0329     xpc_disconnect(XPC_NET_CHANNEL);
0330 
0331     dev_dbg(xpnet, "ifconfig down of %s; XPC disconnected\n", dev->name);
0332 
0333     return 0;
0334 }
0335 
0336 /*
0337  * Notification that the other end has received the message and
0338  * DMA'd the skb information.  At this point, they are done with
0339  * our side.  When all recipients are done processing, we
0340  * release the skb and then release our pending message structure.
0341  */
0342 static void
0343 xpnet_send_completed(enum xp_retval reason, short partid, int channel,
0344              void *__qm)
0345 {
0346     struct xpnet_pending_msg *queued_msg = (struct xpnet_pending_msg *)__qm;
0347 
0348     DBUG_ON(queued_msg == NULL);
0349 
0350     dev_dbg(xpnet, "message to %d notified with reason %d\n",
0351         partid, reason);
0352 
0353     if (atomic_dec_return(&queued_msg->use_count) == 0) {
0354         dev_dbg(xpnet, "all acks for skb->head=-x%p\n",
0355             (void *)queued_msg->skb->head);
0356 
0357         dev_kfree_skb_any(queued_msg->skb);
0358         kfree(queued_msg);
0359     }
0360 }
0361 
0362 static void
0363 xpnet_send(struct sk_buff *skb, struct xpnet_pending_msg *queued_msg,
0364        u64 start_addr, u64 end_addr, u16 embedded_bytes, int dest_partid)
0365 {
0366     u8 msg_buffer[XPNET_MSG_SIZE];
0367     struct xpnet_message *msg = (struct xpnet_message *)&msg_buffer;
0368     u16 msg_size = sizeof(struct xpnet_message);
0369     enum xp_retval ret;
0370 
0371     msg->embedded_bytes = embedded_bytes;
0372     if (unlikely(embedded_bytes != 0)) {
0373         msg->version = XPNET_VERSION_EMBED;
0374         dev_dbg(xpnet, "calling memcpy(0x%p, 0x%p, 0x%lx)\n",
0375             &msg->data, skb->data, (size_t)embedded_bytes);
0376         skb_copy_from_linear_data(skb, &msg->data,
0377                       (size_t)embedded_bytes);
0378         msg_size += embedded_bytes - 1;
0379     } else {
0380         msg->version = XPNET_VERSION;
0381     }
0382     msg->magic = XPNET_MAGIC;
0383     msg->size = end_addr - start_addr;
0384     msg->leadin_ignore = (u64)skb->data - start_addr;
0385     msg->tailout_ignore = end_addr - (u64)skb_tail_pointer(skb);
0386     msg->buf_pa = xp_pa((void *)start_addr);
0387 
0388     dev_dbg(xpnet, "sending XPC message to %d:%d\n"
0389         "msg->buf_pa=0x%lx, msg->size=%u, "
0390         "msg->leadin_ignore=%u, msg->tailout_ignore=%u\n",
0391         dest_partid, XPC_NET_CHANNEL, msg->buf_pa, msg->size,
0392         msg->leadin_ignore, msg->tailout_ignore);
0393 
0394     atomic_inc(&queued_msg->use_count);
0395 
0396     ret = xpc_send_notify(dest_partid, XPC_NET_CHANNEL, XPC_NOWAIT, msg,
0397                   msg_size, xpnet_send_completed, queued_msg);
0398     if (unlikely(ret != xpSuccess))
0399         atomic_dec(&queued_msg->use_count);
0400 }
0401 
0402 /*
0403  * Network layer has formatted a packet (skb) and is ready to place it
0404  * "on the wire".  Prepare and send an xpnet_message to all partitions
0405  * which have connected with us and are targets of this packet.
0406  *
0407  * MAC-NOTE:  For the XPNET driver, the MAC address contains the
0408  * destination partid.  If the destination partid octets are 0xffff,
0409  * this packet is to be broadcast to all connected partitions.
0410  */
0411 static netdev_tx_t
0412 xpnet_dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
0413 {
0414     struct xpnet_pending_msg *queued_msg;
0415     u64 start_addr, end_addr;
0416     short dest_partid;
0417     u16 embedded_bytes = 0;
0418 
0419     dev_dbg(xpnet, ">skb->head=0x%p skb->data=0x%p skb->tail=0x%p "
0420         "skb->end=0x%p skb->len=%d\n", (void *)skb->head,
0421         (void *)skb->data, skb_tail_pointer(skb), skb_end_pointer(skb),
0422         skb->len);
0423 
0424     if (skb->data[0] == 0x33) {
0425         dev_kfree_skb(skb);
0426         return NETDEV_TX_OK;    /* nothing needed to be done */
0427     }
0428 
0429     /*
0430      * The xpnet_pending_msg tracks how many outstanding
0431      * xpc_send_notifies are relying on this skb.  When none
0432      * remain, release the skb.
0433      */
0434     queued_msg = kmalloc(sizeof(struct xpnet_pending_msg), GFP_ATOMIC);
0435     if (queued_msg == NULL) {
0436         dev_warn(xpnet, "failed to kmalloc %ld bytes; dropping "
0437              "packet\n", sizeof(struct xpnet_pending_msg));
0438 
0439         dev->stats.tx_errors++;
0440         dev_kfree_skb(skb);
0441         return NETDEV_TX_OK;
0442     }
0443 
0444     /* get the beginning of the first cacheline and end of last */
0445     start_addr = ((u64)skb->data & ~(L1_CACHE_BYTES - 1));
0446     end_addr = L1_CACHE_ALIGN((u64)skb_tail_pointer(skb));
0447 
0448     /* calculate how many bytes to embed in the XPC message */
0449     if (unlikely(skb->len <= XPNET_MSG_DATA_MAX)) {
0450         /* skb->data does fit so embed */
0451         embedded_bytes = skb->len;
0452     }
0453 
0454     /*
0455      * Since the send occurs asynchronously, we set the count to one
0456      * and begin sending.  Any sends that happen to complete before
0457      * we are done sending will not free the skb.  We will be left
0458      * with that task during exit.  This also handles the case of
0459      * a packet destined for a partition which is no longer up.
0460      */
0461     atomic_set(&queued_msg->use_count, 1);
0462     queued_msg->skb = skb;
0463 
0464     if (skb->data[0] == 0xff) {
0465         /* we are being asked to broadcast to all partitions */
0466         for_each_set_bit(dest_partid, xpnet_broadcast_partitions,
0467                  xp_max_npartitions) {
0468 
0469             xpnet_send(skb, queued_msg, start_addr, end_addr,
0470                    embedded_bytes, dest_partid);
0471         }
0472     } else {
0473         dest_partid = (short)skb->data[XPNET_PARTID_OCTET + 1];
0474         dest_partid |= (short)skb->data[XPNET_PARTID_OCTET + 0] << 8;
0475 
0476         if (dest_partid >= 0 &&
0477             dest_partid < xp_max_npartitions &&
0478             test_bit(dest_partid, xpnet_broadcast_partitions) != 0) {
0479 
0480             xpnet_send(skb, queued_msg, start_addr, end_addr,
0481                    embedded_bytes, dest_partid);
0482         }
0483     }
0484 
0485     dev->stats.tx_packets++;
0486     dev->stats.tx_bytes += skb->len;
0487 
0488     if (atomic_dec_return(&queued_msg->use_count) == 0) {
0489         dev_kfree_skb(skb);
0490         kfree(queued_msg);
0491     }
0492 
0493     return NETDEV_TX_OK;
0494 }
0495 
0496 /*
0497  * Deal with transmit timeouts coming from the network layer.
0498  */
0499 static void
0500 xpnet_dev_tx_timeout(struct net_device *dev, unsigned int txqueue)
0501 {
0502     dev->stats.tx_errors++;
0503 }
0504 
0505 static const struct net_device_ops xpnet_netdev_ops = {
0506     .ndo_open       = xpnet_dev_open,
0507     .ndo_stop       = xpnet_dev_stop,
0508     .ndo_start_xmit     = xpnet_dev_hard_start_xmit,
0509     .ndo_tx_timeout     = xpnet_dev_tx_timeout,
0510     .ndo_set_mac_address    = eth_mac_addr,
0511     .ndo_validate_addr  = eth_validate_addr,
0512 };
0513 
0514 static int __init
0515 xpnet_init(void)
0516 {
0517     u8 addr[ETH_ALEN];
0518     int result;
0519 
0520     if (!is_uv_system())
0521         return -ENODEV;
0522 
0523     dev_info(xpnet, "registering network device %s\n", XPNET_DEVICE_NAME);
0524 
0525     xpnet_broadcast_partitions = bitmap_zalloc(xp_max_npartitions,
0526                            GFP_KERNEL);
0527     if (xpnet_broadcast_partitions == NULL)
0528         return -ENOMEM;
0529 
0530     /*
0531      * use ether_setup() to init the majority of our device
0532      * structure and then override the necessary pieces.
0533      */
0534     xpnet_device = alloc_netdev(0, XPNET_DEVICE_NAME, NET_NAME_UNKNOWN,
0535                     ether_setup);
0536     if (xpnet_device == NULL) {
0537         bitmap_free(xpnet_broadcast_partitions);
0538         return -ENOMEM;
0539     }
0540 
0541     netif_carrier_off(xpnet_device);
0542 
0543     xpnet_device->netdev_ops = &xpnet_netdev_ops;
0544     xpnet_device->mtu = XPNET_DEF_MTU;
0545     xpnet_device->min_mtu = XPNET_MIN_MTU;
0546     xpnet_device->max_mtu = XPNET_MAX_MTU;
0547 
0548     memset(addr, 0, sizeof(addr));
0549     /*
0550      * Multicast assumes the LSB of the first octet is set for multicast
0551      * MAC addresses.  We chose the first octet of the MAC to be unlikely
0552      * to collide with any vendor's officially issued MAC.
0553      */
0554     addr[0] = 0x02;     /* locally administered, no OUI */
0555 
0556     addr[XPNET_PARTID_OCTET + 1] = xp_partition_id;
0557     addr[XPNET_PARTID_OCTET + 0] = (xp_partition_id >> 8);
0558     eth_hw_addr_set(xpnet_device, addr);
0559 
0560     /*
0561      * ether_setup() sets this to a multicast device.  We are
0562      * really not supporting multicast at this time.
0563      */
0564     xpnet_device->flags &= ~IFF_MULTICAST;
0565 
0566     /*
0567      * No need to checksum as it is a DMA transfer.  The BTE will
0568      * report an error if the data is not retrievable and the
0569      * packet will be dropped.
0570      */
0571     xpnet_device->features = NETIF_F_HW_CSUM;
0572 
0573     result = register_netdev(xpnet_device);
0574     if (result != 0) {
0575         free_netdev(xpnet_device);
0576         bitmap_free(xpnet_broadcast_partitions);
0577     }
0578 
0579     return result;
0580 }
0581 
0582 module_init(xpnet_init);
0583 
0584 static void __exit
0585 xpnet_exit(void)
0586 {
0587     dev_info(xpnet, "unregistering network device %s\n",
0588          xpnet_device[0].name);
0589 
0590     unregister_netdev(xpnet_device);
0591     free_netdev(xpnet_device);
0592     bitmap_free(xpnet_broadcast_partitions);
0593 }
0594 
0595 module_exit(xpnet_exit);
0596 
0597 MODULE_AUTHOR("Silicon Graphics, Inc.");
0598 MODULE_DESCRIPTION("Cross Partition Network adapter (XPNET)");
0599 MODULE_LICENSE("GPL");