IB/mad: Check GID/LID when matching requests
[linux-block.git] / drivers / infiniband / core / mad.c
index f7854b65fd553c1b271bd5b8b7a6def94881d6ff..b38e02a5db356c2b8dfdc612032e5fd73a860397 100644 (file)
@@ -34,6 +34,7 @@
  * $Id: mad.c 5596 2006-03-03 01:00:07Z sean.hefty $
  */
 #include <linux/dma-mapping.h>
+#include <rdma/ib_cache.h>
 
 #include "mad_priv.h"
 #include "mad_rmpp.h"
@@ -45,8 +46,7 @@ MODULE_DESCRIPTION("kernel IB MAD API");
 MODULE_AUTHOR("Hal Rosenstock");
 MODULE_AUTHOR("Sean Hefty");
 
-
-kmem_cache_t *ib_mad_cache;
+static kmem_cache_t *ib_mad_cache;
 
 static struct list_head ib_mad_port_list;
 static u32 ib_mad_client_id = 0;
@@ -227,6 +227,11 @@ struct ib_mad_agent *ib_register_mad_agent(struct ib_device *device,
                        if (!is_vendor_oui(mad_reg_req->oui))
                                goto error1;
                }
+               /* Make sure class supplied is consistent with RMPP */
+               if (!ib_is_mad_class_rmpp(mad_reg_req->mgmt_class)) {
+                       if (rmpp_version)
+                               goto error1;
+               }
                /* Make sure class supplied is consistent with QP type */
                if (qp_type == IB_QPT_SMI) {
                        if ((mad_reg_req->mgmt_class !=
@@ -347,7 +352,7 @@ struct ib_mad_agent *ib_register_mad_agent(struct ib_device *device,
        INIT_WORK(&mad_agent_priv->local_work, local_completions,
                   mad_agent_priv);
        atomic_set(&mad_agent_priv->refcount, 1);
-       init_waitqueue_head(&mad_agent_priv->wait);
+       init_completion(&mad_agent_priv->comp);
 
        return &mad_agent_priv->agent;
 
@@ -462,7 +467,7 @@ struct ib_mad_agent *ib_register_mad_snoop(struct ib_device *device,
        mad_snoop_priv->agent.qp = port_priv->qp_info[qpn].qp;
        mad_snoop_priv->agent.port_num = port_num;
        mad_snoop_priv->mad_snoop_flags = mad_snoop_flags;
-       init_waitqueue_head(&mad_snoop_priv->wait);
+       init_completion(&mad_snoop_priv->comp);
        mad_snoop_priv->snoop_index = register_snoop_agent(
                                                &port_priv->qp_info[qpn],
                                                mad_snoop_priv);
@@ -481,6 +486,18 @@ error1:
 }
 EXPORT_SYMBOL(ib_register_mad_snoop);
 
+static inline void deref_mad_agent(struct ib_mad_agent_private *mad_agent_priv)
+{
+       if (atomic_dec_and_test(&mad_agent_priv->refcount))
+               complete(&mad_agent_priv->comp);
+}
+
+static inline void deref_snoop_agent(struct ib_mad_snoop_private *mad_snoop_priv)
+{
+       if (atomic_dec_and_test(&mad_snoop_priv->refcount))
+               complete(&mad_snoop_priv->comp);
+}
+
 static void unregister_mad_agent(struct ib_mad_agent_private *mad_agent_priv)
 {
        struct ib_mad_port_private *port_priv;
@@ -504,9 +521,8 @@ static void unregister_mad_agent(struct ib_mad_agent_private *mad_agent_priv)
        flush_workqueue(port_priv->wq);
        ib_cancel_rmpp_recvs(mad_agent_priv);
 
-       atomic_dec(&mad_agent_priv->refcount);
-       wait_event(mad_agent_priv->wait,
-                  !atomic_read(&mad_agent_priv->refcount));
+       deref_mad_agent(mad_agent_priv);
+       wait_for_completion(&mad_agent_priv->comp);
 
        kfree(mad_agent_priv->reg_req);
        ib_dereg_mr(mad_agent_priv->agent.mr);
@@ -524,9 +540,8 @@ static void unregister_mad_snoop(struct ib_mad_snoop_private *mad_snoop_priv)
        atomic_dec(&qp_info->snoop_count);
        spin_unlock_irqrestore(&qp_info->snoop_lock, flags);
 
-       atomic_dec(&mad_snoop_priv->refcount);
-       wait_event(mad_snoop_priv->wait,
-                  !atomic_read(&mad_snoop_priv->refcount));
+       deref_snoop_agent(mad_snoop_priv);
+       wait_for_completion(&mad_snoop_priv->comp);
 
        kfree(mad_snoop_priv);
 }
@@ -595,8 +610,7 @@ static void snoop_send(struct ib_mad_qp_info *qp_info,
                spin_unlock_irqrestore(&qp_info->snoop_lock, flags);
                mad_snoop_priv->agent.snoop_handler(&mad_snoop_priv->agent,
                                                    send_buf, mad_send_wc);
-               if (atomic_dec_and_test(&mad_snoop_priv->refcount))
-                       wake_up(&mad_snoop_priv->wait);
+               deref_snoop_agent(mad_snoop_priv);
                spin_lock_irqsave(&qp_info->snoop_lock, flags);
        }
        spin_unlock_irqrestore(&qp_info->snoop_lock, flags);
@@ -621,8 +635,7 @@ static void snoop_recv(struct ib_mad_qp_info *qp_info,
                spin_unlock_irqrestore(&qp_info->snoop_lock, flags);
                mad_snoop_priv->agent.recv_handler(&mad_snoop_priv->agent,
                                                   mad_recv_wc);
-               if (atomic_dec_and_test(&mad_snoop_priv->refcount))
-                       wake_up(&mad_snoop_priv->wait);
+               deref_snoop_agent(mad_snoop_priv);
                spin_lock_irqsave(&qp_info->snoop_lock, flags);
        }
        spin_unlock_irqrestore(&qp_info->snoop_lock, flags);
@@ -890,6 +903,35 @@ struct ib_mad_send_buf * ib_create_send_mad(struct ib_mad_agent *mad_agent,
 }
 EXPORT_SYMBOL(ib_create_send_mad);
 
+int ib_get_mad_data_offset(u8 mgmt_class)
+{
+       if (mgmt_class == IB_MGMT_CLASS_SUBN_ADM)
+               return IB_MGMT_SA_HDR;
+       else if ((mgmt_class == IB_MGMT_CLASS_DEVICE_MGMT) ||
+                (mgmt_class == IB_MGMT_CLASS_DEVICE_ADM) ||
+                (mgmt_class == IB_MGMT_CLASS_BIS))
+               return IB_MGMT_DEVICE_HDR;
+       else if ((mgmt_class >= IB_MGMT_CLASS_VENDOR_RANGE2_START) &&
+                (mgmt_class <= IB_MGMT_CLASS_VENDOR_RANGE2_END))
+               return IB_MGMT_VENDOR_HDR;
+       else
+               return IB_MGMT_MAD_HDR;
+}
+EXPORT_SYMBOL(ib_get_mad_data_offset);
+
+int ib_is_mad_class_rmpp(u8 mgmt_class)
+{
+       if ((mgmt_class == IB_MGMT_CLASS_SUBN_ADM) ||
+           (mgmt_class == IB_MGMT_CLASS_DEVICE_MGMT) ||
+           (mgmt_class == IB_MGMT_CLASS_DEVICE_ADM) ||
+           (mgmt_class == IB_MGMT_CLASS_BIS) ||
+           ((mgmt_class >= IB_MGMT_CLASS_VENDOR_RANGE2_START) &&
+            (mgmt_class <= IB_MGMT_CLASS_VENDOR_RANGE2_END)))
+               return 1;
+       return 0;
+}
+EXPORT_SYMBOL(ib_is_mad_class_rmpp);
+
 void *ib_get_rmpp_segment(struct ib_mad_send_buf *send_buf, int seg_num)
 {
        struct ib_mad_send_wr_private *mad_send_wr;
@@ -934,8 +976,7 @@ void ib_free_send_mad(struct ib_mad_send_buf *send_buf)
 
        free_send_rmpp_list(mad_send_wr);
        kfree(send_buf->mad);
-       if (atomic_dec_and_test(&mad_agent_priv->refcount))
-               wake_up(&mad_agent_priv->wait);
+       deref_mad_agent(mad_agent_priv);
 }
 EXPORT_SYMBOL(ib_free_send_mad);
 
@@ -1022,6 +1063,13 @@ int ib_post_send_mad(struct ib_mad_send_buf *send_buf,
                        goto error;
                }
 
+               if (!ib_is_mad_class_rmpp(((struct ib_mad_hdr *) send_buf->mad)->mgmt_class)) {
+                       if (mad_agent_priv->agent.rmpp_version) {
+                               ret = -EINVAL;
+                               goto error;
+                       }
+               }
+
                /*
                 * Save pointer to next work request to post in case the
                 * current one completes, and the user modifies the work
@@ -1618,29 +1666,108 @@ static int is_data_mad(struct ib_mad_agent_private *mad_agent_priv,
                (rmpp_mad->rmpp_hdr.rmpp_type == IB_MGMT_RMPP_TYPE_DATA);
 }
 
+static inline int rcv_has_same_class(struct ib_mad_send_wr_private *wr,
+                                    struct ib_mad_recv_wc *rwc)
+{
+       return ((struct ib_mad *)(wr->send_buf.mad))->mad_hdr.mgmt_class ==
+               rwc->recv_buf.mad->mad_hdr.mgmt_class;
+}
+
+static inline int rcv_has_same_gid(struct ib_mad_agent_private *mad_agent_priv,
+                                  struct ib_mad_send_wr_private *wr,
+                                  struct ib_mad_recv_wc *rwc )
+{
+       struct ib_ah_attr attr;
+       u8 send_resp, rcv_resp;
+       union ib_gid sgid;
+       struct ib_device *device = mad_agent_priv->agent.device;
+       u8 port_num = mad_agent_priv->agent.port_num;
+       u8 lmc;
+
+       send_resp = ((struct ib_mad *)(wr->send_buf.mad))->
+                    mad_hdr.method & IB_MGMT_METHOD_RESP;
+       rcv_resp = rwc->recv_buf.mad->mad_hdr.method & IB_MGMT_METHOD_RESP;
+
+       if (send_resp == rcv_resp)
+               /* both requests, or both responses. GIDs different */
+               return 0;
+
+       if (ib_query_ah(wr->send_buf.ah, &attr))
+               /* Assume not equal, to avoid false positives. */
+               return 0;
+
+       if (!!(attr.ah_flags & IB_AH_GRH) !=
+           !!(rwc->wc->wc_flags & IB_WC_GRH))
+               /* one has GID, other does not.  Assume different */
+               return 0;
+
+       if (!send_resp && rcv_resp) {
+               /* is request/response. */
+               if (!(attr.ah_flags & IB_AH_GRH)) {
+                       if (ib_get_cached_lmc(device, port_num, &lmc))
+                               return 0;
+                       return (!lmc || !((attr.src_path_bits ^
+                                          rwc->wc->dlid_path_bits) &
+                                         ((1 << lmc) - 1)));
+               } else {
+                       if (ib_get_cached_gid(device, port_num,
+                                             attr.grh.sgid_index, &sgid))
+                               return 0;
+                       return !memcmp(sgid.raw, rwc->recv_buf.grh->dgid.raw,
+                                      16);
+               }
+       }
+
+       if (!(attr.ah_flags & IB_AH_GRH))
+               return attr.dlid == rwc->wc->slid;
+       else
+               return !memcmp(attr.grh.dgid.raw, rwc->recv_buf.grh->sgid.raw,
+                              16);
+}
+
+static inline int is_direct(u8 class)
+{
+       return (class == IB_MGMT_CLASS_SUBN_DIRECTED_ROUTE);
+}
+
 struct ib_mad_send_wr_private*
-ib_find_send_mad(struct ib_mad_agent_private *mad_agent_priv, __be64 tid)
+ib_find_send_mad(struct ib_mad_agent_private *mad_agent_priv,
+                struct ib_mad_recv_wc *wc)
 {
-       struct ib_mad_send_wr_private *mad_send_wr;
+       struct ib_mad_send_wr_private *wr;
+       struct ib_mad *mad;
 
-       list_for_each_entry(mad_send_wr, &mad_agent_priv->wait_list,
-                           agent_list) {
-               if (mad_send_wr->tid == tid)
-                       return mad_send_wr;
+       mad = (struct ib_mad *)wc->recv_buf.mad;
+
+       list_for_each_entry(wr, &mad_agent_priv->wait_list, agent_list) {
+               if ((wr->tid == mad->mad_hdr.tid) &&
+                   rcv_has_same_class(wr, wc) &&
+                   /*
+                    * Don't check GID for direct routed MADs.
+                    * These might have permissive LIDs.
+                    */
+                   (is_direct(wc->recv_buf.mad->mad_hdr.mgmt_class) ||
+                    rcv_has_same_gid(mad_agent_priv, wr, wc)))
+                       return wr;
        }
 
        /*
         * It's possible to receive the response before we've
         * been notified that the send has completed
         */
-       list_for_each_entry(mad_send_wr, &mad_agent_priv->send_list,
-                           agent_list) {
-               if (is_data_mad(mad_agent_priv, mad_send_wr->send_buf.mad) &&
-                   mad_send_wr->tid == tid && mad_send_wr->timeout) {
+       list_for_each_entry(wr, &mad_agent_priv->send_list, agent_list) {
+               if (is_data_mad(mad_agent_priv, wr->send_buf.mad) &&
+                   wr->tid == mad->mad_hdr.tid &&
+                   wr->timeout &&
+                   rcv_has_same_class(wr, wc) &&
+                   /*
+                    * Don't check GID for direct routed MADs.
+                    * These might have permissive LIDs.
+                    */
+                   (is_direct(wc->recv_buf.mad->mad_hdr.mgmt_class) ||
+                    rcv_has_same_gid(mad_agent_priv, wr, wc)))
                        /* Verify request has not been canceled */
-                       return (mad_send_wr->status == IB_WC_SUCCESS) ?
-                               mad_send_wr : NULL;
-               }
+                       return (wr->status == IB_WC_SUCCESS) ? wr : NULL;
        }
        return NULL;
 }
@@ -1661,7 +1788,6 @@ static void ib_mad_complete_recv(struct ib_mad_agent_private *mad_agent_priv,
        struct ib_mad_send_wr_private *mad_send_wr;
        struct ib_mad_send_wc mad_send_wc;
        unsigned long flags;
-       __be64 tid;
 
        INIT_LIST_HEAD(&mad_recv_wc->rmpp_list);
        list_add(&mad_recv_wc->recv_buf.list, &mad_recv_wc->rmpp_list);
@@ -1669,22 +1795,19 @@ static void ib_mad_complete_recv(struct ib_mad_agent_private *mad_agent_priv,
                mad_recv_wc = ib_process_rmpp_recv_wc(mad_agent_priv,
                                                      mad_recv_wc);
                if (!mad_recv_wc) {
-                       if (atomic_dec_and_test(&mad_agent_priv->refcount))
-                               wake_up(&mad_agent_priv->wait);
+                       deref_mad_agent(mad_agent_priv);
                        return;
                }
        }
 
        /* Complete corresponding request */
        if (response_mad(mad_recv_wc->recv_buf.mad)) {
-               tid = mad_recv_wc->recv_buf.mad->mad_hdr.tid;
                spin_lock_irqsave(&mad_agent_priv->lock, flags);
-               mad_send_wr = ib_find_send_mad(mad_agent_priv, tid);
+               mad_send_wr = ib_find_send_mad(mad_agent_priv, mad_recv_wc);
                if (!mad_send_wr) {
                        spin_unlock_irqrestore(&mad_agent_priv->lock, flags);
                        ib_free_recv_mad(mad_recv_wc);
-                       if (atomic_dec_and_test(&mad_agent_priv->refcount))
-                               wake_up(&mad_agent_priv->wait);
+                       deref_mad_agent(mad_agent_priv);
                        return;
                }
                ib_mark_mad_done(mad_send_wr);
@@ -1703,8 +1826,7 @@ static void ib_mad_complete_recv(struct ib_mad_agent_private *mad_agent_priv,
        } else {
                mad_agent_priv->agent.recv_handler(&mad_agent_priv->agent,
                                                   mad_recv_wc);
-               if (atomic_dec_and_test(&mad_agent_priv->refcount))
-                       wake_up(&mad_agent_priv->wait);
+               deref_mad_agent(mad_agent_priv);
        }
 }
 
@@ -1934,8 +2056,7 @@ void ib_mad_complete_send_wr(struct ib_mad_send_wr_private *mad_send_wr,
                                                   mad_send_wc);
 
        /* Release reference on agent taken when sending */
-       if (atomic_dec_and_test(&mad_agent_priv->refcount))
-               wake_up(&mad_agent_priv->wait);
+       deref_mad_agent(mad_agent_priv);
        return;
 done:
        spin_unlock_irqrestore(&mad_agent_priv->lock, flags);
@@ -2221,6 +2342,7 @@ static void local_completions(void *data)
                local = list_entry(mad_agent_priv->local_list.next,
                                   struct ib_mad_local_private,
                                   completion_list);
+               list_del(&local->completion_list);
                spin_unlock_irqrestore(&mad_agent_priv->lock, flags);
                if (local->mad_priv) {
                        recv_mad_agent = local->recv_mad_agent;
@@ -2272,7 +2394,6 @@ local_send_completion:
                                                   &mad_send_wc);
 
                spin_lock_irqsave(&mad_agent_priv->lock, flags);
-               list_del(&local->completion_list);
                atomic_dec(&mad_agent_priv->refcount);
                if (!recv)
                        kmem_cache_free(ib_mad_cache, local->mad_priv);
@@ -2408,11 +2529,11 @@ static int ib_mad_post_receive_mads(struct ib_mad_qp_info *qp_info,
                        }
                }
                sg_list.addr = dma_map_single(qp_info->port_priv->
-                                               device->dma_device,
-                                       &mad_priv->grh,
-                                       sizeof *mad_priv -
-                                               sizeof mad_priv->header,
-                                       DMA_FROM_DEVICE);
+                                               device->dma_device,
+                                             &mad_priv->grh,
+                                             sizeof *mad_priv -
+                                               sizeof mad_priv->header,
+                                             DMA_FROM_DEVICE);
                pci_unmap_addr_set(&mad_priv->header, mapping, sg_list.addr);
                recv_wr.wr_id = (unsigned long)&mad_priv->header.mad_list;
                mad_priv->header.mad_list.mad_queue = recv_queue;