/** * brcmf_sdiod_sglist_rw - SDIO interface function for block data access * @sdiodev: brcmfmac sdio device * @func: SDIO function * @write: direction flag * @addr: dongle memory address as source/destination * @pkt: skb pointer * * This function takes the respbonsibility as the interface function to MMC * stack for block data access. It assumes that the skb passed down by the * caller has already been padded and aligned. */ static int brcmf_sdiod_sglist_rw(struct brcmf_sdio_dev *sdiodev, struct sdio_func *func, bool write, u32 addr, struct sk_buff_head *pktlist) { unsigned int req_sz, func_blk_sz, sg_cnt, sg_data_sz, pkt_offset; unsigned int max_req_sz, orig_offset, dst_offset; unsigned short max_seg_cnt, seg_sz; unsigned char *pkt_data, *orig_data, *dst_data; struct sk_buff *pkt_next = NULL, *local_pkt_next; struct sk_buff_head local_list, *target_list; struct mmc_request mmc_req; struct mmc_command mmc_cmd; struct mmc_data mmc_dat; struct scatterlist *sgl; int ret = 0; if (!pktlist->qlen) return -EINVAL; target_list = pktlist; /* for host with broken sg support, prepare a page aligned list */ __skb_queue_head_init(&local_list); if (!write && sdiodev->settings->bus.sdio.broken_sg_support) { req_sz = 0; skb_queue_walk(pktlist, pkt_next) req_sz += pkt_next->len; req_sz = ALIGN(req_sz, func->cur_blksize); while (req_sz > PAGE_SIZE) { pkt_next = brcmu_pkt_buf_get_skb(PAGE_SIZE); if (pkt_next == NULL) { ret = -ENOMEM; goto exit; } __skb_queue_tail(&local_list, pkt_next); req_sz -= PAGE_SIZE; } pkt_next = brcmu_pkt_buf_get_skb(req_sz); if (pkt_next == NULL) { ret = -ENOMEM; goto exit; } __skb_queue_tail(&local_list, pkt_next); target_list = &local_list; } func_blk_sz = func->cur_blksize; max_req_sz = sdiodev->max_request_size; max_seg_cnt = min_t(unsigned short, sdiodev->max_segment_count, target_list->qlen); seg_sz = target_list->qlen; pkt_offset = 0; pkt_next = target_list->next; memset(&mmc_req, 0, sizeof(struct mmc_request)); memset(&mmc_cmd, 0, sizeof(struct mmc_command)); memset(&mmc_dat, 0, sizeof(struct mmc_data)); mmc_dat.sg = sdiodev->sgtable.sgl; mmc_dat.blksz = func_blk_sz; mmc_dat.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ; mmc_cmd.opcode = SD_IO_RW_EXTENDED; mmc_cmd.arg = write ? 1<<31 : 0; /* write flag */ mmc_cmd.arg |= (func->num & 0x7) << 28; /* SDIO func num */ mmc_cmd.arg |= 1 << 27; /* block mode */ /* for function 1 the addr will be incremented */ mmc_cmd.arg |= (func->num == 1) ? 1 << 26 : 0; mmc_cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC; mmc_req.cmd = &mmc_cmd; mmc_req.data = &mmc_dat; while (seg_sz) { req_sz = 0; sg_cnt = 0; sgl = sdiodev->sgtable.sgl; /* prep sg table */ while (pkt_next != (struct sk_buff *)target_list) { pkt_data = pkt_next->data + pkt_offset; sg_data_sz = pkt_next->len - pkt_offset; if (sg_data_sz > sdiodev->max_segment_size) sg_data_sz = sdiodev->max_segment_size; if (sg_data_sz > max_req_sz - req_sz) sg_data_sz = max_req_sz - req_sz; sg_set_buf(sgl, pkt_data, sg_data_sz); sg_cnt++; sgl = sg_next(sgl); req_sz += sg_data_sz; pkt_offset += sg_data_sz; if (pkt_offset == pkt_next->len) { pkt_offset = 0; pkt_next = pkt_next->next; } if (req_sz >= max_req_sz || sg_cnt >= max_seg_cnt) break; } seg_sz -= sg_cnt; if (req_sz % func_blk_sz != 0) { brcmf_err("sg request length %u is not %u aligned\n", req_sz, func_blk_sz); ret = -ENOTBLK; goto exit; } mmc_dat.sg_len = sg_cnt; mmc_dat.blocks = req_sz / func_blk_sz; mmc_cmd.arg |= (addr & 0x1FFFF) << 9; /* address */ mmc_cmd.arg |= mmc_dat.blocks & 0x1FF; /* block count */ /* incrementing addr for function 1 */ if (func->num == 1) addr += req_sz; mmc_set_data_timeout(&mmc_dat, func->card); mmc_wait_for_req(func->card->host, &mmc_req); ret = mmc_cmd.error ? mmc_cmd.error : mmc_dat.error; if (ret == -ENOMEDIUM) { brcmf_sdiod_change_state(sdiodev, BRCMF_SDIOD_NOMEDIUM); break; } else if (ret != 0) { brcmf_err("CMD53 sg block %s failed %d\n", write ? "write" : "read", ret); ret = -EIO; break; } } if (!write && sdiodev->settings->bus.sdio.broken_sg_support) { local_pkt_next = local_list.next; orig_offset = 0; skb_queue_walk(pktlist, pkt_next) { dst_offset = 0; do { req_sz = local_pkt_next->len - orig_offset; req_sz = min_t(uint, pkt_next->len - dst_offset, req_sz); orig_data = local_pkt_next->data + orig_offset; dst_data = pkt_next->data + dst_offset; memcpy(dst_data, orig_data, req_sz); orig_offset += req_sz; dst_offset += req_sz; if (orig_offset == local_pkt_next->len) { orig_offset = 0; local_pkt_next = local_pkt_next->next; } if (dst_offset == pkt_next->len) break; } while (!skb_queue_empty(&local_list)); }
/** * brcmf_sdiod_sglist_rw - SDIO interface function for block data access * @sdiodev: brcmfmac sdio device * @func: SDIO function * @write: direction flag * @addr: dongle memory address as source/destination * @pkt: skb pointer * * This function takes the respbonsibility as the interface function to MMC * stack for block data access. It assumes that the skb passed down by the * caller has already been padded and aligned. */ static int brcmf_sdiod_sglist_rw(struct brcmf_sdio_dev *sdiodev, struct sdio_func *func, bool write, u32 addr, struct sk_buff_head *pktlist) { unsigned int req_sz, func_blk_sz, sg_cnt, sg_data_sz, pkt_offset; unsigned int max_req_sz, src_offset, dst_offset; unsigned char *pkt_data, *orig_data, *dst_data; struct sk_buff_head local_list, *target_list; struct sk_buff *pkt_next = NULL, *src; unsigned short max_seg_cnt; struct mmc_request mmc_req; struct mmc_command mmc_cmd; struct mmc_data mmc_dat; struct scatterlist *sgl; int ret = 0; if (!pktlist->qlen) return -EINVAL; target_list = pktlist; /* for host with broken sg support, prepare a page aligned list */ __skb_queue_head_init(&local_list); if (!write && sdiodev->settings->bus.sdio.broken_sg_support) { req_sz = 0; skb_queue_walk(pktlist, pkt_next) req_sz += pkt_next->len; req_sz = ALIGN(req_sz, func->cur_blksize); while (req_sz > PAGE_SIZE) { pkt_next = brcmu_pkt_buf_get_skb(PAGE_SIZE); if (pkt_next == NULL) { ret = -ENOMEM; goto exit; } __skb_queue_tail(&local_list, pkt_next); req_sz -= PAGE_SIZE; } pkt_next = brcmu_pkt_buf_get_skb(req_sz); if (pkt_next == NULL) { ret = -ENOMEM; goto exit; } __skb_queue_tail(&local_list, pkt_next); target_list = &local_list; } func_blk_sz = func->cur_blksize; max_req_sz = sdiodev->max_request_size; max_seg_cnt = min_t(unsigned short, sdiodev->max_segment_count, target_list->qlen); memset(&mmc_req, 0, sizeof(struct mmc_request)); memset(&mmc_cmd, 0, sizeof(struct mmc_command)); memset(&mmc_dat, 0, sizeof(struct mmc_data)); mmc_dat.sg = sdiodev->sgtable.sgl; mmc_dat.blksz = func_blk_sz; mmc_dat.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ; mmc_cmd.opcode = SD_IO_RW_EXTENDED; mmc_cmd.arg = write ? 1<<31 : 0; /* write flag */ mmc_cmd.arg |= (func->num & 0x7) << 28; /* SDIO func num */ mmc_cmd.arg |= 1 << 27; /* block mode */ /* for function 1 the addr will be incremented */ mmc_cmd.arg |= (func->num == 1) ? 1 << 26 : 0; mmc_cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC; mmc_req.cmd = &mmc_cmd; mmc_req.data = &mmc_dat; req_sz = 0; sg_cnt = 0; sgl = sdiodev->sgtable.sgl; skb_queue_walk(target_list, pkt_next) { pkt_offset = 0; while (pkt_offset < pkt_next->len) { pkt_data = pkt_next->data + pkt_offset; sg_data_sz = pkt_next->len - pkt_offset; if (sg_data_sz > sdiodev->max_segment_size) sg_data_sz = sdiodev->max_segment_size; if (sg_data_sz > max_req_sz - req_sz) sg_data_sz = max_req_sz - req_sz; sg_set_buf(sgl, pkt_data, sg_data_sz); sg_cnt++; sgl = sg_next(sgl); req_sz += sg_data_sz; pkt_offset += sg_data_sz; if (req_sz >= max_req_sz || sg_cnt >= max_seg_cnt) { ret = mmc_submit_one(&mmc_dat, &mmc_req, &mmc_cmd, sg_cnt, req_sz, func_blk_sz, &addr, sdiodev, func, write); if (ret) goto exit_queue_walk; req_sz = 0; sg_cnt = 0; sgl = sdiodev->sgtable.sgl; } } }
/* * This function takes a buffer or packet, and fixes everything up * so that in the end, a DMA-able packet is created. * * A buffer does not have an associated packet pointer, * and may or may not be aligned. * A packet may consist of a single packet, or a packet chain. * If it is a packet chain, then all the packets in the chain * must be properly aligned. * * If the packet data is not aligned, then there may only be * one packet, and in this case, it is copied to a new * aligned packet. * */ int brcmf_sdioh_request_buffer(struct brcmf_sdio_dev *sdiodev, uint fix_inc, uint write, uint func, uint addr, uint reg_width, uint buflen_u, u8 *buffer, struct sk_buff *pkt) { int Status; struct sk_buff *mypkt = NULL; brcmf_dbg(TRACE, "Enter\n"); brcmf_pm_resume_wait(sdiodev, &sdiodev->request_buffer_wait); if (brcmf_pm_resume_error(sdiodev)) return -EIO; /* Case 1: we don't have a packet. */ if (pkt == NULL) { brcmf_dbg(DATA, "Creating new %s Packet, len=%d\n", write ? "TX" : "RX", buflen_u); mypkt = brcmu_pkt_buf_get_skb(buflen_u); if (!mypkt) { brcmf_dbg(ERROR, "brcmu_pkt_buf_get_skb failed: len %d\n", buflen_u); return -EIO; } /* For a write, copy the buffer data into the packet. */ if (write) memcpy(mypkt->data, buffer, buflen_u); Status = brcmf_sdioh_request_packet(sdiodev, fix_inc, write, func, addr, mypkt); /* For a read, copy the packet data back to the buffer. */ if (!write) memcpy(buffer, mypkt->data, buflen_u); brcmu_pkt_buf_free_skb(mypkt); } else if (((ulong) (pkt->data) & DMA_ALIGN_MASK) != 0) { /* * Case 2: We have a packet, but it is unaligned. * In this case, we cannot have a chain (pkt->next == NULL) */ brcmf_dbg(DATA, "Creating aligned %s Packet, len=%d\n", write ? "TX" : "RX", pkt->len); mypkt = brcmu_pkt_buf_get_skb(pkt->len); if (!mypkt) { brcmf_dbg(ERROR, "brcmu_pkt_buf_get_skb failed: len %d\n", pkt->len); return -EIO; } /* For a write, copy the buffer data into the packet. */ if (write) memcpy(mypkt->data, pkt->data, pkt->len); Status = brcmf_sdioh_request_packet(sdiodev, fix_inc, write, func, addr, mypkt); /* For a read, copy the packet data back to the buffer. */ if (!write) memcpy(pkt->data, mypkt->data, mypkt->len); brcmu_pkt_buf_free_skb(mypkt); } else { /* case 3: We have a packet and it is aligned. */ brcmf_dbg(DATA, "Aligned %s Packet, direct DMA\n", write ? "Tx" : "Rx"); Status = brcmf_sdioh_request_packet(sdiodev, fix_inc, write, func, addr, pkt); } return Status; }