diff options
Diffstat (limited to 'drivers/infiniband/core/verbs.c')
-rw-r--r-- | drivers/infiniband/core/verbs.c | 295 |
1 files changed, 202 insertions, 93 deletions
diff --git a/drivers/infiniband/core/verbs.c b/drivers/infiniband/core/verbs.c index e1f2c9887f3f..043a60ee6836 100644 --- a/drivers/infiniband/core/verbs.c +++ b/drivers/infiniband/core/verbs.c | |||
@@ -41,6 +41,9 @@ | |||
41 | #include <linux/export.h> | 41 | #include <linux/export.h> |
42 | #include <linux/string.h> | 42 | #include <linux/string.h> |
43 | #include <linux/slab.h> | 43 | #include <linux/slab.h> |
44 | #include <linux/in.h> | ||
45 | #include <linux/in6.h> | ||
46 | #include <net/addrconf.h> | ||
44 | 47 | ||
45 | #include <rdma/ib_verbs.h> | 48 | #include <rdma/ib_verbs.h> |
46 | #include <rdma/ib_cache.h> | 49 | #include <rdma/ib_cache.h> |
@@ -70,7 +73,7 @@ static const char * const ib_events[] = { | |||
70 | [IB_EVENT_GID_CHANGE] = "GID changed", | 73 | [IB_EVENT_GID_CHANGE] = "GID changed", |
71 | }; | 74 | }; |
72 | 75 | ||
73 | const char *ib_event_msg(enum ib_event_type event) | 76 | const char *__attribute_const__ ib_event_msg(enum ib_event_type event) |
74 | { | 77 | { |
75 | size_t index = event; | 78 | size_t index = event; |
76 | 79 | ||
@@ -104,7 +107,7 @@ static const char * const wc_statuses[] = { | |||
104 | [IB_WC_GENERAL_ERR] = "general error", | 107 | [IB_WC_GENERAL_ERR] = "general error", |
105 | }; | 108 | }; |
106 | 109 | ||
107 | const char *ib_wc_status_msg(enum ib_wc_status status) | 110 | const char *__attribute_const__ ib_wc_status_msg(enum ib_wc_status status) |
108 | { | 111 | { |
109 | size_t index = status; | 112 | size_t index = status; |
110 | 113 | ||
@@ -308,6 +311,35 @@ struct ib_ah *ib_create_ah(struct ib_pd *pd, struct ib_ah_attr *ah_attr) | |||
308 | } | 311 | } |
309 | EXPORT_SYMBOL(ib_create_ah); | 312 | EXPORT_SYMBOL(ib_create_ah); |
310 | 313 | ||
314 | struct find_gid_index_context { | ||
315 | u16 vlan_id; | ||
316 | }; | ||
317 | |||
318 | static bool find_gid_index(const union ib_gid *gid, | ||
319 | const struct ib_gid_attr *gid_attr, | ||
320 | void *context) | ||
321 | { | ||
322 | struct find_gid_index_context *ctx = | ||
323 | (struct find_gid_index_context *)context; | ||
324 | |||
325 | if ((!!(ctx->vlan_id != 0xffff) == !is_vlan_dev(gid_attr->ndev)) || | ||
326 | (is_vlan_dev(gid_attr->ndev) && | ||
327 | vlan_dev_vlan_id(gid_attr->ndev) != ctx->vlan_id)) | ||
328 | return false; | ||
329 | |||
330 | return true; | ||
331 | } | ||
332 | |||
333 | static int get_sgid_index_from_eth(struct ib_device *device, u8 port_num, | ||
334 | u16 vlan_id, const union ib_gid *sgid, | ||
335 | u16 *gid_index) | ||
336 | { | ||
337 | struct find_gid_index_context context = {.vlan_id = vlan_id}; | ||
338 | |||
339 | return ib_find_gid_by_filter(device, sgid, port_num, find_gid_index, | ||
340 | &context, gid_index); | ||
341 | } | ||
342 | |||
311 | int ib_init_ah_from_wc(struct ib_device *device, u8 port_num, | 343 | int ib_init_ah_from_wc(struct ib_device *device, u8 port_num, |
312 | const struct ib_wc *wc, const struct ib_grh *grh, | 344 | const struct ib_wc *wc, const struct ib_grh *grh, |
313 | struct ib_ah_attr *ah_attr) | 345 | struct ib_ah_attr *ah_attr) |
@@ -318,21 +350,30 @@ int ib_init_ah_from_wc(struct ib_device *device, u8 port_num, | |||
318 | 350 | ||
319 | memset(ah_attr, 0, sizeof *ah_attr); | 351 | memset(ah_attr, 0, sizeof *ah_attr); |
320 | if (rdma_cap_eth_ah(device, port_num)) { | 352 | if (rdma_cap_eth_ah(device, port_num)) { |
353 | u16 vlan_id = wc->wc_flags & IB_WC_WITH_VLAN ? | ||
354 | wc->vlan_id : 0xffff; | ||
355 | |||
321 | if (!(wc->wc_flags & IB_WC_GRH)) | 356 | if (!(wc->wc_flags & IB_WC_GRH)) |
322 | return -EPROTOTYPE; | 357 | return -EPROTOTYPE; |
323 | 358 | ||
324 | if (wc->wc_flags & IB_WC_WITH_SMAC && | 359 | if (!(wc->wc_flags & IB_WC_WITH_SMAC) || |
325 | wc->wc_flags & IB_WC_WITH_VLAN) { | 360 | !(wc->wc_flags & IB_WC_WITH_VLAN)) { |
326 | memcpy(ah_attr->dmac, wc->smac, ETH_ALEN); | ||
327 | ah_attr->vlan_id = wc->vlan_id; | ||
328 | } else { | ||
329 | ret = rdma_addr_find_dmac_by_grh(&grh->dgid, &grh->sgid, | 361 | ret = rdma_addr_find_dmac_by_grh(&grh->dgid, &grh->sgid, |
330 | ah_attr->dmac, &ah_attr->vlan_id); | 362 | ah_attr->dmac, |
363 | wc->wc_flags & IB_WC_WITH_VLAN ? | ||
364 | NULL : &vlan_id, | ||
365 | 0); | ||
331 | if (ret) | 366 | if (ret) |
332 | return ret; | 367 | return ret; |
333 | } | 368 | } |
334 | } else { | 369 | |
335 | ah_attr->vlan_id = 0xffff; | 370 | ret = get_sgid_index_from_eth(device, port_num, vlan_id, |
371 | &grh->dgid, &gid_index); | ||
372 | if (ret) | ||
373 | return ret; | ||
374 | |||
375 | if (wc->wc_flags & IB_WC_WITH_SMAC) | ||
376 | memcpy(ah_attr->dmac, wc->smac, ETH_ALEN); | ||
336 | } | 377 | } |
337 | 378 | ||
338 | ah_attr->dlid = wc->slid; | 379 | ah_attr->dlid = wc->slid; |
@@ -344,10 +385,13 @@ int ib_init_ah_from_wc(struct ib_device *device, u8 port_num, | |||
344 | ah_attr->ah_flags = IB_AH_GRH; | 385 | ah_attr->ah_flags = IB_AH_GRH; |
345 | ah_attr->grh.dgid = grh->sgid; | 386 | ah_attr->grh.dgid = grh->sgid; |
346 | 387 | ||
347 | ret = ib_find_cached_gid(device, &grh->dgid, &port_num, | 388 | if (!rdma_cap_eth_ah(device, port_num)) { |
348 | &gid_index); | 389 | ret = ib_find_cached_gid_by_port(device, &grh->dgid, |
349 | if (ret) | 390 | port_num, NULL, |
350 | return ret; | 391 | &gid_index); |
392 | if (ret) | ||
393 | return ret; | ||
394 | } | ||
351 | 395 | ||
352 | ah_attr->grh.sgid_index = (u8) gid_index; | 396 | ah_attr->grh.sgid_index = (u8) gid_index; |
353 | flow_class = be32_to_cpu(grh->version_tclass_flow); | 397 | flow_class = be32_to_cpu(grh->version_tclass_flow); |
@@ -617,9 +661,7 @@ EXPORT_SYMBOL(ib_create_qp); | |||
617 | static const struct { | 661 | static const struct { |
618 | int valid; | 662 | int valid; |
619 | enum ib_qp_attr_mask req_param[IB_QPT_MAX]; | 663 | enum ib_qp_attr_mask req_param[IB_QPT_MAX]; |
620 | enum ib_qp_attr_mask req_param_add_eth[IB_QPT_MAX]; | ||
621 | enum ib_qp_attr_mask opt_param[IB_QPT_MAX]; | 664 | enum ib_qp_attr_mask opt_param[IB_QPT_MAX]; |
622 | enum ib_qp_attr_mask opt_param_add_eth[IB_QPT_MAX]; | ||
623 | } qp_state_table[IB_QPS_ERR + 1][IB_QPS_ERR + 1] = { | 665 | } qp_state_table[IB_QPS_ERR + 1][IB_QPS_ERR + 1] = { |
624 | [IB_QPS_RESET] = { | 666 | [IB_QPS_RESET] = { |
625 | [IB_QPS_RESET] = { .valid = 1 }, | 667 | [IB_QPS_RESET] = { .valid = 1 }, |
@@ -700,12 +742,6 @@ static const struct { | |||
700 | IB_QP_MAX_DEST_RD_ATOMIC | | 742 | IB_QP_MAX_DEST_RD_ATOMIC | |
701 | IB_QP_MIN_RNR_TIMER), | 743 | IB_QP_MIN_RNR_TIMER), |
702 | }, | 744 | }, |
703 | .req_param_add_eth = { | ||
704 | [IB_QPT_RC] = (IB_QP_SMAC), | ||
705 | [IB_QPT_UC] = (IB_QP_SMAC), | ||
706 | [IB_QPT_XRC_INI] = (IB_QP_SMAC), | ||
707 | [IB_QPT_XRC_TGT] = (IB_QP_SMAC) | ||
708 | }, | ||
709 | .opt_param = { | 745 | .opt_param = { |
710 | [IB_QPT_UD] = (IB_QP_PKEY_INDEX | | 746 | [IB_QPT_UD] = (IB_QP_PKEY_INDEX | |
711 | IB_QP_QKEY), | 747 | IB_QP_QKEY), |
@@ -726,21 +762,7 @@ static const struct { | |||
726 | [IB_QPT_GSI] = (IB_QP_PKEY_INDEX | | 762 | [IB_QPT_GSI] = (IB_QP_PKEY_INDEX | |
727 | IB_QP_QKEY), | 763 | IB_QP_QKEY), |
728 | }, | 764 | }, |
729 | .opt_param_add_eth = { | 765 | }, |
730 | [IB_QPT_RC] = (IB_QP_ALT_SMAC | | ||
731 | IB_QP_VID | | ||
732 | IB_QP_ALT_VID), | ||
733 | [IB_QPT_UC] = (IB_QP_ALT_SMAC | | ||
734 | IB_QP_VID | | ||
735 | IB_QP_ALT_VID), | ||
736 | [IB_QPT_XRC_INI] = (IB_QP_ALT_SMAC | | ||
737 | IB_QP_VID | | ||
738 | IB_QP_ALT_VID), | ||
739 | [IB_QPT_XRC_TGT] = (IB_QP_ALT_SMAC | | ||
740 | IB_QP_VID | | ||
741 | IB_QP_ALT_VID) | ||
742 | } | ||
743 | } | ||
744 | }, | 766 | }, |
745 | [IB_QPS_RTR] = { | 767 | [IB_QPS_RTR] = { |
746 | [IB_QPS_RESET] = { .valid = 1 }, | 768 | [IB_QPS_RESET] = { .valid = 1 }, |
@@ -962,13 +984,6 @@ int ib_modify_qp_is_ok(enum ib_qp_state cur_state, enum ib_qp_state next_state, | |||
962 | req_param = qp_state_table[cur_state][next_state].req_param[type]; | 984 | req_param = qp_state_table[cur_state][next_state].req_param[type]; |
963 | opt_param = qp_state_table[cur_state][next_state].opt_param[type]; | 985 | opt_param = qp_state_table[cur_state][next_state].opt_param[type]; |
964 | 986 | ||
965 | if (ll == IB_LINK_LAYER_ETHERNET) { | ||
966 | req_param |= qp_state_table[cur_state][next_state]. | ||
967 | req_param_add_eth[type]; | ||
968 | opt_param |= qp_state_table[cur_state][next_state]. | ||
969 | opt_param_add_eth[type]; | ||
970 | } | ||
971 | |||
972 | if ((mask & req_param) != req_param) | 987 | if ((mask & req_param) != req_param) |
973 | return 0; | 988 | return 0; |
974 | 989 | ||
@@ -979,40 +994,52 @@ int ib_modify_qp_is_ok(enum ib_qp_state cur_state, enum ib_qp_state next_state, | |||
979 | } | 994 | } |
980 | EXPORT_SYMBOL(ib_modify_qp_is_ok); | 995 | EXPORT_SYMBOL(ib_modify_qp_is_ok); |
981 | 996 | ||
982 | int ib_resolve_eth_l2_attrs(struct ib_qp *qp, | 997 | int ib_resolve_eth_dmac(struct ib_qp *qp, |
983 | struct ib_qp_attr *qp_attr, int *qp_attr_mask) | 998 | struct ib_qp_attr *qp_attr, int *qp_attr_mask) |
984 | { | 999 | { |
985 | int ret = 0; | 1000 | int ret = 0; |
986 | union ib_gid sgid; | ||
987 | 1001 | ||
988 | if ((*qp_attr_mask & IB_QP_AV) && | 1002 | if (*qp_attr_mask & IB_QP_AV) { |
989 | (rdma_cap_eth_ah(qp->device, qp_attr->ah_attr.port_num))) { | 1003 | if (qp_attr->ah_attr.port_num < rdma_start_port(qp->device) || |
990 | ret = ib_query_gid(qp->device, qp_attr->ah_attr.port_num, | 1004 | qp_attr->ah_attr.port_num > rdma_end_port(qp->device)) |
991 | qp_attr->ah_attr.grh.sgid_index, &sgid); | 1005 | return -EINVAL; |
992 | if (ret) | 1006 | |
993 | goto out; | 1007 | if (!rdma_cap_eth_ah(qp->device, qp_attr->ah_attr.port_num)) |
1008 | return 0; | ||
1009 | |||
994 | if (rdma_link_local_addr((struct in6_addr *)qp_attr->ah_attr.grh.dgid.raw)) { | 1010 | if (rdma_link_local_addr((struct in6_addr *)qp_attr->ah_attr.grh.dgid.raw)) { |
995 | rdma_get_ll_mac((struct in6_addr *)qp_attr->ah_attr.grh.dgid.raw, qp_attr->ah_attr.dmac); | 1011 | rdma_get_ll_mac((struct in6_addr *)qp_attr->ah_attr.grh.dgid.raw, |
996 | rdma_get_ll_mac((struct in6_addr *)sgid.raw, qp_attr->smac); | 1012 | qp_attr->ah_attr.dmac); |
997 | if (!(*qp_attr_mask & IB_QP_VID)) | ||
998 | qp_attr->vlan_id = rdma_get_vlan_id(&sgid); | ||
999 | } else { | 1013 | } else { |
1000 | ret = rdma_addr_find_dmac_by_grh(&sgid, &qp_attr->ah_attr.grh.dgid, | 1014 | union ib_gid sgid; |
1001 | qp_attr->ah_attr.dmac, &qp_attr->vlan_id); | 1015 | struct ib_gid_attr sgid_attr; |
1002 | if (ret) | 1016 | int ifindex; |
1003 | goto out; | 1017 | |
1004 | ret = rdma_addr_find_smac_by_sgid(&sgid, qp_attr->smac, NULL); | 1018 | ret = ib_query_gid(qp->device, |
1005 | if (ret) | 1019 | qp_attr->ah_attr.port_num, |
1020 | qp_attr->ah_attr.grh.sgid_index, | ||
1021 | &sgid, &sgid_attr); | ||
1022 | |||
1023 | if (ret || !sgid_attr.ndev) { | ||
1024 | if (!ret) | ||
1025 | ret = -ENXIO; | ||
1006 | goto out; | 1026 | goto out; |
1027 | } | ||
1028 | |||
1029 | ifindex = sgid_attr.ndev->ifindex; | ||
1030 | |||
1031 | ret = rdma_addr_find_dmac_by_grh(&sgid, | ||
1032 | &qp_attr->ah_attr.grh.dgid, | ||
1033 | qp_attr->ah_attr.dmac, | ||
1034 | NULL, ifindex); | ||
1035 | |||
1036 | dev_put(sgid_attr.ndev); | ||
1007 | } | 1037 | } |
1008 | *qp_attr_mask |= IB_QP_SMAC; | ||
1009 | if (qp_attr->vlan_id < 0xFFFF) | ||
1010 | *qp_attr_mask |= IB_QP_VID; | ||
1011 | } | 1038 | } |
1012 | out: | 1039 | out: |
1013 | return ret; | 1040 | return ret; |
1014 | } | 1041 | } |
1015 | EXPORT_SYMBOL(ib_resolve_eth_l2_attrs); | 1042 | EXPORT_SYMBOL(ib_resolve_eth_dmac); |
1016 | 1043 | ||
1017 | 1044 | ||
1018 | int ib_modify_qp(struct ib_qp *qp, | 1045 | int ib_modify_qp(struct ib_qp *qp, |
@@ -1021,7 +1048,7 @@ int ib_modify_qp(struct ib_qp *qp, | |||
1021 | { | 1048 | { |
1022 | int ret; | 1049 | int ret; |
1023 | 1050 | ||
1024 | ret = ib_resolve_eth_l2_attrs(qp, qp_attr, &qp_attr_mask); | 1051 | ret = ib_resolve_eth_dmac(qp, qp_attr, &qp_attr_mask); |
1025 | if (ret) | 1052 | if (ret) |
1026 | return ret; | 1053 | return ret; |
1027 | 1054 | ||
@@ -1253,31 +1280,6 @@ struct ib_mr *ib_alloc_mr(struct ib_pd *pd, | |||
1253 | } | 1280 | } |
1254 | EXPORT_SYMBOL(ib_alloc_mr); | 1281 | EXPORT_SYMBOL(ib_alloc_mr); |
1255 | 1282 | ||
1256 | struct ib_fast_reg_page_list *ib_alloc_fast_reg_page_list(struct ib_device *device, | ||
1257 | int max_page_list_len) | ||
1258 | { | ||
1259 | struct ib_fast_reg_page_list *page_list; | ||
1260 | |||
1261 | if (!device->alloc_fast_reg_page_list) | ||
1262 | return ERR_PTR(-ENOSYS); | ||
1263 | |||
1264 | page_list = device->alloc_fast_reg_page_list(device, max_page_list_len); | ||
1265 | |||
1266 | if (!IS_ERR(page_list)) { | ||
1267 | page_list->device = device; | ||
1268 | page_list->max_page_list_len = max_page_list_len; | ||
1269 | } | ||
1270 | |||
1271 | return page_list; | ||
1272 | } | ||
1273 | EXPORT_SYMBOL(ib_alloc_fast_reg_page_list); | ||
1274 | |||
1275 | void ib_free_fast_reg_page_list(struct ib_fast_reg_page_list *page_list) | ||
1276 | { | ||
1277 | page_list->device->free_fast_reg_page_list(page_list); | ||
1278 | } | ||
1279 | EXPORT_SYMBOL(ib_free_fast_reg_page_list); | ||
1280 | |||
1281 | /* Memory windows */ | 1283 | /* Memory windows */ |
1282 | 1284 | ||
1283 | struct ib_mw *ib_alloc_mw(struct ib_pd *pd, enum ib_mw_type type) | 1285 | struct ib_mw *ib_alloc_mw(struct ib_pd *pd, enum ib_mw_type type) |
@@ -1469,3 +1471,110 @@ int ib_check_mr_status(struct ib_mr *mr, u32 check_mask, | |||
1469 | mr->device->check_mr_status(mr, check_mask, mr_status) : -ENOSYS; | 1471 | mr->device->check_mr_status(mr, check_mask, mr_status) : -ENOSYS; |
1470 | } | 1472 | } |
1471 | EXPORT_SYMBOL(ib_check_mr_status); | 1473 | EXPORT_SYMBOL(ib_check_mr_status); |
1474 | |||
1475 | /** | ||
1476 | * ib_map_mr_sg() - Map the largest prefix of a dma mapped SG list | ||
1477 | * and set it the memory region. | ||
1478 | * @mr: memory region | ||
1479 | * @sg: dma mapped scatterlist | ||
1480 | * @sg_nents: number of entries in sg | ||
1481 | * @page_size: page vector desired page size | ||
1482 | * | ||
1483 | * Constraints: | ||
1484 | * - The first sg element is allowed to have an offset. | ||
1485 | * - Each sg element must be aligned to page_size (or physically | ||
1486 | * contiguous to the previous element). In case an sg element has a | ||
1487 | * non contiguous offset, the mapping prefix will not include it. | ||
1488 | * - The last sg element is allowed to have length less than page_size. | ||
1489 | * - If sg_nents total byte length exceeds the mr max_num_sge * page_size | ||
1490 | * then only max_num_sg entries will be mapped. | ||
1491 | * | ||
1492 | * Returns the number of sg elements that were mapped to the memory region. | ||
1493 | * | ||
1494 | * After this completes successfully, the memory region | ||
1495 | * is ready for registration. | ||
1496 | */ | ||
1497 | int ib_map_mr_sg(struct ib_mr *mr, | ||
1498 | struct scatterlist *sg, | ||
1499 | int sg_nents, | ||
1500 | unsigned int page_size) | ||
1501 | { | ||
1502 | if (unlikely(!mr->device->map_mr_sg)) | ||
1503 | return -ENOSYS; | ||
1504 | |||
1505 | mr->page_size = page_size; | ||
1506 | |||
1507 | return mr->device->map_mr_sg(mr, sg, sg_nents); | ||
1508 | } | ||
1509 | EXPORT_SYMBOL(ib_map_mr_sg); | ||
1510 | |||
1511 | /** | ||
1512 | * ib_sg_to_pages() - Convert the largest prefix of a sg list | ||
1513 | * to a page vector | ||
1514 | * @mr: memory region | ||
1515 | * @sgl: dma mapped scatterlist | ||
1516 | * @sg_nents: number of entries in sg | ||
1517 | * @set_page: driver page assignment function pointer | ||
1518 | * | ||
1519 | * Core service helper for drivers to covert the largest | ||
1520 | * prefix of given sg list to a page vector. The sg list | ||
1521 | * prefix converted is the prefix that meet the requirements | ||
1522 | * of ib_map_mr_sg. | ||
1523 | * | ||
1524 | * Returns the number of sg elements that were assigned to | ||
1525 | * a page vector. | ||
1526 | */ | ||
1527 | int ib_sg_to_pages(struct ib_mr *mr, | ||
1528 | struct scatterlist *sgl, | ||
1529 | int sg_nents, | ||
1530 | int (*set_page)(struct ib_mr *, u64)) | ||
1531 | { | ||
1532 | struct scatterlist *sg; | ||
1533 | u64 last_end_dma_addr = 0, last_page_addr = 0; | ||
1534 | unsigned int last_page_off = 0; | ||
1535 | u64 page_mask = ~((u64)mr->page_size - 1); | ||
1536 | int i; | ||
1537 | |||
1538 | mr->iova = sg_dma_address(&sgl[0]); | ||
1539 | mr->length = 0; | ||
1540 | |||
1541 | for_each_sg(sgl, sg, sg_nents, i) { | ||
1542 | u64 dma_addr = sg_dma_address(sg); | ||
1543 | unsigned int dma_len = sg_dma_len(sg); | ||
1544 | u64 end_dma_addr = dma_addr + dma_len; | ||
1545 | u64 page_addr = dma_addr & page_mask; | ||
1546 | |||
1547 | if (i && page_addr != dma_addr) { | ||
1548 | if (last_end_dma_addr != dma_addr) { | ||
1549 | /* gap */ | ||
1550 | goto done; | ||
1551 | |||
1552 | } else if (last_page_off + dma_len <= mr->page_size) { | ||
1553 | /* chunk this fragment with the last */ | ||
1554 | mr->length += dma_len; | ||
1555 | last_end_dma_addr += dma_len; | ||
1556 | last_page_off += dma_len; | ||
1557 | continue; | ||
1558 | } else { | ||
1559 | /* map starting from the next page */ | ||
1560 | page_addr = last_page_addr + mr->page_size; | ||
1561 | dma_len -= mr->page_size - last_page_off; | ||
1562 | } | ||
1563 | } | ||
1564 | |||
1565 | do { | ||
1566 | if (unlikely(set_page(mr, page_addr))) | ||
1567 | goto done; | ||
1568 | page_addr += mr->page_size; | ||
1569 | } while (page_addr < end_dma_addr); | ||
1570 | |||
1571 | mr->length += dma_len; | ||
1572 | last_end_dma_addr = end_dma_addr; | ||
1573 | last_page_addr = end_dma_addr & page_mask; | ||
1574 | last_page_off = end_dma_addr & ~page_mask; | ||
1575 | } | ||
1576 | |||
1577 | done: | ||
1578 | return i; | ||
1579 | } | ||
1580 | EXPORT_SYMBOL(ib_sg_to_pages); | ||